@designcrowd/library.brand-page 5.16.42 → 5.16.43-ast-ai

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/lib/src/{CreatedOnBrandCrowd-de-de-2yOEYkJj.js → CreatedOnBrandCrowd-de-de-B-Oc1A1o.js} +1 -1
  2. package/lib/src/{CreatedOnBrandCrowd-en-us-C3ZcstVb.js → CreatedOnBrandCrowd-en-us-D5C-8hdn.js} +1 -1
  3. package/lib/src/{CreatedOnBrandCrowd-es-es-CUVbeolm.js → CreatedOnBrandCrowd-es-es-58ZSy8Zn.js} +1 -1
  4. package/lib/src/{CreatedOnBrandCrowd-fr-fr-CWrEm5XF.js → CreatedOnBrandCrowd-fr-ca-Csqkn-I_.js} +1 -1
  5. package/lib/src/{CreatedOnBrandCrowd-fr-ca-CWrEm5XF.js → CreatedOnBrandCrowd-fr-fr-Csqkn-I_.js} +1 -1
  6. package/lib/src/{CreatedOnBrandCrowd-pt-pt-5WAjD7l3.js → CreatedOnBrandCrowd-pt-br-ILKVUj-T.js} +1 -1
  7. package/lib/src/{CreatedOnBrandCrowd-pt-br-5WAjD7l3.js → CreatedOnBrandCrowd-pt-pt-ILKVUj-T.js} +1 -1
  8. package/lib/src/{CreatedOnDcom-de-de-B4b3caW2.js → CreatedOnDcom-de-de-B5x4Crpb.js} +1 -1
  9. package/lib/src/{CreatedOnDcom-en-us-DaLELZyT.js → CreatedOnDcom-en-us-DtufP1lJ.js} +1 -1
  10. package/lib/src/{CreatedOnDcom-es-es-p5JZJPXb.js → CreatedOnDcom-es-es-DJ0sw5dx.js} +1 -1
  11. package/lib/src/{CreatedOnDcom-fr-ca-DcaNqreO.js → CreatedOnDcom-fr-ca-DrMbvOz7.js} +1 -1
  12. package/lib/src/{CreatedOnDcom-fr-fr-DKzy6dez.js → CreatedOnDcom-fr-fr-B2EKyBz8.js} +1 -1
  13. package/lib/src/{CreatedOnDcom-pt-br-DXK8qDlO.js → CreatedOnDcom-pt-br-SdOffbzb.js} +1 -1
  14. package/lib/src/{CreatedOnDcom-pt-pt-ChGL1q2P.js → CreatedOnDcom-pt-pt-3khFhgHk.js} +1 -1
  15. package/lib/src/brand-page/__fixtures__/brand-page-sample-data.js +2 -0
  16. package/lib/src/brand-page/clients/brand-page.client.d.ts +2 -2
  17. package/lib/src/brand-page/clients/brand-page.client.js +4 -2
  18. package/lib/src/brand-page/components/contact-form/ContactForm.mixin.d.ts +10 -1
  19. package/lib/src/brand-page/components/contact-form/ContactForm.mixin.js +18 -2
  20. package/lib/src/brand-page/components/image-gallery/ImageGallery.stories.js +2 -1
  21. package/lib/src/brand-page/components/images-section/ImagesSection.stories.js +2 -1
  22. package/lib/src/brand-page/components/mailing-list/MailingList.mixin.d.ts +8 -0
  23. package/lib/src/brand-page/components/mailing-list/MailingList.mixin.js +22 -7
  24. package/lib/src/brand-page/components/page/Page.mixin.d.ts +3 -1
  25. package/lib/src/brand-page/components/page/Page.mixin.js +13 -4
  26. package/lib/src/brand-page/components/payment/__fixtures__/request-payment.d.ts +1 -0
  27. package/lib/src/brand-page/components/payment/__fixtures__/support-me.d.ts +1 -0
  28. package/lib/src/brand-page/components/profile/Profile.stories.js +2 -1
  29. package/lib/src/brand-page/composables/recaptcha.composable.d.ts +23 -0
  30. package/lib/src/brand-page/composables/recaptcha.composable.js +109 -0
  31. package/lib/src/brand-page/constants/misc.constants.d.ts +3 -0
  32. package/lib/src/brand-page/constants/misc.constants.js +4 -0
  33. package/lib/src/brand-page/models/brand-page-context.model.d.ts +2 -0
  34. package/lib/src/brand-page/utils/index.d.ts +1 -0
  35. package/lib/src/brand-page/utils/index.js +1 -0
  36. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.d.ts +3 -0
  37. package/lib/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.mixin.d.ts +7 -1
  38. package/lib/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.mixin.js +15 -0
  39. package/lib/src/brand-page-maker/components/preview/Preview.mixin.js +1 -0
  40. package/lib/src/brand-page-maker/components/template-preview/TemplatePreview.mixin.js +1 -0
  41. package/lib/src/bundled-translations-CaJjdBEL.js +169 -0
  42. package/lib/src/bundled-translations.de-DE-1O8nqesB.js +169 -0
  43. package/lib/src/bundled-translations.es-ES-DpzOLKxV.js +169 -0
  44. package/lib/src/bundled-translations.fr-CA-D4lvna7W.js +169 -0
  45. package/lib/src/bundled-translations.fr-FR-rn7Qlrcy.js +169 -0
  46. package/lib/src/bundled-translations.pt-BR-Cuinfm5y.js +169 -0
  47. package/lib/src/bundled-translations.pt-PT-BWcxkaQn.js +169 -0
  48. package/lib/src/bundles/bundled-translations.de-DE.json +1 -1
  49. package/lib/src/bundles/bundled-translations.es-ES.json +1 -1
  50. package/lib/src/bundles/bundled-translations.fr-CA.json +1 -1
  51. package/lib/src/bundles/bundled-translations.fr-FR.json +2 -2
  52. package/lib/src/bundles/bundled-translations.json +1 -1
  53. package/lib/src/bundles/bundled-translations.pt-BR.json +2 -2
  54. package/lib/src/bundles/bundled-translations.pt-PT.json +2 -2
  55. package/lib/src/css/library.brand-page-brandCrowd.min.css +51 -51
  56. package/lib/src/css/library.brand-page-brandPage.min.css +51 -51
  57. package/lib/src/css/library.brand-page-designCom.min.css +51 -51
  58. package/lib/src/css/library.brand-page-preview.min.css +51 -51
  59. package/lib/src/index.d.ts +3 -1
  60. package/lib/src/index.mjs +116 -111
  61. package/lib/src/{lib-hXQaBPKF.js → lib-Cr89CnhI.js} +17648 -17488
  62. package/lib/src/lib.js +2 -1
  63. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItem.mixin.d.ts +3 -0
  64. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItem.mixin.js +1 -0
  65. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.d.ts +3 -0
  66. package/lib/src/shared/content-settings/components/focused-content-item-settings/FocusedContentItemSettings.mixin.js +1 -0
  67. package/lib/src/shared/content-settings/components/radio-field/RadioField.mixin.js +1 -0
  68. package/lib/src/shared/side-bar/components/SideBar.mixin.d.ts +3 -0
  69. package/lib/src/shared/side-bar/components/SideBarTabButton.mixin.d.ts +3 -0
  70. package/lib/src/shared/side-bar/store/side-bar-store.d.ts +4 -0
  71. package/lib/src/shared/side-bar/store/side-bar-store.js +4 -1
  72. package/package.json +5 -4
  73. package/src/brand-page/__fixtures__/brand-page-sample-data.ts +2 -0
  74. package/src/brand-page/clients/brand-page.client.ts +5 -0
  75. package/src/brand-page/components/about-section/AboutSection.vue.test.ts +1 -0
  76. package/src/brand-page/components/contact-form/ContactForm.mixin.ts +23 -0
  77. package/src/brand-page/components/contact-form/ContactForm.vue +5 -0
  78. package/src/brand-page/components/image/Image.test.ts +1 -0
  79. package/src/brand-page/components/image-gallery/ImageGallery.stories.ts +2 -1
  80. package/src/brand-page/components/image-gallery/ImageGallery.test.ts +1 -0
  81. package/src/brand-page/components/images-section/ImagesSection.stories.ts +2 -1
  82. package/src/brand-page/components/mailing-list/MailingList.mixin.ts +32 -12
  83. package/src/brand-page/components/page/Page.mixin.ts +15 -4
  84. package/src/brand-page/components/page/Page.vue +1 -0
  85. package/src/brand-page/components/profile/Profile.stories.ts +2 -1
  86. package/src/brand-page/components/save-contact/SaveContactWidget.vue +9 -4
  87. package/src/brand-page/components/save-contact/i18n/save-contact.de-DE.json +1 -1
  88. package/src/brand-page/components/save-contact/i18n/save-contact.es-ES.json +1 -1
  89. package/src/brand-page/components/save-contact/i18n/save-contact.fr-CA.json +1 -1
  90. package/src/brand-page/components/save-contact/i18n/save-contact.fr-FR.json +2 -2
  91. package/src/brand-page/components/save-contact/i18n/save-contact.pt-BR.json +2 -2
  92. package/src/brand-page/components/save-contact/i18n/save-contact.pt-PT.json +2 -2
  93. package/src/brand-page/components/social-links/SocialLinks.vue.test.ts +1 -0
  94. package/src/brand-page/components/text-wrapper/TextWrapper.vue +1 -1
  95. package/src/brand-page/composables/recaptcha.composable.ts +159 -0
  96. package/src/brand-page/constants/misc.constants.ts +5 -0
  97. package/src/brand-page/models/brand-page-context.model.ts +2 -0
  98. package/src/brand-page/utils/index.ts +1 -0
  99. package/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.mixin.ts +19 -0
  100. package/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.vue +1 -0
  101. package/src/brand-page-maker/components/brandpage-settings/BrandPageSettings.vue +50 -1
  102. package/src/brand-page-maker/components/preview/Preview.mixin.ts +1 -0
  103. package/src/brand-page-maker/components/template-preview/TemplatePreview.mixin.ts +1 -0
  104. package/src/bundles/bundled-translations.de-DE.json +1 -1
  105. package/src/bundles/bundled-translations.es-ES.json +1 -1
  106. package/src/bundles/bundled-translations.fr-CA.json +1 -1
  107. package/src/bundles/bundled-translations.fr-FR.json +2 -2
  108. package/src/bundles/bundled-translations.json +1 -1
  109. package/src/bundles/bundled-translations.pt-BR.json +2 -2
  110. package/src/bundles/bundled-translations.pt-PT.json +2 -2
  111. package/src/lib.ts +6 -0
  112. package/src/shared/common/i18n/common.json +1 -1
  113. package/src/shared/content-settings/components/content-settings-item/ContentSettingsItem.mixin.ts +1 -0
  114. package/src/shared/content-settings/components/focused-content-item-settings/FocusedContentItemSettings.mixin.ts +1 -0
  115. package/src/shared/content-settings/components/generate-ai-button/GenerateAiButton.vue +1 -1
  116. package/src/shared/content-settings/components/radio-field/RadioField.mixin.ts +1 -0
  117. package/src/shared/side-bar/store/side-bar-store.ts +6 -1
  118. package/lib/src/bundled-translations-m0LLDrVj.js +0 -169
  119. package/lib/src/bundled-translations.de-DE-CTyBT5RM.js +0 -169
  120. package/lib/src/bundled-translations.es-ES-62eLb3OR.js +0 -169
  121. package/lib/src/bundled-translations.fr-CA-38Mskv1H.js +0 -169
  122. package/lib/src/bundled-translations.fr-FR-Ct8Ky13V.js +0 -169
  123. package/lib/src/bundled-translations.pt-BR-smqmYHq1.js +0 -169
  124. package/lib/src/bundled-translations.pt-PT-DjM0Ggh1.js +0 -169
@@ -1,5 +1,5 @@
1
1
  import { createElementBlock as e, openBlock as l, createStaticVNode as a } from "vue";
2
- import { _ as t } from "./lib-hXQaBPKF.js";
2
+ import { _ as t } from "./lib-Cr89CnhI.js";
3
3
  const i = {}, h = {
4
4
  width: "275",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { createElementBlock as e, openBlock as l, createStaticVNode as a } from "vue";
2
- import { _ as t } from "./lib-hXQaBPKF.js";
2
+ import { _ as t } from "./lib-Cr89CnhI.js";
3
3
  const i = {}, h = {
4
4
  width: "251",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { createElementBlock as e, openBlock as l, createStaticVNode as a } from "vue";
2
- import { _ as t } from "./lib-hXQaBPKF.js";
2
+ import { _ as t } from "./lib-Cr89CnhI.js";
3
3
  const i = {}, h = {
4
4
  width: "250",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { createElementBlock as e, openBlock as l, createStaticVNode as a } from "vue";
2
- import { _ as t } from "./lib-hXQaBPKF.js";
2
+ import { _ as t } from "./lib-Cr89CnhI.js";
3
3
  const i = {}, h = {
4
4
  width: "237",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { createElementBlock as e, openBlock as l, createStaticVNode as a } from "vue";
2
- import { _ as t } from "./lib-hXQaBPKF.js";
2
+ import { _ as t } from "./lib-Cr89CnhI.js";
3
3
  const i = {}, h = {
4
4
  width: "237",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { createElementBlock as e, openBlock as l, createStaticVNode as a } from "vue";
2
- import { _ as t } from "./lib-hXQaBPKF.js";
2
+ import { _ as t } from "./lib-Cr89CnhI.js";
3
3
  const i = {}, h = {
4
4
  width: "263",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { createElementBlock as e, openBlock as l, createStaticVNode as a } from "vue";
2
- import { _ as t } from "./lib-hXQaBPKF.js";
2
+ import { _ as t } from "./lib-Cr89CnhI.js";
3
3
  const i = {}, h = {
4
4
  width: "263",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { createElementBlock as H, openBlock as V, createStaticVNode as t } from "vue";
2
- import { _ as e } from "./lib-hXQaBPKF.js";
2
+ import { _ as e } from "./lib-Cr89CnhI.js";
3
3
  const M = {}, Z = {
4
4
  width: "242",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { createElementBlock as H, openBlock as V, createStaticVNode as t } from "vue";
2
- import { _ as e } from "./lib-hXQaBPKF.js";
2
+ import { _ as e } from "./lib-Cr89CnhI.js";
3
3
  const i = {}, M = {
4
4
  width: "231",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { createElementBlock as H, openBlock as V, createStaticVNode as t } from "vue";
2
- import { _ as e } from "./lib-hXQaBPKF.js";
2
+ import { _ as e } from "./lib-Cr89CnhI.js";
3
3
  const M = {}, Z = {
4
4
  width: "230",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { createElementBlock as H, openBlock as V, createStaticVNode as t } from "vue";
2
- import { _ as e } from "./lib-hXQaBPKF.js";
2
+ import { _ as e } from "./lib-Cr89CnhI.js";
3
3
  const i = {}, l = {
4
4
  width: "216",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { createElementBlock as H, openBlock as V, createStaticVNode as t } from "vue";
2
- import { _ as e } from "./lib-hXQaBPKF.js";
2
+ import { _ as e } from "./lib-Cr89CnhI.js";
3
3
  const i = {}, l = {
4
4
  width: "216",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { createElementBlock as H, openBlock as V, createStaticVNode as t } from "vue";
2
- import { _ as M } from "./lib-hXQaBPKF.js";
2
+ import { _ as M } from "./lib-Cr89CnhI.js";
3
3
  const Z = {}, e = {
4
4
  width: "232",
5
5
  height: "36",
@@ -1,5 +1,5 @@
1
1
  import { createElementBlock as H, openBlock as V, createStaticVNode as t } from "vue";
2
- import { _ as M } from "./lib-hXQaBPKF.js";
2
+ import { _ as M } from "./lib-Cr89CnhI.js";
3
3
  const Z = {}, e = {
4
4
  width: "232",
5
5
  height: "36",
@@ -97,6 +97,7 @@ export const context = {
97
97
  mediaSizes: MEDIA_SIZES.sizeThumb,
98
98
  },
99
99
  isCapturingPreview: false,
100
+ recaptchaSiteKey: undefined,
100
101
  };
101
102
  export const BScontext = {
102
103
  isTemplate: false,
@@ -119,6 +120,7 @@ export const BScontext = {
119
120
  mediaSizes: MEDIA_SIZES.sizeThumb,
120
121
  },
121
122
  isCapturingPreview: false,
123
+ recaptchaSiteKey: undefined,
122
124
  };
123
125
  export const logoTheme = {
124
126
  logoTextColor: {
@@ -16,8 +16,8 @@ export interface CartForShopRequest {
16
16
  unitPrice: number;
17
17
  }
18
18
  declare const _default: {
19
- addMailSubscriber: (brandPageToken: string, email: string, name?: string | undefined) => Promise<FormResult>;
20
- submitContactForm: (brandPageToken: string, contentBlockId: string, values: ContactFormField[]) => Promise<FormResult>;
19
+ addMailSubscriber: (brandPageToken: string, email: string, name?: string | undefined, recaptchaToken?: string | undefined) => Promise<FormResult>;
20
+ submitContactForm: (brandPageToken: string, contentBlockId: string, values: ContactFormField[], recaptchaToken?: string | undefined) => Promise<FormResult>;
21
21
  getPaymentConfigStatusByPaymentConfigId: (paymentConfigId: string) => Promise<string>;
22
22
  getStoreProductsByBrandPageToken: (brandPageToken: string) => Promise<string>;
23
23
  createBrandPageCart: (request: {
@@ -1,12 +1,13 @@
1
1
  import axios from 'axios';
2
2
  import * as constants from '../constants';
3
3
  import * as csrfUtil from '../utils/csrf.util';
4
- const addMailSubscriber = async (brandPageToken, email, name) => {
4
+ const addMailSubscriber = async (brandPageToken, email, name, recaptchaToken) => {
5
5
  try {
6
6
  const csrfToken = csrfUtil.getCsrfTokenFromMetaHeader();
7
7
  await axios.post(`/api/mailing-lists/${brandPageToken}`, {
8
8
  email,
9
9
  name,
10
+ recaptchaToken,
10
11
  }, {
11
12
  headers: {
12
13
  'Content-Type': 'application/json',
@@ -28,12 +29,13 @@ const addMailSubscriber = async (brandPageToken, email, name) => {
28
29
  };
29
30
  }
30
31
  };
31
- const submitContactForm = async (brandPageToken, contentBlockId, values) => {
32
+ const submitContactForm = async (brandPageToken, contentBlockId, values, recaptchaToken) => {
32
33
  try {
33
34
  const csrfToken = csrfUtil.getCsrfTokenFromMetaHeader();
34
35
  const formBody = {
35
36
  contentBlockId,
36
37
  fields: values,
38
+ recaptchaToken,
37
39
  };
38
40
  await axios.post(`/api/contact/${brandPageToken}`, formBody, {
39
41
  headers: {
@@ -28,7 +28,16 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
28
28
  required: false;
29
29
  default: undefined;
30
30
  };
31
- }>, {}, {
31
+ }>, {
32
+ recaptcha: {
33
+ isReady: import("vue").ComputedRef<boolean>;
34
+ initialise: (config: import("../../composables/recaptcha.composable").ReCaptchaConfig) => Promise<void>;
35
+ executeAsync: (action: string) => Promise<string | undefined>;
36
+ error: import("vue").ComputedRef<string | undefined>;
37
+ isLoading: import("vue").ComputedRef<boolean>;
38
+ siteKey: import("vue").ComputedRef<string>;
39
+ };
40
+ }, {
32
41
  values: FieldValue[];
33
42
  hasSubmitted: boolean;
34
43
  isSubmitting: boolean;
@@ -3,8 +3,9 @@ import { FORM_FIELD_TYPES } from '../../models';
3
3
  import { getEmailValidationError, getMessageValidationError, getNameValidationError, getPhoneValidationError, } from '../../utils';
4
4
  import brandPageClient from '../../clients/brand-page.client';
5
5
  import { isEmail } from '../../../utils/validator.util';
6
- import { ALIGN_CARD_CENTER, ALIGN_CARD_CENTER_IMAGE, ALIGN_CENTERED, ALIGN_LEFT_FULL_IMAGE, ALIGN_RIGHT_FULL_IMAGE, DISPLAY_STYLE_COLLAPSED, TEXT_ONLY, } from '../../constants';
6
+ import { ALIGN_CARD_CENTER, ALIGN_CARD_CENTER_IMAGE, ALIGN_CENTERED, ALIGN_LEFT_FULL_IMAGE, ALIGN_RIGHT_FULL_IMAGE, DISPLAY_STYLE_COLLAPSED, RECAPTCHA_ACTION_CONTACT_FORM, TEXT_ONLY, } from '../../constants';
7
7
  import { getRenderText } from '../../utils';
8
+ import { useReCaptcha } from '../../composables/recaptcha.composable';
8
9
  export default defineComponent({
9
10
  props: {
10
11
  context: {
@@ -29,6 +30,12 @@ export default defineComponent({
29
30
  default: undefined,
30
31
  },
31
32
  },
33
+ setup() {
34
+ const recaptcha = useReCaptcha();
35
+ return {
36
+ recaptcha,
37
+ };
38
+ },
32
39
  data: () => ({
33
40
  values: [],
34
41
  hasSubmitted: false,
@@ -136,6 +143,11 @@ export default defineComponent({
136
143
  field: f,
137
144
  }));
138
145
  },
146
+ mounted() {
147
+ if (this.context?.recaptchaSiteKey && !this.isPreview && this.recaptcha) {
148
+ this.recaptcha.initialise({ siteKey: this.context.recaptchaSiteKey });
149
+ }
150
+ },
139
151
  methods: {
140
152
  async submitContactForm() {
141
153
  this.validateFormValues();
@@ -152,12 +164,16 @@ export default defineComponent({
152
164
  let result;
153
165
  this.isSubmitting = true;
154
166
  try {
167
+ let recaptchaToken;
168
+ if (this.context?.recaptchaSiteKey && this.recaptcha?.isReady?.value) {
169
+ recaptchaToken = await this.recaptcha.executeAsync(RECAPTCHA_ACTION_CONTACT_FORM);
170
+ }
155
171
  result = await brandPageClient.submitContactForm(this.context.brandPageToken, this.viewModel.id, this.values.map((v) => ({
156
172
  id: v.field.id,
157
173
  name: v.field.name,
158
174
  type: v.field.type,
159
175
  value: v.value,
160
- })));
176
+ })), recaptchaToken);
161
177
  }
162
178
  finally {
163
179
  this.isSubmitting = false;
@@ -29,7 +29,8 @@ const context = {
29
29
  options: {
30
30
  mediaSizes: MEDIA_SIZES.sizeThumb,
31
31
  },
32
- isCapturingPreview: false
32
+ isCapturingPreview: false,
33
+ recaptchaSiteKey: undefined,
33
34
  };
34
35
  export const Carousel = () => {
35
36
  return defineComponent({
@@ -29,7 +29,8 @@ const context = {
29
29
  options: {
30
30
  mediaSizes: MEDIA_SIZES.sizeThumb,
31
31
  },
32
- isCapturingPreview: false
32
+ isCapturingPreview: false,
33
+ recaptchaSiteKey: undefined,
33
34
  };
34
35
  export const Images = () => {
35
36
  return defineComponent({
@@ -21,6 +21,14 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
21
21
  };
22
22
  }>, {
23
23
  mailingListTr: (key: any, valuesToInterpolate?: {}) => any;
24
+ recaptcha: {
25
+ isReady: import("vue").ComputedRef<boolean>;
26
+ initialise: (config: import("../../composables/recaptcha.composable").ReCaptchaConfig) => Promise<void>;
27
+ executeAsync: (action: string) => Promise<string | undefined>;
28
+ error: import("vue").ComputedRef<string | undefined>;
29
+ isLoading: import("vue").ComputedRef<boolean>;
30
+ siteKey: import("vue").ComputedRef<string>;
31
+ };
24
32
  }, {
25
33
  email: string;
26
34
  emailError: string | undefined;
@@ -2,6 +2,8 @@ import { defineComponent } from 'vue';
2
2
  import brandPageClient from '../../clients/brand-page.client';
3
3
  import { getEmailValidationError } from '../../utils';
4
4
  import { contentSettingsTr, mailingListTr, } from '../../../composables/useTranslate';
5
+ import { useReCaptcha } from '../../composables/recaptcha.composable';
6
+ import { RECAPTCHA_ACTION_MAILING_LIST } from '../../constants';
5
7
  const MAX_NAME_LENGTH = 200;
6
8
  export default defineComponent({
7
9
  props: {
@@ -24,8 +26,10 @@ export default defineComponent({
24
26
  },
25
27
  },
26
28
  setup() {
29
+ const recaptcha = useReCaptcha();
27
30
  return {
28
31
  mailingListTr,
32
+ recaptcha,
29
33
  };
30
34
  },
31
35
  data() {
@@ -42,6 +46,9 @@ export default defineComponent({
42
46
  },
43
47
  mounted() {
44
48
  this.show = true;
49
+ if (this.context?.recaptchaSiteKey && !this.isPreview && this.recaptcha) {
50
+ this.recaptcha.initialise({ siteKey: this.context.recaptchaSiteKey });
51
+ }
45
52
  },
46
53
  computed: {
47
54
  hasNameError() {
@@ -107,14 +114,22 @@ export default defineComponent({
107
114
  return;
108
115
  }
109
116
  this.isSubmitting = true;
110
- const result = await brandPageClient.addMailSubscriber(this.brandPageToken, this.email, this.name === '' ? undefined : this.name);
111
- this.isSubmitting = false;
112
- if (!result.success) {
113
- this.submitError = result.errMessage;
114
- return;
117
+ try {
118
+ let recaptchaToken;
119
+ if (this.context?.recaptchaSiteKey && this.recaptcha?.isReady?.value) {
120
+ recaptchaToken = await this.recaptcha.executeAsync(RECAPTCHA_ACTION_MAILING_LIST);
121
+ }
122
+ const result = await brandPageClient.addMailSubscriber(this.brandPageToken, this.email, this.name === '' ? undefined : this.name, recaptchaToken);
123
+ if (!result.success) {
124
+ this.submitError = result.errMessage;
125
+ return;
126
+ }
127
+ this.submitError = undefined;
128
+ this.hasSubscribed = result.success;
129
+ }
130
+ finally {
131
+ this.isSubmitting = false;
115
132
  }
116
- this.submitError = undefined;
117
- this.hasSubscribed = result.success;
118
133
  },
119
134
  validateName() {
120
135
  if (this.name.trim() === '') {
@@ -88,7 +88,8 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
88
88
  scrollToLastContent(): void;
89
89
  onPageHeaderContainerClick(): void;
90
90
  onPageFooterContainerClick(): void;
91
- }, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, ("on-logo-click" | "on-set-preview-styles" | "on-scroll-to-element" | "page-header-container-click" | "page-footer-container-click")[], "on-logo-click" | "on-set-preview-styles" | "on-scroll-to-element" | "page-header-container-click" | "page-footer-container-click", import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
91
+ onSaveContactWidgetClick(): void;
92
+ }, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, ("on-logo-click" | "on-set-preview-styles" | "on-scroll-to-element" | "page-header-container-click" | "page-footer-container-click" | "save-contact-widget-click")[], "on-logo-click" | "on-set-preview-styles" | "on-scroll-to-element" | "page-header-container-click" | "page-footer-container-click" | "save-contact-widget-click", import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
92
93
  context: {
93
94
  type: PropType<BrandPageContext>;
94
95
  required: true;
@@ -138,6 +139,7 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
138
139
  "onOn-scroll-to-element"?: ((...args: any[]) => any) | undefined;
139
140
  "onPage-header-container-click"?: ((...args: any[]) => any) | undefined;
140
141
  "onPage-footer-container-click"?: ((...args: any[]) => any) | undefined;
142
+ "onSave-contact-widget-click"?: ((...args: any[]) => any) | undefined;
141
143
  }>, {
142
144
  viewport: ViewportsType;
143
145
  isMobileDevice: boolean;
@@ -55,6 +55,7 @@ export default defineComponent({
55
55
  'on-scroll-to-element',
56
56
  'page-header-container-click',
57
57
  'page-footer-container-click',
58
+ 'save-contact-widget-click',
58
59
  ],
59
60
  setup(props) {
60
61
  return {
@@ -266,10 +267,12 @@ export default defineComponent({
266
267
  },
267
268
  shouldShowSaveContactWidget() {
268
269
  try {
269
- if (this.context.isPreview ||
270
- this.context.options?.enableSaveContact === false ||
271
- !this.context.isBrandContact ||
272
- this.viewModel.saveContactSettings.isSavingEnabled === false) {
270
+ if ((!this.context.isPreview &&
271
+ this.context.options?.enableSaveContact === false) ||
272
+ !this.context.isBrandContact) {
273
+ return false;
274
+ }
275
+ if (this.viewModel.saveContactSettings?.isSavingEnabled === false) {
273
276
  return false;
274
277
  }
275
278
  const brandPageData = { pages: this.context?.pages || [] };
@@ -355,5 +358,11 @@ export default defineComponent({
355
358
  }
356
359
  this.$emit('page-footer-container-click');
357
360
  },
361
+ onSaveContactWidgetClick() {
362
+ if (!this.context.isPreview) {
363
+ return;
364
+ }
365
+ this.$emit('save-contact-widget-click');
366
+ },
358
367
  },
359
368
  });
@@ -12,6 +12,7 @@ export declare const context: {
12
12
  pages?: import("../../../view-models").PageViewModel[] | undefined;
13
13
  currentPageSlug?: string | undefined;
14
14
  runtimeEnvironment: string;
15
+ recaptchaSiteKey: string | undefined;
15
16
  pageTitle: string;
16
17
  customDomain: string | undefined;
17
18
  brandPageType: "default" | "brandSite" | "brandContact";
@@ -12,6 +12,7 @@ export declare const context: {
12
12
  pages?: import("../../../view-models").PageViewModel[] | undefined;
13
13
  currentPageSlug?: string | undefined;
14
14
  runtimeEnvironment: string;
15
+ recaptchaSiteKey: string | undefined;
15
16
  pageTitle: string;
16
17
  customDomain: string | undefined;
17
18
  brandPageType: "default" | "brandSite" | "brandContact";
@@ -51,7 +51,8 @@ export const Sample = () => {
51
51
  options: {
52
52
  mediaSizes: MEDIA_SIZES.sizeThumb,
53
53
  },
54
- isCapturingPreview: false
54
+ isCapturingPreview: false,
55
+ recaptchaSiteKey: undefined,
55
56
  };
56
57
  return { viewModel, context };
57
58
  },
@@ -0,0 +1,23 @@
1
+ declare global {
2
+ interface Window {
3
+ grecaptcha: {
4
+ enterprise: {
5
+ ready: (callback: () => void) => void;
6
+ execute: (siteKey: string, options: {
7
+ action: string;
8
+ }) => Promise<string>;
9
+ };
10
+ };
11
+ }
12
+ }
13
+ export interface ReCaptchaConfig {
14
+ siteKey: string;
15
+ }
16
+ export declare const useReCaptcha: () => {
17
+ isReady: import("vue").ComputedRef<boolean>;
18
+ initialise: (config: ReCaptchaConfig) => Promise<void>;
19
+ executeAsync: (action: string) => Promise<string | undefined>;
20
+ error: import("vue").ComputedRef<string | undefined>;
21
+ isLoading: import("vue").ComputedRef<boolean>;
22
+ siteKey: import("vue").ComputedRef<string>;
23
+ };
@@ -0,0 +1,109 @@
1
+ import { reactive, computed } from 'vue';
2
+ const store = reactive({
3
+ isLoaded: false,
4
+ isLoading: false,
5
+ siteKey: '',
6
+ error: undefined,
7
+ });
8
+ const SCRIPT_ID = 'recaptcha-enterprise-script';
9
+ const isBrowser = () => {
10
+ return typeof window !== 'undefined' && typeof document !== 'undefined';
11
+ };
12
+ const loadScript = (siteKey) => {
13
+ return new Promise((resolve, reject) => {
14
+ if (!isBrowser()) {
15
+ reject(new Error('reCAPTCHA can only be loaded in browser environment'));
16
+ return;
17
+ }
18
+ // Check if script already exists
19
+ const existingScript = document.getElementById(SCRIPT_ID);
20
+ if (existingScript) {
21
+ resolve();
22
+ return;
23
+ }
24
+ const script = document.createElement('script');
25
+ script.id = SCRIPT_ID;
26
+ script.src = `https://www.google.com/recaptcha/enterprise.js?render=${siteKey}`;
27
+ script.async = true;
28
+ script.defer = true;
29
+ script.onload = () => {
30
+ resolve();
31
+ };
32
+ script.onerror = () => {
33
+ reject(new Error('Failed to load reCAPTCHA script'));
34
+ };
35
+ document.head.appendChild(script);
36
+ });
37
+ };
38
+ export const useReCaptcha = () => {
39
+ const isReady = computed(() => store.isLoaded && !store.isLoading);
40
+ /**
41
+ * Initialize reCAPTCHA with the site key.
42
+ * Should be called once when the component mounts.
43
+ */
44
+ const initialise = async (config) => {
45
+ if (!isBrowser()) {
46
+ return;
47
+ }
48
+ if (store.isLoaded || store.isLoading) {
49
+ return;
50
+ }
51
+ if (!config.siteKey) {
52
+ store.error = 'reCAPTCHA site key is required';
53
+ return;
54
+ }
55
+ store.siteKey = config.siteKey;
56
+ store.isLoading = true;
57
+ store.error = undefined;
58
+ try {
59
+ await loadScript(config.siteKey);
60
+ store.isLoaded = true;
61
+ }
62
+ catch (err) {
63
+ store.error =
64
+ err instanceof Error ? err.message : 'Failed to load reCAPTCHA';
65
+ }
66
+ finally {
67
+ store.isLoading = false;
68
+ }
69
+ };
70
+ /**
71
+ * Execute reCAPTCHA and get a token for the specified action.
72
+ * @param action - The action name for reCAPTCHA (e.g., 'CONTACT_FORM_SUBMIT')
73
+ * @returns Promise<string | undefined> - The reCAPTCHA token or undefined if failed
74
+ */
75
+ const executeAsync = async (action) => {
76
+ if (!isBrowser()) {
77
+ return undefined;
78
+ }
79
+ if (!store.isLoaded || !store.siteKey) {
80
+ return undefined;
81
+ }
82
+ try {
83
+ return await new Promise((resolve, reject) => {
84
+ window.grecaptcha.enterprise.ready(async () => {
85
+ try {
86
+ const token = await window.grecaptcha.enterprise.execute(store.siteKey, { action });
87
+ resolve(token);
88
+ }
89
+ catch (err) {
90
+ reject(err);
91
+ }
92
+ });
93
+ });
94
+ }
95
+ catch (err) {
96
+ store.error =
97
+ err instanceof Error ? err.message : 'Failed to execute reCAPTCHA';
98
+ return undefined;
99
+ }
100
+ };
101
+ return {
102
+ isReady,
103
+ initialise,
104
+ executeAsync,
105
+ error: computed(() => store.error),
106
+ isLoading: computed(() => store.isLoading),
107
+ siteKey: computed(() => store.siteKey),
108
+ };
109
+ };
@@ -51,6 +51,9 @@ export declare const STATUS_CODE_CONFLICT = 409;
51
51
  export declare const MESSAGE_ADD_MAIL_SUBSCRIBER_CONFLICT: () => any;
52
52
  export declare const MESSAGE_ADD_MAIL_SUBSCRIBER_FAILED: () => any;
53
53
  export declare const MESSAGE_CONTACT_MESSAGE_FAILED: () => any;
54
+ export declare const RECAPTCHA_ACTION_MAILING_LIST = "MAILING_LIST_SUBSCRIBE";
55
+ export declare const RECAPTCHA_ACTION_CONTACT_FORM = "CONTACT_FORM_SUBMIT";
56
+ export declare const RECAPTCHA_MIN_SCORE = 0.5;
54
57
  export declare const stripeDefaultStyle: {
55
58
  readonly base: {
56
59
  readonly color: "#7d8284";
@@ -61,6 +61,10 @@ export const STATUS_CODE_CONFLICT = 409;
61
61
  export const MESSAGE_ADD_MAIL_SUBSCRIBER_CONFLICT = () => errorTr('alreadySubscribedError');
62
62
  export const MESSAGE_ADD_MAIL_SUBSCRIBER_FAILED = () => errorTr('mailingListError');
63
63
  export const MESSAGE_CONTACT_MESSAGE_FAILED = () => errorTr('contactMessageError');
64
+ // reCAPTCHA constants
65
+ export const RECAPTCHA_ACTION_MAILING_LIST = 'MAILING_LIST_SUBSCRIBE';
66
+ export const RECAPTCHA_ACTION_CONTACT_FORM = 'CONTACT_FORM_SUBMIT';
67
+ export const RECAPTCHA_MIN_SCORE = 0.5;
64
68
  export const stripeDefaultStyle = {
65
69
  base: {
66
70
  color: '#7d8284',
@@ -59,6 +59,8 @@ export interface BrandPageContext {
59
59
  currentPageSlug?: string;
60
60
  runtimeEnvironment: string;
61
61
  stripePublishableKey: string | undefined;
62
+ /** reCAPTCHA Enterprise site key for spam protection */
63
+ recaptchaSiteKey: string | undefined;
62
64
  pageTitle: string;
63
65
  customDomain: string | undefined;
64
66
  brandPageType: BrandPageType;
@@ -8,6 +8,7 @@ export * from './image.util';
8
8
  export * from './font-loader.util';
9
9
  export * from './aspect-ratio.util';
10
10
  export * from './context.util';
11
+ export * from './color.util';
11
12
  export * from './personalisation.util';
12
13
  export * from './migration.util';
13
14
  export * from '../../utils/validator.util';
@@ -8,6 +8,7 @@ export * from './image.util';
8
8
  export * from './font-loader.util';
9
9
  export * from './aspect-ratio.util';
10
10
  export * from './context.util';
11
+ export * from './color.util';
11
12
  export * from './personalisation.util';
12
13
  export * from './migration.util';
13
14
  export * from '../../utils/validator.util';
@@ -196,6 +196,9 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
196
196
  isVisible: boolean;
197
197
  }): void;
198
198
  setSelectedTabName(payload: import("../../../lib").SetSelectedTabNamePayload): void;
199
+ setSelectedSettingsSection(payload: {
200
+ sectionName?: string | undefined;
201
+ }): void;
199
202
  }>;
200
203
  userStore: import("pinia").Store<"brandPageMakerUser", import("../../stores").UserState, {}, {
201
204
  setUserState(payload: {