diff --git a/.changeset/popular-clouds-play.md b/.changeset/popular-clouds-play.md
new file mode 100644
index 0000000..c36ea74
--- /dev/null
+++ b/.changeset/popular-clouds-play.md
@@ -0,0 +1,7 @@
+---
+"saleor-app-emails-and-messages": patch
+---
+
+Manage gift card permission is now required to enable gift cards event.
+
+Added message to interface for users with missing permission or Saleor version without the support for this event.
diff --git a/apps/emails-and-messages/src/components/manage-permissions-text-link.tsx b/apps/emails-and-messages/src/components/manage-permissions-text-link.tsx
new file mode 100644
index 0000000..53dcefc
--- /dev/null
+++ b/apps/emails-and-messages/src/components/manage-permissions-text-link.tsx
@@ -0,0 +1,58 @@
+import { useAppBridge, actions } from "@saleor/app-sdk/app-bridge";
+import { SaleorVersionCompatibilityValidator } from "@saleor/apps-shared";
+import { PermissionEnum } from "../../generated/graphql";
+import { Text } from "@saleor/macaw-ui/next";
+import { TextLink } from "@saleor/apps-ui";
+
+interface ManagePermissionsTextLinkProps {
+ missingPermission: PermissionEnum;
+}
+
+/*
+ * Returns TextLink component with link to manage permissions if used in compatible dashboard version.
+ * Otherwise returns text instructions to reinstall the app.
+ */
+export const ManagePermissionsTextLink = ({
+ missingPermission,
+}: ManagePermissionsTextLinkProps) => {
+ const { appBridgeState, appBridge } = useAppBridge();
+ const dashboardVersion = appBridgeState?.dashboardVersion;
+
+ // Editing app permissions has been introduced in Saleor Dashboard 3.15
+ const isPermissionManagementAvailable = dashboardVersion
+ ? new SaleorVersionCompatibilityValidator(">=3.15").isValid(dashboardVersion)
+ : false;
+
+ const appId = appBridgeState?.id;
+
+ if (!isPermissionManagementAvailable || !appId) {
+ return (
+
+ To use this feature, the {missingPermission} permission is required. Please reinstall the
+ app.
+
+ );
+ }
+
+ return (
+
+ To use this feature, the {missingPermission} permission is required. Please go to{" "}
+ {/* TODO: Update the shared package to handle dashboard links */}
+ {
+ e.preventDefault();
+
+ appBridge?.dispatch(
+ actions.Redirect({
+ to: `/apps/${appId}`,
+ })
+ );
+ }}
+ href="#"
+ >
+ Manage App section
+
+ , and grant the permission
+
+ );
+};
diff --git a/apps/emails-and-messages/src/lib/fetch-app-permissions.ts b/apps/emails-and-messages/src/lib/fetch-app-permissions.ts
new file mode 100644
index 0000000..35d6c49
--- /dev/null
+++ b/apps/emails-and-messages/src/lib/fetch-app-permissions.ts
@@ -0,0 +1,34 @@
+import { Client, gql } from "urql";
+import {
+ FetchAppPermissionsDocument,
+ FetchAppPermissionsQuery,
+ PermissionEnum,
+} from "../../generated/graphql";
+import { createLogger } from "@saleor/apps-shared";
+
+gql`
+ query FetchAppPermissions {
+ app {
+ permissions {
+ code
+ }
+ }
+ }
+`;
+
+const logger = createLogger({
+ name: "fetchAppPermissions",
+});
+
+export async function fetchAppPermissions(client: Client): Promise {
+ const { error, data } = await client
+ .query(FetchAppPermissionsDocument, {})
+ .toPromise();
+
+ if (error) {
+ logger.error(error, "Error fetching app permissions");
+ throw new Error("Could not fetch the app permissions");
+ }
+
+ return data?.app?.permissions?.map((p) => p.code) || [];
+}
diff --git a/apps/emails-and-messages/src/lib/get-event-form-status.test.ts b/apps/emails-and-messages/src/lib/get-event-form-status.test.ts
new file mode 100644
index 0000000..7b0c7f9
--- /dev/null
+++ b/apps/emails-and-messages/src/lib/get-event-form-status.test.ts
@@ -0,0 +1,65 @@
+import { vi, expect, describe, it } from "vitest";
+import { getEventFormStatus } from "./get-event-form-status";
+import { PermissionEnum } from "../../generated/graphql";
+
+describe("getEventFormStatus", function () {
+ it("No message or disable flag, when event other than GIFT_CARD_SENT is passed", () => {
+ expect(
+ getEventFormStatus({
+ eventType: "ORDER_CREATED",
+ appPermissions: [PermissionEnum.ManageGiftCard],
+ featureFlags: {
+ giftCardSentEvent: true,
+ },
+ })
+ ).toEqual({
+ tooltipMessage: undefined,
+ isDisabled: false,
+ });
+ expect(
+ getEventFormStatus({
+ eventType: "ORDER_CREATED",
+ appPermissions: [],
+ featureFlags: {
+ giftCardSentEvent: false,
+ },
+ })
+ ).toEqual({
+ isDisabled: false,
+ missingPermission: undefined,
+ requiredSaleorVersion: undefined,
+ });
+ });
+
+ it("Return disable flag and lack of the permission message, when GIFT_CARD_SENT is passed and app has no manage gift card permission", () => {
+ expect(
+ getEventFormStatus({
+ eventType: "GIFT_CARD_SENT",
+ appPermissions: [],
+ featureFlags: {
+ giftCardSentEvent: true,
+ },
+ })
+ ).toEqual({
+ isDisabled: true,
+ missingPermission: PermissionEnum.ManageGiftCard,
+ requiredSaleorVersion: undefined,
+ });
+ });
+
+ it("Return disable flag and unsupported Saleor version message, when GIFT_CARD_SENT is passed with missing feature flag", () => {
+ expect(
+ getEventFormStatus({
+ eventType: "GIFT_CARD_SENT",
+ appPermissions: [PermissionEnum.ManageGiftCard],
+ featureFlags: {
+ giftCardSentEvent: false,
+ },
+ })
+ ).toEqual({
+ isDisabled: true,
+ missingPermission: undefined,
+ requiredSaleorVersion: ">=3.13",
+ });
+ });
+});
diff --git a/apps/emails-and-messages/src/lib/get-event-form-status.ts b/apps/emails-and-messages/src/lib/get-event-form-status.ts
new file mode 100644
index 0000000..693906f
--- /dev/null
+++ b/apps/emails-and-messages/src/lib/get-event-form-status.ts
@@ -0,0 +1,40 @@
+import { PermissionEnum } from "../../generated/graphql";
+import { MessageEventTypes } from "../modules/event-handlers/message-event-types";
+import { FeatureFlagsState } from "../modules/feature-flag-service/get-feature-flags";
+
+interface getEventFormStatusArgs {
+ eventType: MessageEventTypes;
+ featureFlags?: FeatureFlagsState;
+ appPermissions?: PermissionEnum[];
+}
+
+export const getEventFormStatus = ({
+ eventType,
+ featureFlags,
+ appPermissions,
+}: getEventFormStatusArgs): {
+ missingPermission: PermissionEnum | undefined;
+ isDisabled: boolean;
+ requiredSaleorVersion: string | undefined;
+} => {
+ // Since GIFT_CARD_SENT is the only event with such validation, we can exit early
+ if (eventType !== "GIFT_CARD_SENT") {
+ return {
+ isDisabled: false,
+ missingPermission: undefined,
+ requiredSaleorVersion: undefined,
+ };
+ }
+
+ const isUnsupported = !featureFlags?.giftCardSentEvent;
+
+ const hasGiftCardPermission = (appPermissions || []).includes(PermissionEnum.ManageGiftCard);
+
+ const isDisabled = isUnsupported || !hasGiftCardPermission;
+
+ return {
+ isDisabled,
+ missingPermission: hasGiftCardPermission ? undefined : PermissionEnum.ManageGiftCard,
+ requiredSaleorVersion: isUnsupported ? ">=3.13" : undefined,
+ };
+};
diff --git a/apps/emails-and-messages/src/modules/app-configuration/app-configuration.router.ts b/apps/emails-and-messages/src/modules/app-configuration/app-configuration.router.ts
index d3b9eee..bacfa80 100644
--- a/apps/emails-and-messages/src/modules/app-configuration/app-configuration.router.ts
+++ b/apps/emails-and-messages/src/modules/app-configuration/app-configuration.router.ts
@@ -1,6 +1,7 @@
import { createLogger } from "@saleor/apps-shared";
import { router } from "../trpc/trpc-server";
import { protectedWithConfigurationServices } from "../trpc/protected-client-procedure-with-services";
+import { fetchAppPermissions } from "../../lib/fetch-app-permissions";
export const appConfigurationRouter = router({
featureFlags: protectedWithConfigurationServices.query(async ({ ctx }) => {
@@ -9,4 +10,12 @@ export const appConfigurationRouter = router({
logger.debug("appConfigurationRouter.featureFlags called");
return await ctx.featureFlagService.getFeatureFlags();
}),
+ appPermissions: protectedWithConfigurationServices.query(async ({ ctx }) => {
+ const logger = createLogger({ saleorApiUrl: ctx.saleorApiUrl });
+
+ logger.debug("appConfigurationRouter.permissions called");
+ const appPermissions = await fetchAppPermissions(ctx.apiClient);
+
+ return appPermissions;
+ }),
});
diff --git a/apps/emails-and-messages/src/modules/sendgrid/ui/sendgrid-events-section.tsx b/apps/emails-and-messages/src/modules/sendgrid/ui/sendgrid-events-section.tsx
index 7d8a741..46f973f 100644
--- a/apps/emails-and-messages/src/modules/sendgrid/ui/sendgrid-events-section.tsx
+++ b/apps/emails-and-messages/src/modules/sendgrid/ui/sendgrid-events-section.tsx
@@ -19,6 +19,8 @@ import { Select } from "@saleor/react-hook-form-macaw";
import { TextLink } from "@saleor/apps-ui";
import { messageEventTypesLabels } from "../../event-handlers/message-event-types";
import { Table } from "../../../components/table";
+import { getEventFormStatus } from "../../../lib/get-event-form-status";
+import { ManagePermissionsTextLink } from "../../../components/manage-permissions-text-link";
interface SendgridEventsSectionProps {
configuration: SendgridConfiguration;
@@ -28,6 +30,7 @@ export const SendgridEventsSection = ({ configuration }: SendgridEventsSectionPr
const { notifySuccess, notifyError } = useDashboardNotification();
const { data: featureFlags } = trpcClient.app.featureFlags.useQuery();
+ const { data: appPermissions } = trpcClient.app.appPermissions.useQuery();
// Sort events by displayed label
const eventsSorted = configuration.events.sort((a, b) =>
@@ -98,8 +101,12 @@ export const SendgridEventsSection = ({ configuration }: SendgridEventsSectionPr
{eventsSorted.map((event, index) => {
- const isUnsupported =
- !featureFlags?.giftCardSentEvent && event.eventType === "GIFT_CARD_SENT";
+ const { isDisabled, requiredSaleorVersion, missingPermission } =
+ getEventFormStatus({
+ appPermissions,
+ featureFlags: featureFlags,
+ eventType: event.eventType,
+ });
return (
@@ -109,14 +116,22 @@ export const SendgridEventsSection = ({ configuration }: SendgridEventsSectionPr
- {isUnsupported && (
+ {requiredSaleorVersion ? (
- Event is available in Saleor version 3.13 and above only.
+ The feature requires Saleor version {requiredSaleorVersion}. Update
+ the instance to enable.
+ ) : (
+ missingPermission && (
+
+
+
+
+ )
)}
@@ -128,7 +143,7 @@ export const SendgridEventsSection = ({ configuration }: SendgridEventsSectionPr
control={control}
name={`events.${index}.template`}
options={templateChoices}
- disabled={isUnsupported}
+ disabled={isDisabled}
/>
diff --git a/apps/emails-and-messages/src/modules/smtp/ui/smtp-events-section.tsx b/apps/emails-and-messages/src/modules/smtp/ui/smtp-events-section.tsx
index c755340..ca4bd95 100644
--- a/apps/emails-and-messages/src/modules/smtp/ui/smtp-events-section.tsx
+++ b/apps/emails-and-messages/src/modules/smtp/ui/smtp-events-section.tsx
@@ -19,6 +19,8 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { trpcClient } from "../../trpc/trpc-client";
import { useForm } from "react-hook-form";
import { setBackendErrors } from "../../../lib/set-backend-errors";
+import { getEventFormStatus } from "../../../lib/get-event-form-status";
+import { ManagePermissionsTextLink } from "../../../components/manage-permissions-text-link";
interface SmtpEventsSectionProps {
configuration: SmtpConfiguration;
@@ -29,6 +31,7 @@ export const SmtpEventsSection = ({ configuration }: SmtpEventsSectionProps) =>
const router = useRouter();
const { data: featureFlags } = trpcClient.app.featureFlags.useQuery();
+ const { data: appPermissions } = trpcClient.app.appPermissions.useQuery();
// Sort events by displayed label
const eventsSorted = configuration.events.sort((a, b) =>
@@ -87,8 +90,12 @@ export const SmtpEventsSection = ({ configuration }: SmtpEventsSectionProps) =>
{eventsSorted.map((event, index) => {
- const isUnsupported =
- !featureFlags?.giftCardSentEvent && event.eventType === "GIFT_CARD_SENT";
+ const { isDisabled, requiredSaleorVersion, missingPermission } =
+ getEventFormStatus({
+ appPermissions,
+ featureFlags: featureFlags,
+ eventType: event.eventType,
+ });
return (
@@ -98,14 +105,22 @@ export const SmtpEventsSection = ({ configuration }: SmtpEventsSectionProps) =>
- {isUnsupported && (
+ {requiredSaleorVersion ? (
- Event is available in Saleor version 3.13 and above only.
+ The feature requires Saleor version {requiredSaleorVersion}. Update
+ the instance to enable.
+ ) : (
+ missingPermission && (
+
+
+
+
+ )
)}
@@ -121,7 +136,7 @@ export const SmtpEventsSection = ({ configuration }: SmtpEventsSectionProps) =>
smtpUrls.eventConfiguration(configuration.id, event.eventType)
);
}}
- disabled={isUnsupported}
+ disabled={isDisabled}
>
Edit template
diff --git a/apps/emails-and-messages/src/pages/api/manifest.ts b/apps/emails-and-messages/src/pages/api/manifest.ts
index a758492..655fb1a 100644
--- a/apps/emails-and-messages/src/pages/api/manifest.ts
+++ b/apps/emails-and-messages/src/pages/api/manifest.ts
@@ -28,7 +28,7 @@ export default createManifestHandler({
homepageUrl: "https://github.com/saleor/apps",
id: "saleor.app.emails-and-messages",
name: "Emails & Messages",
- permissions: ["MANAGE_ORDERS", "MANAGE_USERS"],
+ permissions: ["MANAGE_ORDERS", "MANAGE_USERS", "MANAGE_GIFT_CARD"],
/**
* Requires 3.10 due to invoices event payload - in previous versions, order reference was missing
*/