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:
@@ -13,38 +13,10 @@ use RuntimeException;
|
|||||||
|
|
||||||
class WeeztixApiController extends Controller
|
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
|
public function coupons(Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
$request->validate([
|
$request->validate([
|
||||||
'page_id' => ['required', 'integer', 'exists:preregistration_pages,id'],
|
'page_id' => ['required', 'integer', 'exists:preregistration_pages,id'],
|
||||||
'company_guid' => ['required', 'string', 'max:255'],
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$page = PreregistrationPage::query()->findOrFail($request->integer('page_id'));
|
$page = PreregistrationPage::query()->findOrFail($request->integer('page_id'));
|
||||||
@@ -57,9 +29,12 @@ class WeeztixApiController extends Controller
|
|||||||
], 422);
|
], 422);
|
||||||
}
|
}
|
||||||
|
|
||||||
$companyGuid = $request->string('company_guid')->toString();
|
$companyGuid = $config->company_guid;
|
||||||
$previousGuid = $config->company_guid;
|
if (! is_string($companyGuid) || $companyGuid === '') {
|
||||||
$config->setAttribute('company_guid', $companyGuid);
|
return response()->json([
|
||||||
|
'message' => __('Geen Weeztix-bedrijf gekoppeld. Verbind opnieuw met Weeztix.'),
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$raw = (new WeeztixService($config))->getCoupons();
|
$raw = (new WeeztixService($config))->getCoupons();
|
||||||
@@ -70,8 +45,6 @@ class WeeztixApiController extends Controller
|
|||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => __('Kon kortingsbonnen niet laden.'),
|
'message' => __('Kon kortingsbonnen niet laden.'),
|
||||||
], 422);
|
], 422);
|
||||||
} finally {
|
|
||||||
$config->setAttribute('company_guid', $previousGuid);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -100,6 +100,10 @@ class WeeztixOAuthController extends Controller
|
|||||||
try {
|
try {
|
||||||
$service = new WeeztixService($config);
|
$service = new WeeztixService($config);
|
||||||
$service->exchangeAuthorizationCode($request->string('code')->toString());
|
$service->exchangeAuthorizationCode($request->string('code')->toString());
|
||||||
|
$config = $config->fresh();
|
||||||
|
if ($config !== null) {
|
||||||
|
(new WeeztixService($config))->ensureCompanyStoredFromWeeztix();
|
||||||
|
}
|
||||||
} catch (RuntimeException $e) {
|
} catch (RuntimeException $e) {
|
||||||
Log::error('Weeztix OAuth callback failed', [
|
Log::error('Weeztix OAuth callback failed', [
|
||||||
'page_id' => $page->id,
|
'page_id' => $page->id,
|
||||||
|
|||||||
@@ -44,8 +44,6 @@ class UpdateWeeztixConfigRequest extends FormRequest
|
|||||||
'string',
|
'string',
|
||||||
'max:2048',
|
'max:2048',
|
||||||
],
|
],
|
||||||
'company_guid' => ['nullable', 'string', 'max:255'],
|
|
||||||
'company_name' => ['nullable', 'string', 'max:255'],
|
|
||||||
'coupon_guid' => ['nullable', 'string', 'max:255'],
|
'coupon_guid' => ['nullable', 'string', 'max:255'],
|
||||||
'coupon_name' => ['nullable', 'string', 'max:255'],
|
'coupon_name' => ['nullable', 'string', 'max:255'],
|
||||||
'code_prefix' => ['nullable', 'string', 'max:32'],
|
'code_prefix' => ['nullable', 'string', 'max:32'],
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ use Illuminate\Support\Facades\Log;
|
|||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
use LogicException;
|
use LogicException;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
final class WeeztixService
|
final class WeeztixService
|
||||||
{
|
{
|
||||||
@@ -84,6 +85,42 @@ final class WeeztixService
|
|||||||
return $this->normalizeCompaniesFromProfile($json);
|
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).
|
* Exchange OAuth authorization code for tokens (admin callback).
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -769,16 +769,12 @@ document.addEventListener('alpine:init', () => {
|
|||||||
|
|
||||||
Alpine.data('weeztixSetup', (cfg) => ({
|
Alpine.data('weeztixSetup', (cfg) => ({
|
||||||
pageId: cfg.pageId,
|
pageId: cfg.pageId,
|
||||||
companiesUrl: cfg.companiesUrl,
|
|
||||||
couponsUrl: cfg.couponsUrl,
|
couponsUrl: cfg.couponsUrl,
|
||||||
csrf: cfg.csrf,
|
csrf: cfg.csrf,
|
||||||
isConnected: cfg.isConnected === true,
|
isConnected: cfg.isConnected === true,
|
||||||
callbackUrl: cfg.callbackUrl,
|
callbackUrl: cfg.callbackUrl,
|
||||||
errorMessage: '',
|
errorMessage: '',
|
||||||
companies: [],
|
|
||||||
coupons: [],
|
coupons: [],
|
||||||
companyGuid: '',
|
|
||||||
companyName: '',
|
|
||||||
couponGuid: '',
|
couponGuid: '',
|
||||||
couponName: '',
|
couponName: '',
|
||||||
codePrefix: 'PREREG',
|
codePrefix: 'PREREG',
|
||||||
@@ -797,19 +793,12 @@ document.addEventListener('alpine:init', () => {
|
|||||||
} else {
|
} else {
|
||||||
this.usageCount = 1;
|
this.usageCount = 1;
|
||||||
}
|
}
|
||||||
this.companyGuid = cfg.existing.company_guid || '';
|
|
||||||
this.companyName = cfg.existing.company_name || '';
|
|
||||||
this.couponGuid = cfg.existing.coupon_guid || '';
|
this.couponGuid = cfg.existing.coupon_guid || '';
|
||||||
this.couponName = cfg.existing.coupon_name || '';
|
this.couponName = cfg.existing.coupon_name || '';
|
||||||
}
|
}
|
||||||
if (this.isConnected) {
|
if (this.isConnected) {
|
||||||
await this.loadCompanies();
|
await this.loadCoupons();
|
||||||
if (this.companyGuid) {
|
} else if (cfg.existing && cfg.existing.coupon_guid) {
|
||||||
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();
|
|
||||||
this.ensureSelectedCouponInList();
|
this.ensureSelectedCouponInList();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -829,18 +818,6 @@ document.addEventListener('alpine:init', () => {
|
|||||||
return { res, data };
|
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() {
|
syncCouponName() {
|
||||||
if (!this.couponGuid) {
|
if (!this.couponGuid) {
|
||||||
this.couponName = '';
|
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() {
|
ensureSelectedCouponInList() {
|
||||||
const guid = this.couponGuid;
|
const guid = this.couponGuid;
|
||||||
if (!guid || this.coupons.some((x) => x.guid === guid)) {
|
if (!guid || this.coupons.some((x) => x.guid === guid)) {
|
||||||
@@ -876,36 +841,9 @@ document.addEventListener('alpine:init', () => {
|
|||||||
this.coupons = [{ guid, name: label }, ...this.coupons];
|
this.coupons = [{ guid, name: label }, ...this.coupons];
|
||||||
},
|
},
|
||||||
|
|
||||||
async loadCompanies() {
|
async loadCoupons() {
|
||||||
this.errorMessage = '';
|
this.errorMessage = '';
|
||||||
const { res, data } = await this.postJson(this.companiesUrl, { page_id: this.pageId });
|
const { res, data } = await this.postJson(this.couponsUrl, { 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,
|
|
||||||
});
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
this.errorMessage = data.message || this.strings.loadCouponsError;
|
this.errorMessage = data.message || this.strings.loadCouponsError;
|
||||||
this.ensureSelectedCouponInList();
|
this.ensureSelectedCouponInList();
|
||||||
@@ -915,37 +853,6 @@ document.addEventListener('alpine:init', () => {
|
|||||||
this.ensureSelectedCouponInList();
|
this.ensureSelectedCouponInList();
|
||||||
this.syncCouponName();
|
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),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,6 @@
|
|||||||
$wz = $page->weeztixConfig;
|
$wz = $page->weeztixConfig;
|
||||||
$existing = $wz !== null
|
$existing = $wz !== null
|
||||||
? [
|
? [
|
||||||
'company_guid' => $wz->company_guid,
|
|
||||||
'company_name' => $wz->company_name,
|
|
||||||
'coupon_guid' => $wz->coupon_guid,
|
'coupon_guid' => $wz->coupon_guid,
|
||||||
'coupon_name' => $wz->coupon_name,
|
'coupon_name' => $wz->coupon_name,
|
||||||
'code_prefix' => $wz->code_prefix,
|
'code_prefix' => $wz->code_prefix,
|
||||||
@@ -24,7 +22,6 @@
|
|||||||
class="mx-auto max-w-3xl"
|
class="mx-auto max-w-3xl"
|
||||||
x-data="weeztixSetup(@js([
|
x-data="weeztixSetup(@js([
|
||||||
'pageId' => $page->id,
|
'pageId' => $page->id,
|
||||||
'companiesUrl' => route('admin.weeztix.companies'),
|
|
||||||
'couponsUrl' => route('admin.weeztix.coupons'),
|
'couponsUrl' => route('admin.weeztix.coupons'),
|
||||||
'csrf' => csrf_token(),
|
'csrf' => csrf_token(),
|
||||||
'isConnected' => $wz?->is_connected ?? false,
|
'isConnected' => $wz?->is_connected ?? false,
|
||||||
@@ -33,7 +30,6 @@
|
|||||||
'existing' => $existing,
|
'existing' => $existing,
|
||||||
'strings' => [
|
'strings' => [
|
||||||
'genericError' => __('Er ging iets mis. Probeer het opnieuw.'),
|
'genericError' => __('Er ging iets mis. Probeer het opnieuw.'),
|
||||||
'selectCompany' => __('Selecteer een bedrijf.'),
|
|
||||||
'loadCouponsError' => __('Kon kortingsbonnen niet laden.'),
|
'loadCouponsError' => __('Kon kortingsbonnen niet laden.'),
|
||||||
],
|
],
|
||||||
]))"
|
]))"
|
||||||
@@ -57,6 +53,13 @@
|
|||||||
</div>
|
</div>
|
||||||
@endif
|
@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)
|
@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">
|
<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>
|
<p class="font-medium">{{ __('Verbonden met Weeztix') }}</p>
|
||||||
@@ -154,30 +157,13 @@
|
|||||||
|
|
||||||
@if ($wz !== null)
|
@if ($wz !== null)
|
||||||
<section class="space-y-4 pt-8">
|
<section class="space-y-4 pt-8">
|
||||||
<h2 class="text-lg font-semibold text-slate-900">{{ __('Stap 2: Bedrijf en kortingsbon') }}</h2>
|
<h2 class="text-lg font-semibold text-slate-900">{{ __('Stap 2: Kortingsbon') }}</h2>
|
||||||
<p class="text-sm text-slate-600">{{ __('Na een geslaagde verbinding kun je een bedrijf en bestaande coupon uit Weeztix kiezen.') }}</p>
|
<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">
|
<form method="post" action="{{ route('admin.pages.weeztix.update', $page) }}" class="space-y-4">
|
||||||
@csrf
|
@csrf
|
||||||
@method('PUT')
|
@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>
|
<div>
|
||||||
<label for="weeztix_coupon" class="block text-sm font-medium text-slate-700">{{ __('Coupon (kortingssjabloon)') }}</label>
|
<label for="weeztix_coupon" class="block text-sm font-medium text-slate-700">{{ __('Coupon (kortingssjabloon)') }}</label>
|
||||||
<select
|
<select
|
||||||
|
|||||||
@@ -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('pages/{page}/weeztix/oauth/redirect', [WeeztixOAuthController::class, 'redirect'])->name('pages.weeztix.oauth.redirect');
|
||||||
Route::get('weeztix/callback', [WeeztixOAuthController::class, 'callback'])->name('weeztix.callback');
|
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');
|
Route::post('weeztix/coupons', [WeeztixApiController::class, 'coupons'])->name('weeztix.coupons');
|
||||||
|
|
||||||
// User management (superadmin only)
|
// User management (superadmin only)
|
||||||
|
|||||||
Reference in New Issue
Block a user