Initial commit
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Material;
|
||||
use App\Models\Product;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class CartController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$cart = session()->get('cart', []);
|
||||
$subtotal = 0;
|
||||
|
||||
foreach ($cart as $item) {
|
||||
$subtotal += $item['price'] * $item['quantity'];
|
||||
}
|
||||
|
||||
$shipping = $subtotal > 50 || empty($cart) ? 0.00 : 4.95;
|
||||
$total = $subtotal + $shipping;
|
||||
|
||||
return view('cart.index', compact('cart', 'subtotal', 'shipping', 'total'));
|
||||
}
|
||||
|
||||
public function add(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'product_id' => 'required|exists:products,id',
|
||||
'material' => 'required|string',
|
||||
'color' => 'required|string',
|
||||
'scale' => 'nullable|string',
|
||||
'infill' => 'nullable|string',
|
||||
'quantity' => 'required|integer|min:1|max:50',
|
||||
]);
|
||||
|
||||
$product = Product::findOrFail($validated['product_id']);
|
||||
$material = Material::where('code', $validated['material'])->first();
|
||||
|
||||
// Prijs berekening inclusief materiaal vermenigvuldiger & schaal
|
||||
$unitPrice = $product->base_price;
|
||||
if ($material) {
|
||||
$unitPrice *= $material->price_multiplier;
|
||||
}
|
||||
|
||||
// Schaal aanpassing
|
||||
$scale = $validated['scale'] ?? '100%';
|
||||
if ($scale === '150%') {
|
||||
$unitPrice *= 1.45;
|
||||
} elseif ($scale === '200%') {
|
||||
$unitPrice *= 1.95;
|
||||
}
|
||||
|
||||
$unitPrice = round($unitPrice, 2);
|
||||
|
||||
// Unieke sleutel in sessie op basis van product + configuratie
|
||||
$cartKey = md5($product->id . '_' . $validated['material'] . '_' . $validated['color'] . '_' . $scale);
|
||||
|
||||
$cart = session()->get('cart', []);
|
||||
|
||||
if (isset($cart[$cartKey])) {
|
||||
$cart[$cartKey]['quantity'] += (int)$validated['quantity'];
|
||||
} else {
|
||||
$cart[$cartKey] = [
|
||||
'id' => $product->id,
|
||||
'name' => $product->name,
|
||||
'slug' => $product->slug,
|
||||
'image_url' => $product->image_url,
|
||||
'material' => $material ? $material->name : $validated['material'],
|
||||
'material_code' => $validated['material'],
|
||||
'color' => $validated['color'],
|
||||
'scale' => $scale,
|
||||
'infill' => $validated['infill'] ?? 'Standaard (20%)',
|
||||
'price' => $unitPrice,
|
||||
'quantity' => (int)$validated['quantity'],
|
||||
];
|
||||
}
|
||||
|
||||
session()->put('cart', $cart);
|
||||
|
||||
if ($request->wantsJson()) {
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Artikel toegevoegd aan je winkelwagen!',
|
||||
'cartCount' => array_sum(array_column($cart, 'quantity')),
|
||||
]);
|
||||
}
|
||||
|
||||
return redirect()->route('cart.index')->with('success', "{$product->name} is toegevoegd aan je winkelwagen!");
|
||||
}
|
||||
|
||||
public function update(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'key' => 'required|string',
|
||||
'quantity' => 'required|integer|min:1|max:50',
|
||||
]);
|
||||
|
||||
$cart = session()->get('cart', []);
|
||||
|
||||
if (isset($cart[$validated['key']])) {
|
||||
$cart[$validated['key']]['quantity'] = (int)$validated['quantity'];
|
||||
session()->put('cart', $cart);
|
||||
}
|
||||
|
||||
return redirect()->route('cart.index')->with('success', 'Winkelwagen bijgewerkt.');
|
||||
}
|
||||
|
||||
public function remove($key)
|
||||
{
|
||||
$cart = session()->get('cart', []);
|
||||
|
||||
if (isset($cart[$key])) {
|
||||
unset($cart[$key]);
|
||||
session()->put('cart', $cart);
|
||||
}
|
||||
|
||||
return redirect()->route('cart.index')->with('success', 'Artikel verwijderd uit je winkelwagen.');
|
||||
}
|
||||
|
||||
public function count()
|
||||
{
|
||||
$cart = session()->get('cart', []);
|
||||
$totalItems = array_sum(array_column($cart, 'quantity'));
|
||||
|
||||
return response()->json(['count' => $totalItems]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Order;
|
||||
use App\Models\OrderItem;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class CheckoutController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$cart = session()->get('cart', []);
|
||||
|
||||
if (empty($cart)) {
|
||||
return redirect()->route('cart.index')->with('error', 'Je winkelwagen is nog leeg.');
|
||||
}
|
||||
|
||||
$subtotal = 0;
|
||||
foreach ($cart as $item) {
|
||||
$subtotal += $item['price'] * $item['quantity'];
|
||||
}
|
||||
|
||||
$shipping = $subtotal > 50 ? 0.00 : 4.95;
|
||||
$total = $subtotal + $shipping;
|
||||
|
||||
return view('checkout.index', compact('cart', 'subtotal', 'shipping', 'total'));
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$cart = session()->get('cart', []);
|
||||
|
||||
if (empty($cart)) {
|
||||
return redirect()->route('cart.index')->with('error', 'Je winkelwagen is leeg.');
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'customer_name' => 'required|string|max:255',
|
||||
'customer_email' => 'required|email|max:255',
|
||||
'customer_phone' => 'nullable|string|max:50',
|
||||
'shipping_street' => 'required|string|max:255',
|
||||
'shipping_number' => 'required|string|max:50',
|
||||
'shipping_postal_code' => 'required|string|max:20',
|
||||
'shipping_city' => 'required|string|max:100',
|
||||
'notes' => 'nullable|string|max:1000',
|
||||
'payment_method' => 'required|string|in:ideal,bancontact,overboeking',
|
||||
]);
|
||||
|
||||
$subtotal = 0;
|
||||
foreach ($cart as $item) {
|
||||
$subtotal += $item['price'] * $item['quantity'];
|
||||
}
|
||||
|
||||
$shipping = $subtotal > 50 ? 0.00 : 4.95;
|
||||
$total = $subtotal + $shipping;
|
||||
|
||||
$orderNumber = 'J3D-' . date('Y') . '-' . strtoupper(Str::random(6));
|
||||
|
||||
$order = Order::create([
|
||||
'order_number' => $orderNumber,
|
||||
'customer_name' => $validated['customer_name'],
|
||||
'customer_email' => $validated['customer_email'],
|
||||
'customer_phone' => $validated['customer_phone'] ?? null,
|
||||
'shipping_street' => $validated['shipping_street'],
|
||||
'shipping_number' => $validated['shipping_number'],
|
||||
'shipping_postal_code' => $validated['shipping_postal_code'],
|
||||
'shipping_city' => $validated['shipping_city'],
|
||||
'shipping_country' => 'Nederland',
|
||||
'notes' => $validated['notes'] ?? null,
|
||||
'status' => 'in_behandeling',
|
||||
'subtotal' => $subtotal,
|
||||
'shipping_cost' => $shipping,
|
||||
'total' => $total,
|
||||
'payment_status' => 'in_afwachting',
|
||||
'payment_method' => $validated['payment_method'],
|
||||
]);
|
||||
|
||||
foreach ($cart as $item) {
|
||||
OrderItem::create([
|
||||
'order_id' => $order->id,
|
||||
'product_id' => $item['id'],
|
||||
'product_name' => $item['name'],
|
||||
'material' => $item['material'] ?? null,
|
||||
'color' => $item['color'] ?? null,
|
||||
'scale' => $item['scale'] ?? '100%',
|
||||
'infill' => $item['infill'] ?? 'Standaard (20%)',
|
||||
'quantity' => $item['quantity'],
|
||||
'unit_price' => $item['price'],
|
||||
'total_price' => $item['price'] * $item['quantity'],
|
||||
]);
|
||||
}
|
||||
|
||||
// Leeg winkelwagen na succesvolle plaatsing
|
||||
session()->forget('cart');
|
||||
|
||||
return redirect()->route('checkout.success', $order->order_number);
|
||||
}
|
||||
|
||||
public function success($orderNumber)
|
||||
{
|
||||
$order = Order::with('items')->where('order_number', $orderNumber)->firstOrFail();
|
||||
|
||||
return view('checkout.success', compact('order'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
abstract class Controller
|
||||
{
|
||||
//
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Category;
|
||||
use App\Models\Color;
|
||||
use App\Models\CustomRequest;
|
||||
use App\Models\Material;
|
||||
use App\Models\Product;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ShopController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$categories = Category::where('is_featured', true)
|
||||
->orderBy('sort_order')
|
||||
->take(6)
|
||||
->get();
|
||||
|
||||
$featuredProducts = Product::with('category')
|
||||
->where('is_active', true)
|
||||
->where('is_featured', true)
|
||||
->latest()
|
||||
->take(6)
|
||||
->get();
|
||||
|
||||
$materials = Material::where('is_active', true)->get();
|
||||
|
||||
return view('shop.index', compact('categories', 'featuredProducts', 'materials'));
|
||||
}
|
||||
|
||||
public function catalog(Request $request)
|
||||
{
|
||||
$query = Product::with('category')->where('is_active', true);
|
||||
|
||||
// Filter op categorie
|
||||
if ($request->filled('category')) {
|
||||
$query->whereHas('category', function ($q) use ($request) {
|
||||
$q->where('slug', $request->category);
|
||||
});
|
||||
}
|
||||
|
||||
// Filter op materiaal
|
||||
if ($request->filled('material')) {
|
||||
$query->whereJsonContains('available_materials', $request->material);
|
||||
}
|
||||
|
||||
// Zoekfunctie
|
||||
if ($request->filled('q')) {
|
||||
$search = $request->q;
|
||||
$query->where(function ($q) use ($search) {
|
||||
$q->where('name', 'like', "%{$search}%")
|
||||
->orWhere('short_description', 'like', "%{$search}%")
|
||||
->orWhere('description', 'like', "%{$search}%");
|
||||
});
|
||||
}
|
||||
|
||||
// Sortering
|
||||
$sort = $request->get('sort', 'newest');
|
||||
if ($sort === 'price_asc') {
|
||||
$query->orderBy('base_price', 'asc');
|
||||
} elseif ($sort === 'price_desc') {
|
||||
$query->orderBy('base_price', 'desc');
|
||||
} else {
|
||||
$query->latest();
|
||||
}
|
||||
|
||||
$products = $query->paginate(12)->withQueryString();
|
||||
$categories = Category::orderBy('name')->get();
|
||||
$materials = Material::where('is_active', true)->get();
|
||||
|
||||
return view('shop.catalog', compact('products', 'categories', 'materials'));
|
||||
}
|
||||
|
||||
public function show($slug)
|
||||
{
|
||||
$product = Product::with('category')->where('slug', $slug)->firstOrFail();
|
||||
$allMaterials = Material::where('is_active', true)->get()->keyBy('code');
|
||||
$allColors = Color::where('in_stock', true)->get();
|
||||
|
||||
$relatedProducts = Product::where('id', '!=', $product->id)
|
||||
->where('category_id', $product->category_id)
|
||||
->where('is_active', true)
|
||||
->take(3)
|
||||
->get();
|
||||
|
||||
return view('shop.show', compact('product', 'allMaterials', 'allColors', 'relatedProducts'));
|
||||
}
|
||||
|
||||
public function customRequest()
|
||||
{
|
||||
$materials = Material::where('is_active', true)->get();
|
||||
$colors = Color::where('in_stock', true)->get();
|
||||
|
||||
return view('shop.custom', compact('materials', 'colors'));
|
||||
}
|
||||
|
||||
public function storeCustomRequest(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|email|max:255',
|
||||
'phone' => 'nullable|string|max:50',
|
||||
'description' => 'required|string|max:2000',
|
||||
'preferred_material' => 'nullable|string|max:50',
|
||||
'preferred_color' => 'nullable|string|max:50',
|
||||
'dimensions' => 'nullable|string|max:100',
|
||||
'model_file' => 'nullable|file|max:51200', // max 50MB (stl, step, obj, zip)
|
||||
]);
|
||||
|
||||
$filePath = null;
|
||||
$originalName = null;
|
||||
|
||||
if ($request->hasFile('model_file')) {
|
||||
$file = $request->file('model_file');
|
||||
$originalName = $file->getClientOriginalName();
|
||||
$filePath = $file->store('custom_models', 'public');
|
||||
}
|
||||
|
||||
CustomRequest::create([
|
||||
'name' => $validated['name'],
|
||||
'email' => $validated['email'],
|
||||
'phone' => $validated['phone'] ?? null,
|
||||
'description' => $validated['description'],
|
||||
'preferred_material' => $validated['preferred_material'] ?? null,
|
||||
'preferred_color' => $validated['preferred_color'] ?? null,
|
||||
'dimensions' => $validated['dimensions'] ?? null,
|
||||
'file_path' => $filePath,
|
||||
'original_filename' => $originalName,
|
||||
'status' => 'nieuw',
|
||||
]);
|
||||
|
||||
return redirect()->route('custom.request')->with('success', 'Bedankt! Jouw 3D print aanvraag is succesvol ontvangen. We beoordelen je model en sturen binnen 24 uur een vrijblijvende prijsopgave.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class Category extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'slug',
|
||||
'description',
|
||||
'icon',
|
||||
'is_featured',
|
||||
'sort_order',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'is_featured' => 'boolean',
|
||||
'sort_order' => 'integer',
|
||||
];
|
||||
|
||||
public function products(): HasMany
|
||||
{
|
||||
return $this->hasMany(Product::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Color extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'hex',
|
||||
'in_stock',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'in_stock' => 'boolean',
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class CustomRequest extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'email',
|
||||
'phone',
|
||||
'description',
|
||||
'file_path',
|
||||
'original_filename',
|
||||
'preferred_material',
|
||||
'preferred_color',
|
||||
'dimensions',
|
||||
'status',
|
||||
'quoted_price',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'quoted_price' => 'float',
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Material extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'code',
|
||||
'description',
|
||||
'badge_color',
|
||||
'price_multiplier',
|
||||
'is_active',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'price_multiplier' => 'float',
|
||||
'is_active' => 'boolean',
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class Order extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'order_number',
|
||||
'customer_name',
|
||||
'customer_email',
|
||||
'customer_phone',
|
||||
'shipping_street',
|
||||
'shipping_number',
|
||||
'shipping_postal_code',
|
||||
'shipping_city',
|
||||
'shipping_country',
|
||||
'notes',
|
||||
'status',
|
||||
'subtotal',
|
||||
'shipping_cost',
|
||||
'total',
|
||||
'payment_status',
|
||||
'payment_method',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'subtotal' => 'float',
|
||||
'shipping_cost' => 'float',
|
||||
'total' => 'float',
|
||||
];
|
||||
|
||||
public function items(): HasMany
|
||||
{
|
||||
return $this->hasMany(OrderItem::class);
|
||||
}
|
||||
|
||||
public function getFormattedTotalAttribute(): string
|
||||
{
|
||||
return '€ ' . number_format($this->total, 2, ',', '.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class OrderItem extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'order_id',
|
||||
'product_id',
|
||||
'product_name',
|
||||
'material',
|
||||
'color',
|
||||
'scale',
|
||||
'infill',
|
||||
'quantity',
|
||||
'unit_price',
|
||||
'total_price',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'unit_price' => 'float',
|
||||
'total_price' => 'float',
|
||||
'quantity' => 'integer',
|
||||
];
|
||||
|
||||
public function order(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Order::class);
|
||||
}
|
||||
|
||||
public function product(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Product::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class Product extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'category_id',
|
||||
'name',
|
||||
'slug',
|
||||
'short_description',
|
||||
'description',
|
||||
'base_price',
|
||||
'sku',
|
||||
'is_print_on_demand',
|
||||
'stock',
|
||||
'is_featured',
|
||||
'is_active',
|
||||
'dimensions',
|
||||
'weight_grams',
|
||||
'print_time_hours',
|
||||
'image_url',
|
||||
'gallery_images',
|
||||
'available_materials',
|
||||
'available_colors',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'base_price' => 'float',
|
||||
'is_print_on_demand' => 'boolean',
|
||||
'is_featured' => 'boolean',
|
||||
'is_active' => 'boolean',
|
||||
'print_time_hours' => 'float',
|
||||
'gallery_images' => 'array',
|
||||
'available_materials' => 'array',
|
||||
'available_colors' => 'array',
|
||||
];
|
||||
|
||||
public function category(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Category::class);
|
||||
}
|
||||
|
||||
public function orderItems(): HasMany
|
||||
{
|
||||
return $this->hasMany(OrderItem::class);
|
||||
}
|
||||
|
||||
public function getFormattedPriceAttribute(): string
|
||||
{
|
||||
return '€ ' . number_format($this->base_price, 2, ',', '.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Database\Factories\UserFactory;
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Attributes\Hidden;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
|
||||
#[Fillable(['name', 'email', 'password'])]
|
||||
#[Hidden(['password', 'remember_token'])]
|
||||
class User extends Authenticatable
|
||||
{
|
||||
/** @use HasFactory<UserFactory> */
|
||||
use HasFactory, Notifiable;
|
||||
|
||||
/**
|
||||
* Get the attributes that should be cast.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'email_verified_at' => 'datetime',
|
||||
'password' => 'hashed',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user