feat: add Weeztix OAuth, coupon codes, and Mailwizz mapping

Implement Weeztix integration per documentation: database config and
subscriber coupon_code, OAuth redirect/callback, admin setup UI with
company/coupon selection via AJAX, synchronous coupon creation on public
subscribe with duplicate and rate-limit handling, Mailwizz field mapping
for coupon codes, subscriber table and CSV export, and connection hint
on the pages list.

Made-with: Cursor
This commit is contained in:
2026-04-04 14:52:41 +02:00
parent 17e784fee7
commit d3abdb7ed9
30 changed files with 2272 additions and 5 deletions

View File

@@ -1,5 +1,7 @@
@php
$config = $page->mailwizzConfig;
$page->loadMissing('weeztixConfig');
$hasWeeztixForCouponMap = $page->weeztixConfig !== null && $page->weeztixConfig->is_connected;
$existing = $config !== null
? [
'list_uid' => $config->list_uid,
@@ -8,6 +10,7 @@
'field_first_name' => $config->field_first_name,
'field_last_name' => $config->field_last_name,
'field_phone' => $config->field_phone,
'field_coupon_code' => $config->field_coupon_code,
'tag_field' => $config->tag_field,
'tag_value' => $config->tag_value,
]
@@ -27,6 +30,7 @@
'csrf' => csrf_token(),
'phoneEnabled' => $page->isPhoneFieldEnabledForSubscribers(),
'hasExistingConfig' => $config !== null,
'hasWeeztixIntegration' => $hasWeeztixForCouponMap,
'existing' => $existing,
'strings' => [
'apiKeyRequired' => __('Enter your Mailwizz API key to continue.'),
@@ -190,6 +194,16 @@
</template>
</select>
</div>
<div x-show="hasWeeztixIntegration">
<label class="block text-sm font-medium text-slate-700">{{ __('Kortingscode (Weeztix)') }} <span class="font-normal text-slate-500">({{ __('optional') }})</span></label>
<p class="mt-1 text-xs text-slate-500">{{ __('Koppel aan een tekstveld in Mailwizz om de persoonlijke code in e-mails te tonen.') }}</p>
<select x-model="fieldCouponCode" class="mt-1 block w-full rounded-lg border border-slate-300 px-3 py-2 text-sm text-slate-900 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500">
<option value="">{{ __('Select field…') }}</option>
<template x-for="f in textFields()" :key="'cp-' + f.tag">
<option :value="f.tag" x-text="f.label + ' (' + f.tag + ')'"></option>
</template>
</select>
</div>
<div>
<label class="block text-sm font-medium text-slate-700">{{ __('Tag / source (checkbox list)') }}</label>
<select
@@ -244,6 +258,7 @@
<input type="hidden" name="field_first_name" x-bind:value="fieldFirstName">
<input type="hidden" name="field_last_name" x-bind:value="fieldLastName">
<input type="hidden" name="field_phone" x-bind:value="phoneEnabled ? fieldPhone : ''">
<input type="hidden" name="field_coupon_code" x-bind:value="hasWeeztixIntegration ? fieldCouponCode : ''">
<input type="hidden" name="tag_field" x-bind:value="tagField">
<input type="hidden" name="tag_value" x-bind:value="tagValue">