diff --git a/.changeset/nine-rivers-flow.md b/.changeset/nine-rivers-flow.md
new file mode 100644
index 0000000..14d320b
--- /dev/null
+++ b/.changeset/nine-rivers-flow.md
@@ -0,0 +1,5 @@
+---
+"saleor-app-klaviyo": patch
+---
+
+Fixed error where config couldn't be saved
diff --git a/.changeset/real-pigs-promise.md b/.changeset/real-pigs-promise.md
new file mode 100644
index 0000000..c8ce3bb
--- /dev/null
+++ b/.changeset/real-pigs-promise.md
@@ -0,0 +1,5 @@
+---
+"saleor-app-invoices": minor
+---
+
+Replace text "loading" messages with skeletons
diff --git a/.changeset/twelve-pianos-relate.md b/.changeset/twelve-pianos-relate.md
new file mode 100644
index 0000000..2fcc79c
--- /dev/null
+++ b/.changeset/twelve-pianos-relate.md
@@ -0,0 +1,5 @@
+---
+"saleor-app-invoices": minor
+---
+
+Redesigned app layout. Now app uses shared sections as other apps.
diff --git a/.changeset/wicked-llamas-talk.md b/.changeset/wicked-llamas-talk.md
new file mode 100644
index 0000000..1f44420
--- /dev/null
+++ b/.changeset/wicked-llamas-talk.md
@@ -0,0 +1,5 @@
+---
+"saleor-app-klaviyo": minor
+---
+
+Improved app layout to match modern style.
diff --git a/apps/invoices/next.config.js b/apps/invoices/next.config.js
index 6f81e6a..9d3f40c 100644
--- a/apps/invoices/next.config.js
+++ b/apps/invoices/next.config.js
@@ -1,13 +1,18 @@
const { withSentryConfig } = require("@sentry/nextjs");
const isSentryPropertiesInEnvironment = Boolean(
- process.env.SENTRY_AUTH_TOKEN && process.env.SENTRY_PROJECT && process.env.SENTRY_ORG
+ process.env.SENTRY_AUTH_TOKEN && process.env.SENTRY_PROJECT && process.env.SENTRY_ORG,
);
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
- transpilePackages: ["@saleor/apps-shared", "@saleor/apps-ui", "@saleor/react-hook-form-macaw"],
+ transpilePackages: [
+ "@saleor/apps-shared",
+ "@saleor/apps-ui",
+ "@saleor/react-hook-form-macaw",
+ "@saleor/trpc",
+ ],
};
const configWithSentry = withSentryConfig(
@@ -23,9 +28,7 @@ const configWithSentry = withSentryConfig(
tunnelRoute: "/monitoring",
hideSourceMaps: true,
disableLogger: true,
- }
+ },
);
-
module.exports = isSentryPropertiesInEnvironment ? configWithSentry : nextConfig;
-
diff --git a/apps/invoices/package.json b/apps/invoices/package.json
index 20e7ff5..7282de7 100644
--- a/apps/invoices/package.json
+++ b/apps/invoices/package.json
@@ -15,7 +15,9 @@
"@hookform/resolvers": "^3.1.0",
"@saleor/app-sdk": "0.41.1",
"@saleor/apps-shared": "workspace:*",
+ "@saleor/apps-ui": "workspace:*",
"@saleor/macaw-ui": "0.8.0-pre.127",
+ "@saleor/trpc": "workspace:*",
"@sentry/nextjs": "7.67.0",
"@tanstack/react-query": "4.29.19",
"@trpc/client": "10.34.0",
diff --git a/apps/invoices/src/lib/theme-synchronizer.tsx b/apps/invoices/src/lib/theme-synchronizer.tsx
deleted file mode 100644
index 2ae0fe6..0000000
--- a/apps/invoices/src/lib/theme-synchronizer.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import { useAppBridge } from "@saleor/app-sdk/app-bridge";
-import { useTheme } from "@saleor/macaw-ui/next";
-import { memo, useEffect } from "react";
-
-// todo move to shared
-export function ThemeSynchronizer() {
- const { appBridgeState } = useAppBridge();
- const { setTheme } = useTheme();
-
- useEffect(() => {
- if (!setTheme || !appBridgeState?.theme) {
- return;
- }
-
- if (appBridgeState.theme === "light") {
- setTheme("defaultLight");
- }
-
- if (appBridgeState.theme === "dark") {
- setTheme("defaultDark");
- }
- }, [appBridgeState?.theme, setTheme]);
-
- return null;
-}
diff --git a/apps/invoices/src/modules/app-configuration/app-configuration-router.test.ts b/apps/invoices/src/modules/app-configuration/app-configuration-router.test.ts
index aebe150..c0457bd 100644
--- a/apps/invoices/src/modules/app-configuration/app-configuration-router.test.ts
+++ b/apps/invoices/src/modules/app-configuration/app-configuration-router.test.ts
@@ -53,6 +53,8 @@ describe("appConfigurationRouter", function () {
token: "TOKEN",
saleorApiUrl: "http://localhost:8000/graphql/",
appId: "app",
+ ssr: true,
+ baseUrl: "localhost:3000",
})
.upsertChannelOverride({
channelSlug: "test",
diff --git a/apps/invoices/src/modules/app-configuration/ui/address-form.tsx b/apps/invoices/src/modules/app-configuration/ui/address-form.tsx
index fc8b764..dd77680 100644
--- a/apps/invoices/src/modules/app-configuration/ui/address-form.tsx
+++ b/apps/invoices/src/modules/app-configuration/ui/address-form.tsx
@@ -1,13 +1,14 @@
import { Controller, useForm } from "react-hook-form";
-import React, { useCallback, useEffect } from "react";
-import { Box, Button, Input, Text } from "@saleor/macaw-ui/next";
-import { SellerAddress } from "../address";
-import { trpcClient } from "../../trpc/trpc-client";
import { zodResolver } from "@hookform/resolvers/zod";
-import { z } from "zod";
import { useDashboardNotification } from "@saleor/apps-shared";
+import { ButtonsBox, Layout, SkeletonLayout } from "@saleor/apps-ui";
+import { Box, Button, Input, Text } from "@saleor/macaw-ui/next";
import { useRouter } from "next/router";
+import { useCallback } from "react";
+import { z } from "zod";
+import { trpcClient } from "../../trpc/trpc-client";
+import { SellerAddress } from "../address";
import { AddressV2Schema, AddressV2Shape } from "../schema-v2/app-config-schema.v2";
type Props = {
@@ -57,12 +58,29 @@ export const AddressForm = (props: Props & InnerFormProps) => {
});
return (
-
+
);
};
@@ -164,9 +168,6 @@ export const ConnectedAddressForm = (props: Props) => {
const { push } = useRouter();
- const addressData =
- channelOverrideConfigQuery.data && channelOverrideConfigQuery.data[props.channelSlug];
-
const submitHandler = useCallback(
async (data: AddressV2Shape) => {
return upsertConfigMutation.mutate({
@@ -174,7 +175,7 @@ export const ConnectedAddressForm = (props: Props) => {
channelSlug: props.channelSlug,
});
},
- [props.channelSlug, upsertConfigMutation]
+ [props.channelSlug, upsertConfigMutation],
);
const onCancelHandler = useCallback(() => {
@@ -182,7 +183,7 @@ export const ConnectedAddressForm = (props: Props) => {
}, [push]);
if (channelOverrideConfigQuery.isLoading) {
- return Loading;
+ return ;
}
return (
diff --git a/apps/invoices/src/modules/app-configuration/views/app-config.view.tsx b/apps/invoices/src/modules/app-configuration/views/app-config.view.tsx
index 35dba1f..0e680fa 100644
--- a/apps/invoices/src/modules/app-configuration/views/app-config.view.tsx
+++ b/apps/invoices/src/modules/app-configuration/views/app-config.view.tsx
@@ -1,29 +1,25 @@
import { Box, Text } from "@saleor/macaw-ui/next";
import { DefaultShopAddress } from "../../shop-info/ui/default-shop-address";
-import { AppSection } from "../../ui/AppSection";
import { PerChannelConfigList } from "../../channels/ui/per-channel-config-list";
import { actions, useAppBridge } from "@saleor/app-sdk/app-bridge";
+import { Layout } from "@saleor/apps-ui";
export const AppConfigView = () => {
const { appBridge } = useAppBridge();
return (
-
-
-
- Configuration
-
-
- The Invoices App will generate invoices for each order, for which{" "}
- INVOICE_REQUESTED
event will be triggered
-
+
+ Configuration
+
+
+ The Invoices App will generate invoices for each order, for which{" "}
+ INVOICE_REQUESTED
event will be triggered
+
+
By default it will use{" "}
{
appBridge?.dispatch(
actions.Redirect({
to: "/site-settings",
- })
+ }),
);
}}
>
@@ -40,22 +36,25 @@ export const AppConfigView = () => {
{" "}
address, but each channel can be configured separately
-
-
-
-
-
-
+
+
+
+ }
sideContent={
Configure custom billing address for each channel. If not set, default shop address will
be used
}
- />
+ >
+
+
+
+
);
};
diff --git a/apps/invoices/src/modules/app-configuration/views/channel-config.view.tsx b/apps/invoices/src/modules/app-configuration/views/channel-config.view.tsx
index 489b1da..042ac41 100644
--- a/apps/invoices/src/modules/app-configuration/views/channel-config.view.tsx
+++ b/apps/invoices/src/modules/app-configuration/views/channel-config.view.tsx
@@ -1,9 +1,9 @@
import { Box, ChevronRightIcon, Text, Button } from "@saleor/macaw-ui/next";
-import { AppSection } from "../../ui/AppSection";
import { useRouter } from "next/router";
import { ConnectedAddressForm } from "../ui/address-form";
import { trpcClient } from "../../trpc/trpc-client";
import { useDashboardNotification } from "@saleor/apps-shared";
+import { Layout } from "@saleor/apps-ui";
export const ChannelConfigView = () => {
const {
@@ -15,7 +15,7 @@ export const ChannelConfigView = () => {
const { notifySuccess } = useDashboardNotification();
if (!channel) {
- return null;
+ return null; // TODO: error
}
return (
@@ -29,10 +29,9 @@ export const ChannelConfigView = () => {
{channel}
- }
sideContent={
@@ -51,7 +50,9 @@ export const ChannelConfigView = () => {
}
- />
+ >
+
+
);
};
diff --git a/apps/invoices/src/modules/channels/ui/per-channel-config-list.tsx b/apps/invoices/src/modules/channels/ui/per-channel-config-list.tsx
index 18b2961..65a2950 100644
--- a/apps/invoices/src/modules/channels/ui/per-channel-config-list.tsx
+++ b/apps/invoices/src/modules/channels/ui/per-channel-config-list.tsx
@@ -1,6 +1,7 @@
import { Box, Text, Chip, Button } from "@saleor/macaw-ui/next";
import { trpcClient } from "../../trpc/trpc-client";
import { useRouter } from "next/router";
+import { SkeletonLayout } from "@saleor/apps-ui";
const defaultAddressChip = (
@@ -17,7 +18,7 @@ export const PerChannelConfigList = () => {
const { push } = useRouter();
if (shopChannelsQuery.isLoading || channelsOverridesQuery.isLoading) {
- return Loading...;
+ return ;
}
const renderChannelAddress = (slug: string) => {
diff --git a/apps/invoices/src/modules/shop-info/ui/default-shop-address.tsx b/apps/invoices/src/modules/shop-info/ui/default-shop-address.tsx
index 171e5d0..e6d5b2e 100644
--- a/apps/invoices/src/modules/shop-info/ui/default-shop-address.tsx
+++ b/apps/invoices/src/modules/shop-info/ui/default-shop-address.tsx
@@ -2,30 +2,31 @@ import { Box, Text, Button } from "@saleor/macaw-ui/next";
import { trpcClient } from "../../trpc/trpc-client";
import { PropsWithChildren } from "react";
import { actions, useAppBridge } from "@saleor/app-sdk/app-bridge";
+import { ButtonsBox, Layout, SkeletonLayout } from "@saleor/apps-ui";
const Wrapper = ({ children }: PropsWithChildren<{}>) => {
const { appBridge } = useAppBridge();
return (
-
-
- Default address of the shop
-
-
+
+
+
+ }
+ >
{children}
-
+
);
};
@@ -46,7 +47,7 @@ export const DefaultShopAddress = () => {
if (isLoading) {
return (
- Loading...
+
);
}
@@ -70,6 +71,9 @@ export const DefaultShopAddress = () => {
if (data && data.companyAddress) {
return (
+
+ This address will be used if custom address is not set for channel
+
{data.companyAddress.companyName}
diff --git a/apps/invoices/src/modules/trpc/trpc-client.ts b/apps/invoices/src/modules/trpc/trpc-client.ts
index 7411da9..cca3b09 100644
--- a/apps/invoices/src/modules/trpc/trpc-client.ts
+++ b/apps/invoices/src/modules/trpc/trpc-client.ts
@@ -1,34 +1,13 @@
-import { httpBatchLink } from "@trpc/client";
import { createTRPCNext } from "@trpc/next";
-import { SALEOR_API_URL_HEADER, SALEOR_AUTHORIZATION_BEARER_HEADER } from "@saleor/app-sdk/const";
+import { createHttpBatchLink } from "@saleor/trpc";
import { appBridgeInstance } from "../../pages/_app";
import { AppRouter } from "./trpc-app-router";
-function getBaseUrl() {
- if (typeof window !== "undefined") return "";
- if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`;
-
- return `http://localhost:${process.env.PORT ?? 3000}`;
-}
-
export const trpcClient = createTRPCNext({
- config({ ctx }) {
+ config() {
return {
- links: [
- httpBatchLink({
- url: `${getBaseUrl()}/api/trpc`,
- headers() {
- return {
- /**
- * Attach headers from app to client requests, so tRPC can add them to context
- */
- [SALEOR_AUTHORIZATION_BEARER_HEADER]: appBridgeInstance?.getState().token,
- [SALEOR_API_URL_HEADER]: appBridgeInstance?.getState().saleorApiUrl,
- };
- },
- }),
- ],
+ links: [createHttpBatchLink(appBridgeInstance)],
// queryClientConfig: { defaultOptions: { queries: { staleTime: 60 } } },
};
},
diff --git a/apps/invoices/src/modules/trpc/trpc-context.ts b/apps/invoices/src/modules/trpc/trpc-context.ts
deleted file mode 100644
index 6e4bcbf..0000000
--- a/apps/invoices/src/modules/trpc/trpc-context.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import * as trpcNext from "@trpc/server/adapters/next";
-import { SALEOR_AUTHORIZATION_BEARER_HEADER, SALEOR_API_URL_HEADER } from "@saleor/app-sdk/const";
-import { inferAsyncReturnType } from "@trpc/server";
-
-export const createTrpcContext = async ({ res, req }: trpcNext.CreateNextContextOptions) => {
- return {
- token: req.headers[SALEOR_AUTHORIZATION_BEARER_HEADER] as string | undefined,
- saleorApiUrl: req.headers[SALEOR_API_URL_HEADER] as string | undefined,
- appId: undefined as undefined | string,
- };
-};
-
-export type TrpcContext = inferAsyncReturnType;
diff --git a/apps/invoices/src/modules/trpc/trpc-server.ts b/apps/invoices/src/modules/trpc/trpc-server.ts
index 9a930bd..7989c90 100644
--- a/apps/invoices/src/modules/trpc/trpc-server.ts
+++ b/apps/invoices/src/modules/trpc/trpc-server.ts
@@ -1,6 +1,7 @@
import { initTRPC } from "@trpc/server";
-import { TrpcContext } from "./trpc-context";
+
import { Permission } from "@saleor/app-sdk/types";
+import { TrpcContext } from "@saleor/trpc";
interface Meta {
requiredClientPermissions?: Permission[];
diff --git a/apps/invoices/src/modules/ui/AppSection.tsx b/apps/invoices/src/modules/ui/AppSection.tsx
deleted file mode 100644
index c9a61e9..0000000
--- a/apps/invoices/src/modules/ui/AppSection.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import { Box, PropsWithBox, Text } from "@saleor/macaw-ui/next";
-import { ReactNode } from "react";
-
-// todo move to shared
-export const AppSection = ({
- heading,
- sideContent,
- mainContent,
- includePadding = false,
- ...props
-}: PropsWithBox<{
- heading: string;
- sideContent?: ReactNode;
- mainContent: ReactNode;
- includePadding?: boolean;
-}>) => {
- return (
-
-
-
- {heading}
-
- {sideContent}
-
-
- {mainContent}
-
-
- );
-};
diff --git a/apps/invoices/src/pages/_app.tsx b/apps/invoices/src/pages/_app.tsx
index 70a64bc..0cd0711 100644
--- a/apps/invoices/src/pages/_app.tsx
+++ b/apps/invoices/src/pages/_app.tsx
@@ -6,12 +6,11 @@ import { RoutePropagator } from "@saleor/app-sdk/app-bridge/next";
import React, { ReactElement } from "react";
import { AppProps } from "next/app";
-import { NoSSRWrapper } from "@saleor/apps-shared";
+import { NoSSRWrapper, ThemeSynchronizer } from "@saleor/apps-shared";
import { trpcClient } from "../modules/trpc/trpc-client";
import { Box, ThemeProvider } from "@saleor/macaw-ui/next";
import { NextPage } from "next";
-import { ThemeSynchronizer } from "../lib/theme-synchronizer";
/**
* Ensure instance is a singleton.
diff --git a/apps/invoices/src/pages/api/trpc/[trpc].ts b/apps/invoices/src/pages/api/trpc/[trpc].ts
index 80bfedf..cc490e9 100644
--- a/apps/invoices/src/pages/api/trpc/[trpc].ts
+++ b/apps/invoices/src/pages/api/trpc/[trpc].ts
@@ -1,7 +1,7 @@
import * as trpcNext from "@trpc/server/adapters/next";
-import { createTrpcContext } from "../../../modules/trpc/trpc-context";
import { appRouter } from "../../../modules/trpc/trpc-app-router";
import { createLogger } from "@saleor/apps-shared";
+import { createTrpcContext } from "@saleor/trpc";
const logger = createLogger({ name: "tRPC error" });
diff --git a/apps/invoices/src/public/favicon.ico b/apps/invoices/src/public/favicon.ico
deleted file mode 100644
index 718d6fe..0000000
Binary files a/apps/invoices/src/public/favicon.ico and /dev/null differ
diff --git a/apps/invoices/src/public/vercel.svg b/apps/invoices/src/public/vercel.svg
deleted file mode 100644
index fbf0e25..0000000
--- a/apps/invoices/src/public/vercel.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
\ No newline at end of file
diff --git a/apps/invoices/src/styles/globals.css b/apps/invoices/src/styles/globals.css
index 2366a15..fe307dc 100644
--- a/apps/invoices/src/styles/globals.css
+++ b/apps/invoices/src/styles/globals.css
@@ -1,8 +1,3 @@
-body {
- font-family: Inter, -apple-system, "system-ui", "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell,
- "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
-}
-
a {
cursor: pointer;
text-decoration: none;
diff --git a/apps/klaviyo/package.json b/apps/klaviyo/package.json
index 8864eba..d659e4e 100644
--- a/apps/klaviyo/package.json
+++ b/apps/klaviyo/package.json
@@ -16,6 +16,7 @@
"@material-ui/lab": "4.0.0-alpha.61",
"@saleor/app-sdk": "0.41.1",
"@saleor/apps-shared": "workspace:*",
+ "@saleor/apps-ui": "workspace:*",
"@saleor/macaw-ui": "0.8.0-pre.127",
"@sentry/nextjs": "7.67.0",
"@urql/exchange-auth": "^2.1.4",
diff --git a/apps/klaviyo/src/hooks/theme-synchronizer.tsx b/apps/klaviyo/src/hooks/theme-synchronizer.tsx
deleted file mode 100644
index 3386f49..0000000
--- a/apps/klaviyo/src/hooks/theme-synchronizer.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import { useAppBridge } from "@saleor/app-sdk/app-bridge";
-import { useTheme } from "@saleor/macaw-ui/next";
-import { useEffect } from "react";
-
-// todo move to shared
-export function ThemeSynchronizer() {
- const { appBridgeState } = useAppBridge();
- const { setTheme } = useTheme();
-
- useEffect(() => {
- if (!setTheme || !appBridgeState?.theme) {
- return;
- }
-
- if (appBridgeState.theme === "light") {
- setTheme("defaultLight");
- }
-
- if (appBridgeState.theme === "dark") {
- setTheme("defaultDark");
- }
- }, [appBridgeState?.theme, setTheme]);
-
- return null;
-}
diff --git a/apps/klaviyo/src/lib/ui/app-columns-layout.tsx b/apps/klaviyo/src/lib/ui/app-columns-layout.tsx
deleted file mode 100644
index 9e1ae6d..0000000
--- a/apps/klaviyo/src/lib/ui/app-columns-layout.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import { Box } from "@saleor/macaw-ui/next";
-import { PropsWithChildren } from "react";
-
-export function AppColumnsLayout({ children }: PropsWithChildren<{}>) {
- return (
-
- {children}
-
- );
-}
diff --git a/apps/klaviyo/src/pages/_app.tsx b/apps/klaviyo/src/pages/_app.tsx
index a50a676..e0427bb 100644
--- a/apps/klaviyo/src/pages/_app.tsx
+++ b/apps/klaviyo/src/pages/_app.tsx
@@ -1,11 +1,8 @@
-import "@saleor/macaw-ui/next/style";
import { AppBridge, AppBridgeProvider } from "@saleor/app-sdk/app-bridge";
-import React from "react";
-import { AppProps } from "next/app";
-import { RoutePropagator } from "@saleor/app-sdk/app-bridge/next";
+import { NoSSRWrapper, ThemeSynchronizer } from "@saleor/apps-shared";
import { Box, ThemeProvider } from "@saleor/macaw-ui/next";
-import { NoSSRWrapper } from "@saleor/apps-shared";
-import { ThemeSynchronizer } from "../hooks/theme-synchronizer";
+import "@saleor/macaw-ui/next/style";
+import { AppProps } from "next/app";
/**
* Ensure instance is a singleton.
@@ -18,7 +15,9 @@ function SaleorApp({ Component, pageProps }: AppProps) {
-
+
+
+
diff --git a/apps/klaviyo/src/pages/_document.tsx b/apps/klaviyo/src/pages/_document.tsx
index 82f6b4d..33153d7 100644
--- a/apps/klaviyo/src/pages/_document.tsx
+++ b/apps/klaviyo/src/pages/_document.tsx
@@ -3,13 +3,8 @@ import { Head, Html, Main, NextScript } from "next/document";
export default function Document() {
return (
-
-
-
+
diff --git a/apps/klaviyo/src/pages/api/configuration.ts b/apps/klaviyo/src/pages/api/configuration.ts
index 3971999..b2b8297 100644
--- a/apps/klaviyo/src/pages/api/configuration.ts
+++ b/apps/klaviyo/src/pages/api/configuration.ts
@@ -61,12 +61,20 @@ const handler: NextProtectedApiHandler = async (request, res, ctx) => {
data: await getAppSettings(settings),
});
case "POST": {
- await settings.set((request.body as PostRequestBody).data);
+ try {
+ await settings.set((JSON.parse(request.body) as PostRequestBody).data);
- return res.json({
- success: true,
- data: await getAppSettings(settings),
- });
+ return res.json({
+ success: true,
+ data: await getAppSettings(settings),
+ });
+ } catch (e) {
+ console.error(e);
+
+ return res.json({
+ success: false,
+ });
+ }
}
default:
return res.status(405).end();
diff --git a/apps/klaviyo/src/pages/api/manifest.ts b/apps/klaviyo/src/pages/api/manifest.ts
index 58e28b8..0a7ee12 100644
--- a/apps/klaviyo/src/pages/api/manifest.ts
+++ b/apps/klaviyo/src/pages/api/manifest.ts
@@ -8,17 +8,17 @@ import { orderCreatedWebhook } from "./webhooks/order-created";
import { orderFullyPaidWebhook } from "./webhooks/order-fully-paid";
const handler = createManifestHandler({
- async manifestFactory(context): Promise {
- const { appBaseUrl } = context;
+ async manifestFactory({ appBaseUrl }): Promise {
+ const iframeBaseUrl = process.env.APP_IFRAME_BASE_URL ?? appBaseUrl;
+ const apiBaseURL = process.env.APP_API_BASE_URL ?? appBaseUrl;
return {
- about:
- "Klaviyo integration allows sending Klaviyo notifications on Saleor events.",
- appUrl: appBaseUrl,
+ about: "Klaviyo integration allows sending Klaviyo notifications on Saleor events.",
+ appUrl: iframeBaseUrl,
author: "Saleor Commerce",
brand: {
logo: {
- default: `${context.appBaseUrl}/logo.png`,
+ default: `${apiBaseURL}/logo.png`,
},
},
dataPrivacyUrl: "https://saleor.io/legal/privacy/",
@@ -27,7 +27,7 @@ const handler = createManifestHandler({
name: "Klaviyo",
permissions: ["MANAGE_USERS", "MANAGE_ORDERS"],
supportUrl: "https://github.com/saleor/apps/discussions",
- tokenTargetUrl: `${appBaseUrl}/api/register`,
+ tokenTargetUrl: `${apiBaseURL}/api/register`,
version: pkg.version,
webhooks: [
customerCreatedWebhook.getWebhookManifest(appBaseUrl),
diff --git a/apps/klaviyo/src/pages/configuration.tsx b/apps/klaviyo/src/pages/configuration.tsx
index 702151d..96e3e19 100644
--- a/apps/klaviyo/src/pages/configuration.tsx
+++ b/apps/klaviyo/src/pages/configuration.tsx
@@ -1,47 +1,31 @@
-import { useAppBridge, withAuthorization } from "@saleor/app-sdk/app-bridge";
+import { useAppBridge, useAuthenticatedFetch } from "@saleor/app-sdk/app-bridge";
import { SALEOR_API_URL_HEADER, SALEOR_AUTHORIZATION_BEARER_HEADER } from "@saleor/app-sdk/const";
import { ChangeEvent, SyntheticEvent, useEffect, useState } from "react";
-import { useAppApi } from "../hooks/useAppApi";
-import { AppColumnsLayout } from "../lib/ui/app-columns-layout";
import { useDashboardNotification } from "@saleor/apps-shared";
-import { Box, BoxProps, Text, Input, Button } from "@saleor/macaw-ui/next";
+import { Breadcrumbs, ButtonsBox, Layout, SkeletonLayout, TextLink } from "@saleor/apps-ui";
+import { Box, Button, Input, Text } from "@saleor/macaw-ui/next";
+import { useAppApi } from "../hooks/useAppApi";
interface ConfigurationField {
key: string;
value: string;
}
-function Section(props: BoxProps) {
- return ;
-}
-
function Instructions() {
- const { appBridge } = useAppBridge();
-
- const openExternalUrl = (url: string) => {
- // eslint-disable-next-line
- appBridge?.dispatch({
- type: "redirect",
- payload: {
- newContext: true,
- actionId: "redirect_from_klaviyo_app",
- to: url,
- },
- });
- };
-
return (
-
-
+
+
How to set up
- App will send events as Klaviyo metrics each time Saleor Event occurs.
-
+
+ App will send events as Klaviyo metrics each time Saleor Event occurs.
+
+
When first metric is sent, it should be available in Klaviyo to build on top of.
-
+
Metric name can be customized, PUBLIC_TOKEN must be provided to enable the app.
@@ -49,16 +33,9 @@ function Instructions() {
@@ -66,47 +43,28 @@ function Instructions() {
-
+
);
}
@@ -114,6 +72,7 @@ function Configuration() {
const { appBridgeState } = useAppBridge();
const { notifySuccess, notifyError } = useDashboardNotification();
const [configuration, setConfiguration] = useState();
+ const authenticatedFetch = useAuthenticatedFetch() as typeof window.fetch;
const { data: configurationData, error } = useAppApi({
url: "/api/configuration",
@@ -131,13 +90,8 @@ function Configuration() {
const handleSubmit = (event: SyntheticEvent) => {
event.preventDefault();
- fetch("/api/configuration", {
+ authenticatedFetch("/api/configuration", {
method: "POST",
- headers: [
- ["content-type", "application/json"],
- [SALEOR_API_URL_HEADER, appBridgeState?.saleorApiUrl!],
- [SALEOR_AUTHORIZATION_BEARER_HEADER, appBridgeState?.token!],
- ],
body: JSON.stringify({ data: configuration }),
})
.then(async (response) => {
@@ -149,7 +103,7 @@ function Configuration() {
})
.catch(async () => {
await notifyError(
- "Configuration update failed. Ensure fields are filled correctly and you have MANAGE_APPS permission"
+ "Configuration update failed. Ensure fields are filled correctly and you have MANAGE_APPS permission",
);
});
};
@@ -158,7 +112,7 @@ function Configuration() {
const { name, value } = event.target as HTMLInputElement;
setConfiguration((prev) =>
- prev!.map((prevField) => (prevField.key === name ? { ...prevField, value } : prevField))
+ prev!.map((prevField) => (prevField.key === name ? { ...prevField, value } : prevField)),
);
};
@@ -192,31 +146,36 @@ function Configuration() {
}
if (configuration === undefined) {
- return Loading...
;
+ return ;
}
return (
-
-
-
-
-
+
+
+ Configuration
+
+ }>
+
+
+
+ }
+ >
+
+ {configuration!.map(({ key, value }) => (
+
+
+
+ ))}
+
+
+
+
);
}
diff --git a/apps/klaviyo/turbo.json b/apps/klaviyo/turbo.json
index abd10d3..e0aa903 100644
--- a/apps/klaviyo/turbo.json
+++ b/apps/klaviyo/turbo.json
@@ -18,7 +18,9 @@
"SENTRY_PROJECT",
"SENTRY_AUTH_TOKEN",
"NEXT_PUBLIC_VERCEL_ENV",
- "SENTRY_ENVIRONMENT"
+ "SENTRY_ENVIRONMENT",
+ "APP_API_BASE_URL",
+ "APP_IFRAME_BASE_URL"
]
}
}
diff --git a/cspell.json b/cspell.json
index 166b708..0cee309 100644
--- a/cspell.json
+++ b/cspell.json
@@ -67,7 +67,8 @@
"vals",
"urql",
"Protos",
- "pino"
+ "pino",
+ "IFRAME"
],
"ignorePaths": [
"node_modules",
diff --git a/package.json b/package.json
index fe57896..b316811 100644
--- a/package.json
+++ b/package.json
@@ -18,6 +18,7 @@
},
"devDependencies": {
"@changesets/cli": "^2.26.2",
+ "@sentry/cli": "2.20.6",
"@types/node": "18.15.3",
"cspell": "^7.2.0",
"eslint": "8.46.0",
@@ -27,8 +28,7 @@
"next": "13.4.8",
"prettier": "3.0.3",
"syncpack": "10.9.3",
- "turbo": "1.10.12",
- "@sentry/cli": "2.20.6"
+ "turbo": "1.10.12"
},
"engines": {
"node": ">=18.0.0"
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index bc1dd0f..2f04812 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -642,9 +642,15 @@ importers:
'@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/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)
@@ -787,6 +793,9 @@ importers:
'@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)