218 lines
15 KiB
HTML
218 lines
15 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="nl">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>Telvero Sales</title>
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<script src="https://unpkg.com/alpinejs" defer></script>
|
|
<style>[x-cloak] { display: none !important; }</style>
|
|
</head>
|
|
<body class="bg-slate-100 min-h-screen" x-data="salesApp()" x-init="checkAuth()">
|
|
|
|
<template x-if="!isLoggedIn">
|
|
<div class="fixed inset-0 bg-slate-900 flex items-center justify-center p-4 z-50">
|
|
<div class="bg-white p-10 rounded-[2.5rem] shadow-2xl w-full max-w-md text-center">
|
|
<h2 class="text-3xl font-black mb-8 italic">TELVERO <span class="text-blue-600">LOGIN</span></h2>
|
|
<div class="space-y-4">
|
|
<input type="text" x-model="loginForm.username" placeholder="Gebruikersnaam" class="w-full border-2 border-slate-100 p-4 rounded-2xl outline-none focus:border-blue-500 transition-all">
|
|
<input type="password" x-model="loginForm.password" @keyup.enter="doLogin()" placeholder="Wachtwoord" class="w-full border-2 border-slate-100 p-4 rounded-2xl outline-none focus:border-blue-500 transition-all">
|
|
<button @click="doLogin()" class="w-full bg-blue-600 text-white p-5 rounded-2xl font-black shadow-lg hover:bg-blue-700 transition active:scale-95">INLOGGEN</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<div x-show="isLoggedIn" x-cloak class="max-w-[1400px] mx-auto p-6">
|
|
<header class="flex justify-between items-center mb-8 bg-white p-6 rounded-3xl shadow-sm border-b-4 border-blue-600">
|
|
<h1 class="text-2xl font-black italic text-slate-800">TELVERO <span class="text-blue-600">PANEL</span></h1>
|
|
<div class="flex items-center gap-6">
|
|
<span class="font-bold text-slate-400 text-sm" x-text="'Agent: ' + currentUser"></span>
|
|
<button @click="doLogout()" class="text-xs font-black text-red-500 underline uppercase tracking-tighter">Uitloggen</button>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="grid grid-cols-12 gap-6">
|
|
<div class="col-span-12 lg:col-span-4 bg-white p-8 rounded-[2rem] shadow-sm border border-slate-200">
|
|
<div class="mb-8">
|
|
<label class="block text-[10px] font-black text-blue-600 uppercase tracking-widest mb-3">Bron / Mediacode</label>
|
|
<select x-model="meta.mediacode" class="w-full border-2 border-blue-500 p-4 rounded-2xl font-bold text-blue-800 bg-blue-50 outline-none shadow-sm">
|
|
<option value="">-- SELECTEER MEDIACODE --</option>
|
|
<option value="TELVERO-NET5">TELVERO-NET5</option>
|
|
<option value="TELVERO-SBS6">TELVERO-SBS6</option>
|
|
</select>
|
|
</div>
|
|
|
|
<h2 class="font-bold mb-6 text-slate-400 uppercase text-[10px] tracking-widest border-b pb-2 italic">Klantgegevens</h2>
|
|
<div class="space-y-4">
|
|
<div class="grid grid-cols-2 gap-3">
|
|
<input type="text" x-model="form.initials" @blur="formatInitials()" placeholder="Voorletters" class="border p-3 rounded-xl w-full bg-slate-50">
|
|
<input type="text" x-model="form.lastname" @blur="formatLastname()" placeholder="Achternaam" class="border p-3 rounded-xl w-full bg-slate-50">
|
|
</div>
|
|
<div class="grid grid-cols-3 gap-2">
|
|
<input type="text" x-model="form.postcode" placeholder="Postcode" class="border p-3 rounded-xl w-full uppercase">
|
|
<input type="text" x-model="form.houseno" @blur="lookupAddress()" placeholder="Nr." class="border p-3 rounded-xl w-full">
|
|
<input type="text" x-model="form.suffix" placeholder="Toev." class="border p-3 rounded-xl w-full">
|
|
</div>
|
|
<input type="text" x-model="form.street" placeholder="Straat" class="w-full border p-3 rounded-xl bg-slate-100 font-bold text-xs" readonly>
|
|
<input type="text" x-model="form.city" placeholder="Stad" class="w-full border p-3 rounded-xl bg-slate-100 font-bold text-xs" readonly>
|
|
<input type="tel" x-model="form.phone" placeholder="Telefoonnummer (06...)" class="border-2 border-slate-100 p-3 rounded-xl w-full focus:border-blue-500 outline-none">
|
|
<input type="email" x-model="form.email" placeholder="E-mail (Verplicht)" class="border-2 border-amber-300 p-3 rounded-xl w-full outline-none focus:border-amber-400 transition-all">
|
|
<input type="text" x-model="form.dob" @blur="formatDOB()" placeholder="Geboortedatum (DDMMYYYY)" class="border p-3 rounded-xl w-full">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-span-12 lg:col-span-5 bg-white p-8 rounded-[2rem] shadow-sm border border-slate-200">
|
|
<h2 class="font-bold mb-6 text-slate-400 uppercase text-[10px] tracking-widest border-b pb-2 italic">Productselectie</h2>
|
|
<select x-model="selectedProductId" @change="selectProduct()" class="w-full border-2 border-slate-100 p-5 rounded-2xl font-black text-slate-700 mb-6 outline-none focus:border-blue-500 bg-slate-50">
|
|
<option value="">-- Kies Hoofdproduct --</option>
|
|
<template x-for="p in products" :key="p.id">
|
|
<option :value="p.id" x-text="p.name"></option>
|
|
</template>
|
|
</select>
|
|
|
|
<div x-show="variations.length > 0" x-cloak class="mb-8 p-6 bg-blue-50 rounded-3xl border border-blue-100 shadow-inner">
|
|
<label class="block text-[10px] font-black text-blue-600 uppercase mb-3 tracking-widest">Kies Optie</label>
|
|
<select x-model="selectedVariationId" @change="selectVariation()" class="w-full border-2 border-white p-4 rounded-2xl font-bold bg-white text-slate-700 shadow-sm">
|
|
<option value="">-- Maak een keuze --</option>
|
|
<template x-for="v in variations" :key="v.id">
|
|
<option :value="v.id" x-text="getVarName(v) + ' (€' + v.price + ')'"></option>
|
|
</template>
|
|
</select>
|
|
</div>
|
|
|
|
<div x-show="upsellOptions.length > 0" x-cloak class="space-y-3">
|
|
<p class="text-[10px] font-black text-red-500 uppercase tracking-widest italic px-2">Aanbevolen Extra's</p>
|
|
<template x-for="u in upsellOptions" :key="u.id">
|
|
<div class="flex items-center justify-between p-4 border rounded-2xl bg-slate-50 hover:bg-white transition-all shadow-sm">
|
|
<span class="text-xs font-bold text-slate-700" x-text="u.name + ' (€' + u.price + ')'"></span>
|
|
<button @click="toggleUpsell(u)" :class="isInCart(u.id) ? 'bg-red-500' : 'bg-green-600'" class="text-white px-6 py-2 rounded-xl text-[10px] font-black shadow-md uppercase transition active:scale-90" x-text="isInCart(u.id) ? 'Verwijder' : 'Voeg toe'"></button>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-span-12 lg:col-span-3">
|
|
<div class="bg-slate-900 text-white p-8 rounded-[2.5rem] shadow-2xl sticky top-6 border border-slate-800">
|
|
<h2 class="font-bold mb-6 border-b border-slate-800 pb-2 text-[10px] uppercase text-slate-500 tracking-widest italic">Overzicht</h2>
|
|
<div class="space-y-4 mb-8 min-h-[100px]">
|
|
<template x-for="item in cart" :key="item.id + '-' + (item.variation_id || 0)">
|
|
<div class="flex justify-between text-[11px] items-start">
|
|
<span x-text="item.name" class="opacity-80 leading-tight pr-4"></span>
|
|
<span x-text="'€' + item.price" class="font-bold text-blue-400 whitespace-nowrap"></span>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
|
|
<div class="mb-8 pt-4 border-t border-slate-800 space-y-2">
|
|
<button @click="payment_method = 'mollie_methods_ideal'" :class="payment_method === 'mollie_methods_ideal' ? 'bg-blue-600 border-blue-400' : 'bg-slate-800 border-slate-700'" class="w-full text-left p-4 rounded-2xl border text-[10px] font-bold transition-all shadow-inner">SEPA INCASSO (0.01)</button>
|
|
<button @click="payment_method = 'rve_riverty'" :class="payment_method === 'rve_riverty' ? 'bg-orange-600 border-orange-400' : 'bg-slate-800 border-slate-700'" class="w-full text-left p-4 rounded-2xl border text-[10px] font-bold uppercase transition-all shadow-inner">Riverty Achteraf</button>
|
|
</div>
|
|
|
|
<div class="flex justify-between items-center mb-8 pt-4 border-t border-slate-800">
|
|
<span class="text-xs font-bold uppercase tracking-widest text-slate-500 italic">Totaal</span>
|
|
<span class="text-3xl font-black text-green-400" x-text="'€' + total"></span>
|
|
</div>
|
|
|
|
<button @click="submitOrder()"
|
|
:disabled="submitting || !form.email || !meta.mediacode || cart.length === 0"
|
|
class="w-full bg-blue-600 hover:bg-blue-500 p-6 rounded-2xl font-black text-lg shadow-xl disabled:opacity-20 transition active:scale-95 uppercase tracking-tighter">
|
|
<span x-text="submitting ? 'PROCESSING...' : 'ORDER VERSTUREN'"></span>
|
|
</button>
|
|
<p x-show="!meta.mediacode" class="text-[9px] text-red-400 mt-4 text-center font-bold italic animate-pulse">Selecteer eerst een Mediacode!</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
function salesApp() {
|
|
return {
|
|
isLoggedIn: false, currentUser: '', loginForm: { username: '', password: '' },
|
|
products: [], upsellOptions: [], cart: [], activeProduct: null,
|
|
selectedProductId: '', selectedVariationId: '', variations: [],
|
|
payment_method: 'mollie_methods_ideal', submitting: false,
|
|
form: { initials: '', lastname: '', postcode: '', houseno: '', suffix: '', street: '', city: '', email: '', dob: '', phone: '' },
|
|
meta: { mediacode: '' },
|
|
|
|
async doLogin() {
|
|
const res = await fetch('api.php?action=login', { method: 'POST', body: JSON.stringify(this.loginForm) });
|
|
const data = await res.json();
|
|
if(data.success) { this.isLoggedIn = true; this.currentUser = data.user; this.initData(); }
|
|
else { alert(data.error); }
|
|
},
|
|
|
|
checkAuth() { if (document.cookie.includes('PHPSESSID')) { /* Optionele check */ } },
|
|
|
|
async initData() {
|
|
const res = await fetch('api.php?action=get_products');
|
|
this.products = await res.json();
|
|
},
|
|
|
|
async lookupAddress() {
|
|
if (this.form.postcode.length >= 6 && this.form.houseno) {
|
|
const res = await fetch(`api.php?action=postcode_check&postcode=${this.form.postcode}&number=${this.form.houseno}`);
|
|
const data = await res.json();
|
|
if (data.street) { this.form.street = data.street.toUpperCase(); this.form.city = data.city.toUpperCase(); }
|
|
}
|
|
},
|
|
|
|
selectProduct() {
|
|
const p = this.products.find(x => x.id == this.selectedProductId);
|
|
if(!p) return;
|
|
this.activeProduct = p;
|
|
this.variations = p.variation_details || [];
|
|
this.cart = [];
|
|
this.selectedVariationId = '';
|
|
if (p.type !== 'variable') {
|
|
this.cart = [{ id: parseInt(p.id), name: p.name, price: p.price }];
|
|
this.loadUpsells(p);
|
|
}
|
|
},
|
|
|
|
selectVariation() {
|
|
const v = this.variations.find(x => x.id == this.selectedVariationId);
|
|
if(!v) return;
|
|
this.cart = [{ id: parseInt(this.activeProduct.id), variation_id: parseInt(v.id), name: this.activeProduct.name + ' - ' + this.getVarName(v), price: v.price }];
|
|
this.loadUpsells(this.activeProduct);
|
|
},
|
|
|
|
loadUpsells(product) {
|
|
this.upsellOptions = [];
|
|
if (product.upsell_ids && product.upsell_ids.length > 0) {
|
|
this.upsellOptions = this.products.filter(x => product.upsell_ids.includes(x.id));
|
|
}
|
|
},
|
|
|
|
getVarName(v) { return v.attributes.map(a => a.option).join(' '); },
|
|
toggleUpsell(u) {
|
|
const idx = this.cart.findIndex(i => i.id === u.id);
|
|
idx > -1 ? this.cart.splice(idx, 1) : this.cart.push({ id: parseInt(u.id), name: u.name, price: u.price });
|
|
},
|
|
isInCart(id) { return this.cart.some(i => i.id === id); },
|
|
get total() { return this.cart.reduce((s, i) => s + parseFloat(i.price), 0).toFixed(2); },
|
|
|
|
formatInitials() { let v = this.form.initials.replace(/[^a-z]/gi, '').toUpperCase(); this.form.initials = v.split('').join('.') + (v ? '.' : ''); },
|
|
formatLastname() { this.form.lastname = this.form.lastname.charAt(0).toUpperCase() + this.form.lastname.slice(1); },
|
|
formatDOB() { let d = this.form.dob.replace(/[^0-9]/g, ''); if(d.length === 8) this.form.dob = `${d.slice(0,2)}-${d.slice(2,4)}-${d.slice(4,8)}`; },
|
|
|
|
async submitOrder() {
|
|
this.submitting = true;
|
|
const payload = {
|
|
payment_method: this.payment_method, mediacode_internal: this.meta.mediacode,
|
|
billing: { first_name: this.form.initials, last_name: this.form.lastname, address_1: (this.form.street + ' ' + this.form.houseno).trim(), city: this.form.city, postcode: this.form.postcode, country: 'NL', email: this.form.email, phone: this.form.phone },
|
|
line_items: this.cart.map(i => ({ product_id: i.id, variation_id: i.variation_id || 0, quantity: 1 }))
|
|
};
|
|
try {
|
|
const res = await fetch('api.php?action=create_order', { method: 'POST', body: JSON.stringify(payload) });
|
|
const result = await res.json();
|
|
if(result.payment_url) { alert("SUCCES! De order is geplaatst."); window.location.reload(); }
|
|
else { alert("Fout: " + result.error); }
|
|
} catch(e) { alert("Systeemfout"); }
|
|
this.submitting = false;
|
|
},
|
|
async doLogout() { await fetch('api.php?action=logout'); location.reload(); }
|
|
}
|
|
}
|
|
</script>
|
|
</body>
|
|
</html> |