diff --git a/app/Http/Controllers/ShopAuthController.php b/app/Http/Controllers/ShopAuthController.php new file mode 100644 index 0000000..781b6e2 --- /dev/null +++ b/app/Http/Controllers/ShopAuthController.php @@ -0,0 +1,126 @@ +current(); + if (! $tenant || ! data_get($tenant->settings, 'shop.enabled')) { + throw new NotFoundHttpException('Magazinul online nu este activ.'); + } + return $tenant; + } + + public function showRegister() + { + $tenant = $this->tenantOrFail(); + if (Auth::guard('shop')->check()) return redirect('/shop/account'); + return view('shop.auth.register', ['tenant' => $tenant, 'cartCount' => $this->cartCount()]); + } + + public function register(Request $request) + { + $tenant = $this->tenantOrFail(); + $data = $request->validate([ + 'name' => 'required|string|max:160', + 'phone' => 'required|string|max:40', + 'email' => 'nullable|email|max:160', + 'password' => 'required|string|min:6|confirmed', + ]); + + // Unique per tenant (handled by composite index, but check for nicer error). + if (ShopCustomer::where('phone', $data['phone'])->exists()) { + return back()->withErrors(['phone' => 'Există deja un cont cu acest telefon.'])->withInput(); + } + + // Auto-link to existing Client by phone if present. + $client = Client::where('phone', $data['phone'])->first(); + + $customer = ShopCustomer::create([ + 'client_id' => $client?->id, + 'name' => $data['name'], + 'phone' => $data['phone'], + 'email' => $data['email'] ?? null, + 'password' => $data['password'], // hashed by cast + ]); + + event(new Registered($customer)); + Auth::guard('shop')->login($customer, remember: true); + $customer->forceFill(['last_login_at' => now()])->save(); + + return redirect('/shop/account'); + } + + public function showLogin() + { + $tenant = $this->tenantOrFail(); + if (Auth::guard('shop')->check()) return redirect('/shop/account'); + return view('shop.auth.login', ['tenant' => $tenant, 'cartCount' => $this->cartCount()]); + } + + public function login(Request $request) + { + $tenant = $this->tenantOrFail(); + $data = $request->validate([ + 'phone' => 'required|string|max:40', + 'password' => 'required|string', + ]); + + $ok = Auth::guard('shop')->attempt( + ['phone' => $data['phone'], 'password' => $data['password']], + remember: true + ); + if (! $ok) { + return back()->withErrors(['phone' => 'Telefon sau parolă incorecte.'])->withInput(); + } + + $request->session()->regenerate(); + Auth::guard('shop')->user()?->forceFill(['last_login_at' => now()])->save(); + return redirect()->intended('/shop/account'); + } + + public function logout(Request $request) + { + Auth::guard('shop')->logout(); + $request->session()->invalidate(); + $request->session()->regenerateToken(); + return redirect('/shop'); + } + + public function account() + { + $tenant = $this->tenantOrFail(); + $customer = Auth::guard('shop')->user(); + if (! $customer) return redirect('/shop/login'); + + $orders = $customer->orders() + ->latest('created_at') + ->limit(50) + ->get(); + + return view('shop.account', [ + 'tenant' => $tenant, + 'customer' => $customer, + 'orders' => $orders, + 'cartCount' => $this->cartCount(), + ]); + } + + private function cartCount(): int + { + $tenant = app(TenantManager::class)->current(); + $cart = (array) session('shop_cart_' . ($tenant?->id ?? '0'), []); + return (int) collect($cart)->sum('qty'); + } +} diff --git a/app/Http/Controllers/ShopController.php b/app/Http/Controllers/ShopController.php index cf78637..a06e067 100644 --- a/app/Http/Controllers/ShopController.php +++ b/app/Http/Controllers/ShopController.php @@ -155,11 +155,13 @@ class ShopController extends Controller if (empty($cart)) return redirect('/shop'); $subtotal = collect($cart)->sum(fn ($i) => $i['price'] * $i['qty']); + $customer = \Illuminate\Support\Facades\Auth::guard('shop')->user(); return view('shop.checkout', [ 'tenant' => $tenant, 'cart' => $cart, 'subtotal' => $subtotal, + 'customer' => $customer, 'deliveryOptions' => (array) data_get($tenant->settings, 'shop.delivery_methods', ['pickup']), 'cartCount' => $this->cartCount(), ]); @@ -188,9 +190,13 @@ class ShopController extends Controller $deliveryFee = ($freeOver > 0 && $subtotal >= $freeOver) ? 0.0 : $fee; } - $order = DB::transaction(function () use ($tenant, $cart, $data, $deliveryFee) { + $shopCustomer = \Illuminate\Support\Facades\Auth::guard('shop')->user(); + + $order = DB::transaction(function () use ($tenant, $cart, $data, $deliveryFee, $shopCustomer) { $order = OnlineOrder::create([ 'number' => OnlineOrder::generateNumber($tenant->id), + 'shop_customer_id' => $shopCustomer?->id, + 'client_id' => $shopCustomer?->client_id, 'customer_name' => $data['customer_name'], 'customer_phone' => $data['customer_phone'], 'customer_email' => $data['customer_email'] ?? null, diff --git a/app/Models/Tenant/OnlineOrder.php b/app/Models/Tenant/OnlineOrder.php index 5cb555d..0dc14ab 100644 --- a/app/Models/Tenant/OnlineOrder.php +++ b/app/Models/Tenant/OnlineOrder.php @@ -29,7 +29,7 @@ class OnlineOrder extends Model ]; protected $fillable = [ - 'company_id', 'number', 'tracking_token', 'client_id', + 'company_id', 'number', 'tracking_token', 'client_id', 'shop_customer_id', 'customer_name', 'customer_phone', 'customer_email', 'delivery_method', 'address', 'status', 'subtotal', 'delivery_fee', 'total', 'notes', @@ -51,6 +51,11 @@ class OnlineOrder extends Model return $this->belongsTo(Client::class); } + public function shopCustomer(): BelongsTo + { + return $this->belongsTo(ShopCustomer::class); + } + public function trackingUrl(): string { return url('/shop/order/' . $this->tracking_token); diff --git a/app/Models/Tenant/ShopCustomer.php b/app/Models/Tenant/ShopCustomer.php new file mode 100644 index 0000000..1616dde --- /dev/null +++ b/app/Models/Tenant/ShopCustomer.php @@ -0,0 +1,42 @@ + 'datetime', + 'password' => 'hashed', + ]; + + public function client(): BelongsTo + { + return $this->belongsTo(Client::class); + } + + public function orders(): HasMany + { + return $this->hasMany(OnlineOrder::class); + } + + /** Auth column for Laravel's session guard. */ + public function getAuthIdentifierName() + { + return 'id'; + } +} diff --git a/config/auth.php b/config/auth.php index d9f7be5..0d39ef8 100644 --- a/config/auth.php +++ b/config/auth.php @@ -22,6 +22,12 @@ return [ 'driver' => 'session', 'provider' => 'super_admins', ], + + // Public storefront customer auth (per-tenant). + 'shop' => [ + 'driver' => 'session', + 'provider' => 'shop_customers', + ], ], 'providers' => [ @@ -33,6 +39,10 @@ return [ 'driver' => 'eloquent', 'model' => SuperAdmin::class, ], + 'shop_customers' => [ + 'driver' => 'eloquent', + 'model' => \App\Models\Tenant\ShopCustomer::class, + ], ], 'passwords' => [ diff --git a/database/migrations/2026_06_02_120000_create_shop_customers.php b/database/migrations/2026_06_02_120000_create_shop_customers.php new file mode 100644 index 0000000..d800e5d --- /dev/null +++ b/database/migrations/2026_06_02_120000_create_shop_customers.php @@ -0,0 +1,43 @@ +id(); + $t->foreignId('company_id')->constrained()->cascadeOnDelete(); + $t->foreignId('client_id')->nullable()->constrained()->nullOnDelete(); + $t->string('name', 160); + $t->string('phone', 40); + $t->string('email', 160)->nullable(); + $t->string('password'); + $t->dateTime('last_login_at')->nullable(); + $t->rememberToken(); + $t->timestamps(); + $t->softDeletes(); + + $t->unique(['company_id', 'phone'], 'shop_customers_company_phone_unique'); + $t->index(['company_id', 'email']); + }); + + Schema::table('online_orders', function (Blueprint $t) { + $t->foreignId('shop_customer_id')->nullable()->after('client_id') + ->constrained()->nullOnDelete(); + $t->index(['company_id', 'shop_customer_id']); + }); + } + + public function down(): void + { + Schema::table('online_orders', function (Blueprint $t) { + $t->dropForeign(['shop_customer_id']); + $t->dropColumn('shop_customer_id'); + }); + Schema::dropIfExists('shop_customers'); + } +}; diff --git a/resources/views/shop/account.blade.php b/resources/views/shop/account.blade.php new file mode 100644 index 0000000..0c527e7 --- /dev/null +++ b/resources/views/shop/account.blade.php @@ -0,0 +1,47 @@ +@extends('shop.layout') +@section('title', 'Contul meu') +@section('content') +@php + $currency = $tenant->settings['currency'] ?? 'MDL'; + $statuses = \App\Models\Tenant\OnlineOrder::STATUSES; +@endphp + +
📞 {{ $customer->phone }}
+ @if ($customer->email)✉️ {{ $customer->email }}
@endif +Nu ai nicio comandă încă.
+ Vezi catalogul +| Nr. | Data | Articole | Total | Status | + |
|---|---|---|---|---|---|
| #{{ $order->number }} | +{{ $order->created_at->format('d.m.Y') }} | +{{ $order->items()->count() }} | +{{ number_format((float) $order->total, 2) }} {{ $currency }} | +{{ $statuses[$order->status] ?? $order->status }} | +Detalii → | +
+ Nu ai cont? Înregistrare +
++ Ai deja cont? Login +
+