feat(weeztix): auto company from OAuth, remove company UI

Store company_guid after OAuth via profile API; drop company select and
companies endpoint. Coupons AJAX uses stored company only. Form request
no longer accepts company fields from the browser.

Made-with: Cursor
This commit is contained in:
2026-04-05 10:56:29 +02:00
parent 977e09d8ac
commit 6561bda30d
7 changed files with 60 additions and 156 deletions

View File

@@ -13,38 +13,10 @@ use RuntimeException;
class WeeztixApiController extends Controller
{
public function companies(Request $request): JsonResponse
{
$request->validate([
'page_id' => ['required', 'integer', 'exists:preregistration_pages,id'],
]);
$page = PreregistrationPage::query()->findOrFail($request->integer('page_id'));
$this->authorize('update', $page);
$config = $page->weeztixConfig;
if ($config === null || ! $config->is_connected) {
return response()->json([
'message' => __('Niet verbonden met Weeztix.'),
], 422);
}
try {
$companies = (new WeeztixService($config))->getCompanies();
return response()->json(['companies' => $companies]);
} catch (RuntimeException) {
return response()->json([
'message' => __('Kon bedrijven niet laden. Vernieuw de verbinding indien nodig.'),
], 422);
}
}
public function coupons(Request $request): JsonResponse
{
$request->validate([
'page_id' => ['required', 'integer', 'exists:preregistration_pages,id'],
'company_guid' => ['required', 'string', 'max:255'],
]);
$page = PreregistrationPage::query()->findOrFail($request->integer('page_id'));
@@ -57,9 +29,12 @@ class WeeztixApiController extends Controller
], 422);
}
$companyGuid = $request->string('company_guid')->toString();
$previousGuid = $config->company_guid;
$config->setAttribute('company_guid', $companyGuid);
$companyGuid = $config->company_guid;
if (! is_string($companyGuid) || $companyGuid === '') {
return response()->json([
'message' => __('Geen Weeztix-bedrijf gekoppeld. Verbind opnieuw met Weeztix.'),
], 422);
}
try {
$raw = (new WeeztixService($config))->getCoupons();
@@ -70,8 +45,6 @@ class WeeztixApiController extends Controller
return response()->json([
'message' => __('Kon kortingsbonnen niet laden.'),
], 422);
} finally {
$config->setAttribute('company_guid', $previousGuid);
}
}

View File

@@ -100,6 +100,10 @@ class WeeztixOAuthController extends Controller
try {
$service = new WeeztixService($config);
$service->exchangeAuthorizationCode($request->string('code')->toString());
$config = $config->fresh();
if ($config !== null) {
(new WeeztixService($config))->ensureCompanyStoredFromWeeztix();
}
} catch (RuntimeException $e) {
Log::error('Weeztix OAuth callback failed', [
'page_id' => $page->id,

View File

@@ -44,8 +44,6 @@ class UpdateWeeztixConfigRequest extends FormRequest
'string',
'max:2048',
],
'company_guid' => ['nullable', 'string', 'max:255'],
'company_name' => ['nullable', 'string', 'max:255'],
'coupon_guid' => ['nullable', 'string', 'max:255'],
'coupon_name' => ['nullable', 'string', 'max:255'],
'code_prefix' => ['nullable', 'string', 'max:32'],

View File

@@ -13,6 +13,7 @@ use Illuminate\Support\Facades\Log;
use InvalidArgumentException;
use LogicException;
use RuntimeException;
use Throwable;
final class WeeztixService
{
@@ -84,6 +85,42 @@ final class WeeztixService
return $this->normalizeCompaniesFromProfile($json);
}
/**
* Persist company_guid when still empty after OAuth (single company per page; chosen in Weeztix login).
* Uses the first company returned from the user profile when several are present.
*/
public function ensureCompanyStoredFromWeeztix(): void
{
$this->config->refresh();
if (is_string($this->config->company_guid) && $this->config->company_guid !== '') {
return;
}
try {
$companies = $this->getCompanies();
if ($companies === []) {
Log::warning('Weeztix: geen bedrijf uit profiel voor automatische koppeling.', [
'weeztix_config_id' => $this->config->id,
]);
return;
}
$row = $companies[0];
$this->config->update([
'company_guid' => $row['guid'],
'company_name' => $row['name'],
]);
$this->config->refresh();
} catch (Throwable $e) {
Log::warning('Weeztix: automatisch bedrijf vastleggen mislukt', [
'weeztix_config_id' => $this->config->id,
'message' => $e->getMessage(),
]);
}
}
/**
* Exchange OAuth authorization code for tokens (admin callback).
*/

View File

@@ -769,16 +769,12 @@ document.addEventListener('alpine:init', () => {
Alpine.data('weeztixSetup', (cfg) => ({
pageId: cfg.pageId,
companiesUrl: cfg.companiesUrl,
couponsUrl: cfg.couponsUrl,
csrf: cfg.csrf,
isConnected: cfg.isConnected === true,
callbackUrl: cfg.callbackUrl,
errorMessage: '',
companies: [],
coupons: [],
companyGuid: '',
companyName: '',
couponGuid: '',
couponName: '',
codePrefix: 'PREREG',
@@ -797,19 +793,12 @@ document.addEventListener('alpine:init', () => {
} else {
this.usageCount = 1;
}
this.companyGuid = cfg.existing.company_guid || '';
this.companyName = cfg.existing.company_name || '';
this.couponGuid = cfg.existing.coupon_guid || '';
this.couponName = cfg.existing.coupon_name || '';
}
if (this.isConnected) {
await this.loadCompanies();
if (this.companyGuid) {
await this.loadCouponsForGuid(this.companyGuid);
}
} else if (cfg.existing && (cfg.existing.company_guid || cfg.existing.coupon_guid)) {
// Show saved choices even when not connected (e.g. expired refresh); lists are from DB only.
this.ensureSelectedCompanyInList();
await this.loadCoupons();
} else if (cfg.existing && cfg.existing.coupon_guid) {
this.ensureSelectedCouponInList();
}
},
@@ -829,18 +818,6 @@ document.addEventListener('alpine:init', () => {
return { res, data };
},
syncCompanyNameFromSelection() {
if (!this.companyGuid) {
this.companyName = '';
return;
}
const c = this.companies.find((x) => x.guid === this.companyGuid);
if (c && typeof c.name === 'string' && c.name.trim() !== '') {
this.companyName = c.name.trim();
}
// If API row has no name or list is still loading, keep companyName from server (DB).
},
syncCouponName() {
if (!this.couponGuid) {
this.couponName = '';
@@ -852,18 +829,6 @@ document.addEventListener('alpine:init', () => {
}
},
ensureSelectedCompanyInList() {
const guid = this.companyGuid;
if (!guid || this.companies.some((x) => x.guid === guid)) {
return;
}
const label =
typeof this.companyName === 'string' && this.companyName.trim() !== ''
? this.companyName.trim()
: guid;
this.companies = [{ guid, name: label }, ...this.companies];
},
ensureSelectedCouponInList() {
const guid = this.couponGuid;
if (!guid || this.coupons.some((x) => x.guid === guid)) {
@@ -876,36 +841,9 @@ document.addEventListener('alpine:init', () => {
this.coupons = [{ guid, name: label }, ...this.coupons];
},
async loadCompanies() {
async loadCoupons() {
this.errorMessage = '';
const { res, data } = await this.postJson(this.companiesUrl, { page_id: this.pageId });
if (!res.ok) {
this.errorMessage = data.message || this.strings.genericError;
this.ensureSelectedCompanyInList();
return;
}
this.companies = Array.isArray(data.companies) ? data.companies : [];
this.ensureSelectedCompanyInList();
this.syncCompanyNameFromSelection();
},
async onCompanyChange() {
this.syncCompanyNameFromSelection();
this.couponGuid = '';
this.couponName = '';
this.coupons = [];
if (!this.companyGuid) {
return;
}
await this.loadCouponsForGuid(this.companyGuid);
},
async loadCouponsForGuid(guid) {
this.errorMessage = '';
const { res, data } = await this.postJson(this.couponsUrl, {
page_id: this.pageId,
company_guid: guid,
});
const { res, data } = await this.postJson(this.couponsUrl, { page_id: this.pageId });
if (!res.ok) {
this.errorMessage = data.message || this.strings.loadCouponsError;
this.ensureSelectedCouponInList();
@@ -915,37 +853,6 @@ document.addEventListener('alpine:init', () => {
this.ensureSelectedCouponInList();
this.syncCouponName();
},
/** Prefer human-readable label; skip API "names" that are just the GUID / UUID. */
companyLabel(c) {
if (!c || typeof c.guid !== 'string') {
return '';
}
const g = c.guid;
const isBadLabel = (s) => {
const t = typeof s === 'string' ? s.trim() : '';
return (
t === '' ||
t.toLowerCase() === g.toLowerCase() ||
this.stringLooksLikeUuid(t)
);
};
const fromApi = typeof c.name === 'string' ? c.name : '';
if (!isBadLabel(fromApi)) {
return fromApi.trim();
}
if (this.companyGuid === g && !isBadLabel(this.companyName)) {
return String(this.companyName).trim();
}
return g;
},
stringLooksLikeUuid(s) {
return /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$/.test(
String(s),
);
},
}));
});

View File

@@ -3,8 +3,6 @@
$wz = $page->weeztixConfig;
$existing = $wz !== null
? [
'company_guid' => $wz->company_guid,
'company_name' => $wz->company_name,
'coupon_guid' => $wz->coupon_guid,
'coupon_name' => $wz->coupon_name,
'code_prefix' => $wz->code_prefix,
@@ -24,7 +22,6 @@
class="mx-auto max-w-3xl"
x-data="weeztixSetup(@js([
'pageId' => $page->id,
'companiesUrl' => route('admin.weeztix.companies'),
'couponsUrl' => route('admin.weeztix.coupons'),
'csrf' => csrf_token(),
'isConnected' => $wz?->is_connected ?? false,
@@ -33,7 +30,6 @@
'existing' => $existing,
'strings' => [
'genericError' => __('Er ging iets mis. Probeer het opnieuw.'),
'selectCompany' => __('Selecteer een bedrijf.'),
'loadCouponsError' => __('Kon kortingsbonnen niet laden.'),
],
]))"
@@ -57,6 +53,13 @@
</div>
@endif
@if ($wz !== null && $wz->is_connected && ($wz->company_guid === null || $wz->company_guid === ''))
<div class="mb-8 rounded-xl border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-950">
<p class="font-medium">{{ __('Bedrijf nog niet vastgelegd') }}</p>
<p class="mt-1 text-amber-900">{{ __('Verbind opnieuw met Weeztix zodat het juiste bedrijf automatisch wordt gekoppeld.') }}</p>
</div>
@endif
@if ($wz !== null && $wz->is_connected)
<div class="mb-8 rounded-xl border border-emerald-200 bg-emerald-50 px-4 py-3 text-sm text-emerald-900">
<p class="font-medium">{{ __('Verbonden met Weeztix') }}</p>
@@ -154,30 +157,13 @@
@if ($wz !== null)
<section class="space-y-4 pt-8">
<h2 class="text-lg font-semibold text-slate-900">{{ __('Stap 2: Bedrijf en kortingsbon') }}</h2>
<p class="text-sm text-slate-600">{{ __('Na een geslaagde verbinding kun je een bedrijf en bestaande coupon uit Weeztix kiezen.') }}</p>
<h2 class="text-lg font-semibold text-slate-900">{{ __('Stap 2: Kortingsbon') }}</h2>
<p class="text-sm text-slate-600">{{ __('Na een geslaagde verbinding kies je een bestaande coupon uit Weeztix. Het bedrijf volgt uit je Weeztix-login en wordt automatisch opgeslagen.') }}</p>
<form method="post" action="{{ route('admin.pages.weeztix.update', $page) }}" class="space-y-4">
@csrf
@method('PUT')
<div>
<label for="weeztix_company" class="block text-sm font-medium text-slate-700">{{ __('Bedrijf') }}</label>
<select
id="weeztix_company"
name="company_guid"
x-model="companyGuid"
@change="onCompanyChange()"
class="mt-1 block w-full rounded-lg border border-slate-300 px-3 py-2 text-slate-900 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500"
>
<option value="">{{ __('Selecteer een bedrijf…') }}</option>
<template x-for="c in companies" :key="c.guid">
<option :value="c.guid" x-text="companyLabel(c)"></option>
</template>
</select>
<input type="hidden" name="company_name" :value="companyName">
</div>
<div>
<label for="weeztix_coupon" class="block text-sm font-medium text-slate-700">{{ __('Coupon (kortingssjabloon)') }}</label>
<select

View File

@@ -56,7 +56,6 @@ Route::middleware(['auth', 'verified'])->prefix('admin')->name('admin.')->group(
Route::get('pages/{page}/weeztix/oauth/redirect', [WeeztixOAuthController::class, 'redirect'])->name('pages.weeztix.oauth.redirect');
Route::get('weeztix/callback', [WeeztixOAuthController::class, 'callback'])->name('weeztix.callback');
Route::post('weeztix/companies', [WeeztixApiController::class, 'companies'])->name('weeztix.companies');
Route::post('weeztix/coupons', [WeeztixApiController::class, 'coupons'])->name('weeztix.coupons');
// User management (superadmin only)