New Initial Commit
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\CategoryResource\Pages;
|
||||
use App\Models\Category;
|
||||
use Filament\Forms;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Actions;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class CategoryResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Category::class;
|
||||
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-tag';
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('name')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
Forms\Components\Textarea::make('description')
|
||||
->maxLength(65535)
|
||||
->columnSpanFull(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('name')
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('products_count')
|
||||
->counts('products')
|
||||
->label('Products'),
|
||||
Tables\Columns\TextColumn::make('created_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
])
|
||||
->filters([
|
||||
//
|
||||
])
|
||||
->actions([
|
||||
//
|
||||
])
|
||||
->bulkActions([
|
||||
//
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ManageCategories::route('/'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\CategoryResource\Pages;
|
||||
|
||||
use App\Filament\Resources\CategoryResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ManageRecords;
|
||||
|
||||
class ManageCategories extends ManageRecords
|
||||
{
|
||||
protected static string $resource = CategoryResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make(),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getTableActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\EditAction::make(),
|
||||
Actions\DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Models\HeroImage;
|
||||
use App\Filament\Resources\HeroImageResource\Pages;
|
||||
use Filament\Forms;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class HeroImageResource extends Resource
|
||||
{
|
||||
protected static ?string $model = HeroImage::class;
|
||||
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-photo';
|
||||
|
||||
protected static ?string $navigationLabel = 'Hero Images';
|
||||
|
||||
protected static ?string $modelLabel = 'Hero Image';
|
||||
|
||||
protected static ?string $pluralModelLabel = 'Hero Images';
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->schema([
|
||||
Forms\Components\Select::make('page')
|
||||
->label('Page')
|
||||
->options(HeroImage::getPages())
|
||||
->required()
|
||||
->helperText('Choose which page this hero image will appear on'),
|
||||
|
||||
Forms\Components\FileUpload::make('image_path')
|
||||
->label('Hero Image')
|
||||
->image()
|
||||
->required()
|
||||
->disk('public')
|
||||
->directory('hero-images')
|
||||
->maxSize(5120)
|
||||
->acceptedFileTypes(['image/jpeg', 'image/png', 'image/webp', 'image/gif'])
|
||||
->helperText('Upload a high-quality image (max 5MB). Recommended size: 1920x1080px'),
|
||||
|
||||
Forms\Components\TextInput::make('title')
|
||||
->label('Heading')
|
||||
->required()
|
||||
->maxLength(255)
|
||||
->helperText('Main heading text displayed on the hero image'),
|
||||
|
||||
Forms\Components\Textarea::make('description')
|
||||
->label('Description')
|
||||
->maxLength(500)
|
||||
->rows(3)
|
||||
->helperText('Subheading text displayed below the title'),
|
||||
|
||||
Forms\Components\TextInput::make('button_text')
|
||||
->label('Button Text')
|
||||
->maxLength(100)
|
||||
->helperText('Text for the call-to-action button (optional)'),
|
||||
|
||||
Forms\Components\TextInput::make('button_link')
|
||||
->label('Button Link')
|
||||
->url()
|
||||
->maxLength(255)
|
||||
->helperText('URL or page anchor (e.g., #products or /products)'),
|
||||
|
||||
Forms\Components\TextInput::make('sort_order')
|
||||
->label('Sort Order')
|
||||
->numeric()
|
||||
->default(0)
|
||||
->helperText('Lower numbers appear first in the slider'),
|
||||
|
||||
Forms\Components\Toggle::make('is_active')
|
||||
->label('Active')
|
||||
->default(true)
|
||||
->helperText('Disable to hide this image from the website'),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('page')
|
||||
->label('Page')
|
||||
->sortable()
|
||||
->formatStateUsing(fn($state) => HeroImage::getPages()[$state] ?? $state)
|
||||
->badge(),
|
||||
|
||||
Tables\Columns\ImageColumn::make('image_path')
|
||||
->label('Image')
|
||||
->size(80),
|
||||
|
||||
Tables\Columns\TextColumn::make('title')
|
||||
->label('Title')
|
||||
->searchable()
|
||||
->limit(50),
|
||||
|
||||
Tables\Columns\TextColumn::make('sort_order')
|
||||
->label('Order')
|
||||
->sortable()
|
||||
->numeric(),
|
||||
|
||||
Tables\Columns\IconColumn::make('is_active')
|
||||
->label('Active')
|
||||
->boolean()
|
||||
->sortable(),
|
||||
|
||||
Tables\Columns\TextColumn::make('created_at')
|
||||
->label('Created')
|
||||
->dateTime('M d, Y')
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
])
|
||||
->filters([
|
||||
Tables\Filters\SelectFilter::make('page')
|
||||
->options(HeroImage::getPages()),
|
||||
|
||||
Tables\Filters\TernaryFilter::make('is_active')
|
||||
->label('Active')
|
||||
->native(false),
|
||||
])
|
||||
->actions([
|
||||
//
|
||||
])
|
||||
->bulkActions([
|
||||
//
|
||||
])
|
||||
->defaultSort('page', 'asc')
|
||||
->defaultSort('sort_order', 'asc');
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListHeroImages::route('/'),
|
||||
'create' => Pages\CreateHeroImage::route('/create'),
|
||||
'edit' => Pages\EditHeroImage::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\HeroImageResource\Pages;
|
||||
|
||||
use App\Filament\Resources\HeroImageResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateHeroImage extends CreateRecord
|
||||
{
|
||||
protected static string $resource = HeroImageResource::class;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\HeroImageResource\Pages;
|
||||
|
||||
use App\Filament\Resources\HeroImageResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditHeroImage extends EditRecord
|
||||
{
|
||||
protected static string $resource = HeroImageResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\HeroImageResource\Pages;
|
||||
|
||||
use App\Filament\Resources\HeroImageResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListHeroImages extends ListRecords
|
||||
{
|
||||
protected static string $resource = HeroImageResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make()
|
||||
->label('Add New Hero Image'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\OrderResource\Pages;
|
||||
use App\Models\Order;
|
||||
use Filament\Forms;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Actions;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class OrderResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Order::class;
|
||||
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-shopping-bag';
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('order_number')
|
||||
->required()
|
||||
->disabled()
|
||||
->maxLength(255),
|
||||
Forms\Components\TextInput::make('customer_name')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
Forms\Components\TextInput::make('customer_email')
|
||||
->email()
|
||||
->required()
|
||||
->maxLength(255),
|
||||
Forms\Components\TextInput::make('customer_phone')
|
||||
->tel()
|
||||
->maxLength(20),
|
||||
Forms\Components\Textarea::make('shipping_address')
|
||||
->required()
|
||||
->maxLength(500)
|
||||
->columnSpanFull(),
|
||||
Forms\Components\Textarea::make('notes')
|
||||
->maxLength(500)
|
||||
->columnSpanFull(),
|
||||
Forms\Components\TextInput::make('total')
|
||||
->required()
|
||||
->numeric()
|
||||
->prefix('R'),
|
||||
Forms\Components\Select::make('status')
|
||||
->options([
|
||||
'pending' => 'Pending',
|
||||
'processing' => 'Processing',
|
||||
'completed' => 'Completed',
|
||||
'cancelled' => 'Cancelled',
|
||||
])
|
||||
->required(),
|
||||
Forms\Components\Select::make('payment_status')
|
||||
->options([
|
||||
'pending' => 'Pending',
|
||||
'paid' => 'Paid',
|
||||
'failed' => 'Failed',
|
||||
])
|
||||
->required(),
|
||||
Forms\Components\TextInput::make('payment_method')
|
||||
->maxLength(50),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('order_number')
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('customer_name')
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('customer_email')
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('total')
|
||||
->money('ZAR')
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('status')
|
||||
->badge()
|
||||
->color(fn (string $state): string => match ($state) {
|
||||
'pending' => 'warning',
|
||||
'processing' => 'info',
|
||||
'completed' => 'success',
|
||||
'cancelled' => 'danger',
|
||||
}),
|
||||
Tables\Columns\TextColumn::make('payment_status')
|
||||
->badge()
|
||||
->color(fn (string $state): string => match ($state) {
|
||||
'pending' => 'warning',
|
||||
'paid' => 'success',
|
||||
'failed' => 'danger',
|
||||
}),
|
||||
Tables\Columns\TextColumn::make('created_at')
|
||||
->dateTime()
|
||||
->sortable(),
|
||||
])
|
||||
->filters([
|
||||
Tables\Filters\SelectFilter::make('status')
|
||||
->options([
|
||||
'pending' => 'Pending',
|
||||
'processing' => 'Processing',
|
||||
'completed' => 'Completed',
|
||||
'cancelled' => 'Cancelled',
|
||||
]),
|
||||
Tables\Filters\SelectFilter::make('payment_status')
|
||||
->options([
|
||||
'pending' => 'Pending',
|
||||
'paid' => 'Paid',
|
||||
'failed' => 'Failed',
|
||||
]),
|
||||
])
|
||||
->actions([
|
||||
//
|
||||
])
|
||||
->bulkActions([
|
||||
//
|
||||
])
|
||||
->defaultSort('created_at', 'desc');
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListOrders::route('/'),
|
||||
'create' => Pages\CreateOrder::route('/create'),
|
||||
'view' => Pages\ViewOrder::route('/{record}'),
|
||||
'edit' => Pages\EditOrder::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\OrderResource\Pages;
|
||||
|
||||
use App\Filament\Resources\OrderResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateOrder extends CreateRecord
|
||||
{
|
||||
protected static string $resource = OrderResource::class;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\OrderResource\Pages;
|
||||
|
||||
use App\Filament\Resources\OrderResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditOrder extends EditRecord
|
||||
{
|
||||
protected static string $resource = OrderResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\ViewAction::make(),
|
||||
Actions\DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\OrderResource\Pages;
|
||||
|
||||
use App\Filament\Resources\OrderResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListOrders extends ListRecords
|
||||
{
|
||||
protected static string $resource = OrderResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\OrderResource\Pages;
|
||||
|
||||
use App\Filament\Resources\OrderResource;
|
||||
use App\Filament\Resources\OrderResource\Widgets\OrderItemsTable;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ViewRecord;
|
||||
use Illuminate\Contracts\Support\Htmlable;
|
||||
|
||||
class ViewOrder extends ViewRecord
|
||||
{
|
||||
protected static string $resource = OrderResource::class;
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'Order #' . $this->record->order_number;
|
||||
}
|
||||
|
||||
public function getHeading(): string | Htmlable
|
||||
{
|
||||
return 'Order #' . $this->record->order_number;
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\EditAction::make(),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getHeaderWidgets(): array
|
||||
{
|
||||
return [
|
||||
OrderItemsTable::class,
|
||||
];
|
||||
}
|
||||
|
||||
public function getHeaderWidgetsColumns(): int | array
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\OrderResource\Widgets;
|
||||
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
use Filament\Widgets\TableWidget as BaseWidget;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class OrderItemsTable extends BaseWidget
|
||||
{
|
||||
public ?Model $record = null;
|
||||
|
||||
protected static ?string $heading = 'Order Items';
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->query(
|
||||
fn () => $this->record->items()->getQuery()
|
||||
)
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('product.name')
|
||||
->label('Product'),
|
||||
Tables\Columns\TextColumn::make('printStock.name')
|
||||
->label('Print Stock')
|
||||
->default('N/A'),
|
||||
Tables\Columns\TextColumn::make('type')
|
||||
->badge(),
|
||||
Tables\Columns\TextColumn::make('specifications')
|
||||
->label('Specifications')
|
||||
->getStateUsing(function ($record) {
|
||||
if ($record->type === 'wallpaper') {
|
||||
return "Length: {$record->length}m";
|
||||
} elseif ($record->type === 'mural') {
|
||||
$area = number_format($record->width * $record->height, 2);
|
||||
return "{$record->width}m × {$record->height}m ({$area}m²)";
|
||||
}
|
||||
return 'N/A';
|
||||
}),
|
||||
Tables\Columns\TextColumn::make('price')
|
||||
->money('ZAR')
|
||||
->alignEnd(),
|
||||
])
|
||||
->paginated(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\PrintStockResource\Pages;
|
||||
use App\Models\PrintStock;
|
||||
use Filament\Forms;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Actions;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class PrintStockResource extends Resource
|
||||
{
|
||||
protected static ?string $model = PrintStock::class;
|
||||
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-cube';
|
||||
|
||||
protected static ?string $navigationLabel = 'Print Stocks';
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('name')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
Forms\Components\Textarea::make('description')
|
||||
->maxLength(65535)
|
||||
->columnSpanFull(),
|
||||
Forms\Components\TextInput::make('cost_per_meter')
|
||||
->required()
|
||||
->numeric()
|
||||
->prefix('R')
|
||||
->label('Cost per Meter'),
|
||||
Forms\Components\TextInput::make('cost_per_m2')
|
||||
->required()
|
||||
->numeric()
|
||||
->prefix('R')
|
||||
->label('Cost per m²'),
|
||||
Forms\Components\Select::make('type')
|
||||
->options([
|
||||
'wallpaper' => 'Wallpaper',
|
||||
'mural' => 'Mural',
|
||||
'both' => 'Both',
|
||||
])
|
||||
->required(),
|
||||
Forms\Components\TextInput::make('width')
|
||||
->label('Width (meters)')
|
||||
->numeric()
|
||||
->step(0.01)
|
||||
->minValue(0.1)
|
||||
->default(1)
|
||||
->helperText('Width of wallpaper roll in meters'),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('name')
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('cost_per_meter')
|
||||
->money('ZAR')
|
||||
->label('Cost/m')
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('cost_per_m2')
|
||||
->money('ZAR')
|
||||
->label('Cost/m²')
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('type')
|
||||
->badge()
|
||||
->color(fn (string $state): string => match ($state) {
|
||||
'wallpaper' => 'success',
|
||||
'mural' => 'info',
|
||||
'both' => 'warning',
|
||||
}),
|
||||
Tables\Columns\TextColumn::make('created_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
])
|
||||
->filters([
|
||||
Tables\Filters\SelectFilter::make('type')
|
||||
->options([
|
||||
'wallpaper' => 'Wallpaper',
|
||||
'mural' => 'Mural',
|
||||
'both' => 'Both',
|
||||
]),
|
||||
])
|
||||
->actions([
|
||||
//
|
||||
])
|
||||
->bulkActions([
|
||||
//
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ManagePrintStocks::route('/'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\PrintStockResource\Pages;
|
||||
|
||||
use App\Filament\Resources\PrintStockResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ManageRecords;
|
||||
|
||||
class ManagePrintStocks extends ManageRecords
|
||||
{
|
||||
protected static string $resource = PrintStockResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make(),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getTableActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\EditAction::make(),
|
||||
Actions\DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\ProductResource\Pages;
|
||||
use App\Models\Product;
|
||||
use Filament\Forms;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Actions;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class ProductResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Product::class;
|
||||
|
||||
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-rectangle-stack';
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('name')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
Forms\Components\Select::make('category_id')
|
||||
->relationship('category', 'name')
|
||||
->required(),
|
||||
Forms\Components\Textarea::make('description')
|
||||
->maxLength(65535)
|
||||
->columnSpanFull(),
|
||||
Forms\Components\TextInput::make('price')
|
||||
->required()
|
||||
->numeric()
|
||||
->prefix('R'),
|
||||
Forms\Components\TextInput::make('stock')
|
||||
->required()
|
||||
->numeric()
|
||||
->default(100),
|
||||
Forms\Components\FileUpload::make('images')
|
||||
->multiple()
|
||||
->disk('public')
|
||||
->directory('products')
|
||||
->reorderable()
|
||||
->maxSize(5120)
|
||||
->acceptedFileTypes(['image/jpeg', 'image/png', 'image/webp']),
|
||||
Forms\Components\Select::make('type')
|
||||
->options([
|
||||
'wallpaper' => 'Wallpaper',
|
||||
'mural' => 'Mural',
|
||||
])
|
||||
->required(),
|
||||
Forms\Components\Select::make('printStocks')
|
||||
->relationship('printStocks', 'name')
|
||||
->multiple()
|
||||
->preload(),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\ImageColumn::make('images.image_path')
|
||||
->label('Image')
|
||||
->disk('public')
|
||||
->getStateUsing(function ($record) {
|
||||
return $record->images->first()?->image_path;
|
||||
})
|
||||
->defaultImageUrl('/images/placeholder.png')
|
||||
->circular()
|
||||
->size(60),
|
||||
Tables\Columns\TextColumn::make('name')
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('category.name')
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('price')
|
||||
->money('ZAR')
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('stock')
|
||||
->numeric()
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('type')
|
||||
->badge()
|
||||
->color(fn (string $state): string => match ($state) {
|
||||
'wallpaper' => 'success',
|
||||
'mural' => 'info',
|
||||
}),
|
||||
Tables\Columns\TextColumn::make('created_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
])
|
||||
->filters([
|
||||
Tables\Filters\SelectFilter::make('type')
|
||||
->options([
|
||||
'wallpaper' => 'Wallpaper',
|
||||
'mural' => 'Mural',
|
||||
]),
|
||||
Tables\Filters\SelectFilter::make('category')
|
||||
->relationship('category', 'name'),
|
||||
])
|
||||
->actions([
|
||||
//
|
||||
])
|
||||
->bulkActions([
|
||||
//
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListProducts::route('/'),
|
||||
'create' => Pages\CreateProduct::route('/create'),
|
||||
'edit' => Pages\EditProduct::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\ProductResource\Pages;
|
||||
|
||||
use App\Filament\Resources\ProductResource;
|
||||
use App\Models\ProductImage;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateProduct extends CreateRecord
|
||||
{
|
||||
protected static string $resource = ProductResource::class;
|
||||
|
||||
protected function mutateFormDataBeforeSave(array $data): array
|
||||
{
|
||||
// Store images in a temporary property
|
||||
if (isset($data['images']) && is_array($data['images'])) {
|
||||
$this->pendingImages = $data['images'];
|
||||
}
|
||||
unset($data['images']);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function afterSave(): void
|
||||
{
|
||||
if (!isset($this->pendingImages)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->pendingImages as $index => $imagePath) {
|
||||
ProductImage::create([
|
||||
'product_id' => $this->record->id,
|
||||
'image_path' => $imagePath,
|
||||
'sort_order' => $index,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\ProductResource\Pages;
|
||||
|
||||
use App\Filament\Resources\ProductResource;
|
||||
use App\Models\ProductImage;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditProduct extends EditRecord
|
||||
{
|
||||
protected static string $resource = ProductResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
|
||||
protected function mutateFormDataBeforeFill(array $data): array
|
||||
{
|
||||
// Load existing images for the form
|
||||
$data['images'] = $this->record->images()->orderBy('sort_order')->pluck('image_path')->toArray();
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function mutateFormDataBeforeSave(array $data): array
|
||||
{
|
||||
// Store images for processing after save
|
||||
$this->pendingImages = $data['images'] ?? [];
|
||||
unset($data['images']);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function afterSave(): void
|
||||
{
|
||||
if (!isset($this->pendingImages)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$newImages = $this->pendingImages;
|
||||
$existingImages = $this->record->images()->pluck('image_path')->toArray();
|
||||
|
||||
// Find images to delete (in existing but not in new)
|
||||
$imagesToDelete = array_diff($existingImages, $newImages);
|
||||
foreach ($imagesToDelete as $imagePath) {
|
||||
$this->record->images()->where('image_path', $imagePath)->delete();
|
||||
}
|
||||
|
||||
// Add new images and update sort order
|
||||
foreach ($newImages as $index => $imagePath) {
|
||||
ProductImage::firstOrCreate(
|
||||
[
|
||||
'product_id' => $this->record->id,
|
||||
'image_path' => $imagePath,
|
||||
],
|
||||
[
|
||||
'sort_order' => $index,
|
||||
]
|
||||
)->update(['sort_order' => $index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\ProductResource\Pages;
|
||||
|
||||
use App\Filament\Resources\ProductResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListProducts extends ListRecords
|
||||
{
|
||||
protected static string $resource = ProductResource::class;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Widgets;
|
||||
|
||||
use App\Filament\Resources\OrderResource;
|
||||
use App\Models\Order;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
use Filament\Widgets\TableWidget as BaseWidget;
|
||||
|
||||
class LatestOrders extends BaseWidget
|
||||
{
|
||||
protected static ?int $sort = 2;
|
||||
|
||||
protected int | string | array $columnSpan = 'full';
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->query(OrderResource::getEloquentQuery()->latest()->limit(10))
|
||||
->defaultPaginationPageOption(5)
|
||||
->defaultSort('created_at', 'desc')
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('order_number')
|
||||
->label('Order #')
|
||||
->searchable()
|
||||
->sortable()
|
||||
->url(fn (Order $record): string => OrderResource::getUrl('view', ['record' => $record]))
|
||||
->color('primary'),
|
||||
Tables\Columns\TextColumn::make('customer_name')
|
||||
->searchable()
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('total')
|
||||
->money('ZAR')
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('status')
|
||||
->badge()
|
||||
->color(fn (string $state): string => match ($state) {
|
||||
'pending' => 'warning',
|
||||
'processing' => 'info',
|
||||
'completed' => 'success',
|
||||
'cancelled' => 'danger',
|
||||
}),
|
||||
Tables\Columns\TextColumn::make('payment_status')
|
||||
->badge()
|
||||
->color(fn (string $state): string => match ($state) {
|
||||
'pending' => 'warning',
|
||||
'paid' => 'success',
|
||||
'failed' => 'danger',
|
||||
}),
|
||||
Tables\Columns\TextColumn::make('created_at')
|
||||
->dateTime()
|
||||
->sortable()
|
||||
->since(),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getTableHeading(): string
|
||||
{
|
||||
return 'Latest Orders';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Widgets;
|
||||
|
||||
use App\Models\Order;
|
||||
use Filament\Widgets\ChartWidget;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class RevenueChart extends ChartWidget
|
||||
{
|
||||
protected ?string $heading = 'Revenue (Last 7 Days)';
|
||||
|
||||
protected static ?int $sort = 3;
|
||||
|
||||
protected function getData(): array
|
||||
{
|
||||
$data = Order::where('payment_status', 'paid')
|
||||
->where('created_at', '>=', now()->subDays(7))
|
||||
->select(
|
||||
DB::raw('DATE(created_at) as date'),
|
||||
DB::raw('SUM(total) as revenue')
|
||||
)
|
||||
->groupBy('date')
|
||||
->orderBy('date')
|
||||
->get();
|
||||
|
||||
$labels = [];
|
||||
$revenues = [];
|
||||
|
||||
// Fill in last 7 days
|
||||
for ($i = 6; $i >= 0; $i--) {
|
||||
$date = now()->subDays($i)->format('Y-m-d');
|
||||
$dayName = now()->subDays($i)->format('D');
|
||||
$labels[] = $dayName;
|
||||
|
||||
$dayData = $data->firstWhere('date', $date);
|
||||
$revenues[] = $dayData ? $dayData->revenue : 0;
|
||||
}
|
||||
|
||||
return [
|
||||
'datasets' => [
|
||||
[
|
||||
'label' => 'Revenue (R)',
|
||||
'data' => $revenues,
|
||||
'backgroundColor' => 'rgba(59, 130, 246, 0.1)',
|
||||
'borderColor' => 'rgb(59, 130, 246)',
|
||||
'fill' => true,
|
||||
],
|
||||
],
|
||||
'labels' => $labels,
|
||||
];
|
||||
}
|
||||
|
||||
protected function getType(): string
|
||||
{
|
||||
return 'line';
|
||||
}
|
||||
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'scales' => [
|
||||
'y' => [
|
||||
'beginAtZero' => true,
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Widgets;
|
||||
|
||||
use App\Models\Order;
|
||||
use App\Models\Product;
|
||||
use Filament\Widgets\StatsOverviewWidget;
|
||||
use Filament\Widgets\StatsOverviewWidget\Stat;
|
||||
|
||||
class StatsOverview extends StatsOverviewWidget
|
||||
{
|
||||
protected static ?int $sort = 1;
|
||||
|
||||
protected function getStats(): array
|
||||
{
|
||||
$totalOrders = Order::count();
|
||||
$pendingOrders = Order::where('status', 'pending')->count();
|
||||
$processingOrders = Order::where('status', 'processing')->count();
|
||||
$lowStockProducts = Product::where('stock', '<', 10)->count();
|
||||
|
||||
$totalRevenue = Order::where('payment_status', 'paid')->sum('total');
|
||||
$monthlyRevenue = Order::where('payment_status', 'paid')
|
||||
->whereMonth('created_at', now()->month)
|
||||
->whereYear('created_at', now()->year)
|
||||
->sum('total');
|
||||
|
||||
$todayOrders = Order::whereDate('created_at', today())->count();
|
||||
$todayRevenue = Order::where('payment_status', 'paid')
|
||||
->whereDate('created_at', today())
|
||||
->sum('total');
|
||||
|
||||
return [
|
||||
Stat::make('Pending Orders', $pendingOrders)
|
||||
->description('Awaiting processing')
|
||||
->descriptionIcon('heroicon-m-clock')
|
||||
->color('warning')
|
||||
->chart([7, 5, 10, 5, $pendingOrders]),
|
||||
|
||||
Stat::make('Processing Orders', $processingOrders)
|
||||
->description('Currently being processed')
|
||||
->descriptionIcon('heroicon-m-arrow-path')
|
||||
->color('info'),
|
||||
|
||||
Stat::make('Low Stock Alert', $lowStockProducts)
|
||||
->description('Products with less than 10 units')
|
||||
->descriptionIcon('heroicon-m-exclamation-triangle')
|
||||
->color($lowStockProducts > 0 ? 'danger' : 'success'),
|
||||
|
||||
Stat::make('Today\'s Orders', $todayOrders)
|
||||
->description('Orders placed today')
|
||||
->descriptionIcon('heroicon-m-shopping-bag')
|
||||
->color('success'),
|
||||
|
||||
Stat::make('Today\'s Revenue', 'R' . number_format($todayRevenue, 2))
|
||||
->description(now()->format('l, F j'))
|
||||
->descriptionIcon('heroicon-m-banknotes')
|
||||
->color('success'),
|
||||
|
||||
Stat::make('Monthly Revenue', 'R' . number_format($monthlyRevenue, 2))
|
||||
->description(now()->format('F Y'))
|
||||
->descriptionIcon('heroicon-m-chart-bar')
|
||||
->color('info'),
|
||||
|
||||
Stat::make('Total Revenue', 'R' . number_format($totalRevenue, 2))
|
||||
->description('All time revenue')
|
||||
->descriptionIcon('heroicon-m-currency-dollar')
|
||||
->color('success'),
|
||||
|
||||
Stat::make('Total Orders', $totalOrders)
|
||||
->description('All time orders')
|
||||
->descriptionIcon('heroicon-m-shopping-cart')
|
||||
->color('primary'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Product;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class CartController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$cart = session()->get('cart', []);
|
||||
$total = 0;
|
||||
$items = [];
|
||||
|
||||
foreach ($cart as $itemKey => $cartItem) {
|
||||
// Handle both old and new cart formats
|
||||
if (is_array($cartItem)) {
|
||||
$productId = $cartItem['product_id'] ?? null;
|
||||
$type = $cartItem['type'] ?? 'wallpaper';
|
||||
$printStockId = $cartItem['print_stock_id'] ?? null;
|
||||
} else {
|
||||
// Old format: just the product ID
|
||||
$productId = $itemKey;
|
||||
$type = 'wallpaper';
|
||||
$printStockId = null;
|
||||
}
|
||||
|
||||
if ($productId) {
|
||||
$product = Product::find($productId);
|
||||
if ($product) {
|
||||
$quantity = is_array($cartItem) ? ($cartItem['quantity'] ?? 1) : $cartItem;
|
||||
$stockCost = 0;
|
||||
$stock = null;
|
||||
|
||||
// Get print stock if available
|
||||
if ($printStockId) {
|
||||
$stock = $product->printStocks()->find($printStockId);
|
||||
if ($stock) {
|
||||
$stockCost = ($type === 'wallpaper') ? $stock->cost_per_meter : $stock->cost_per_m2;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate price based on stock cost only (no base design cost)
|
||||
$basePrice = $stockCost;
|
||||
|
||||
if ($type === 'wallpaper') {
|
||||
$length = is_array($cartItem) ? ($cartItem['length'] ?? 0) : 0;
|
||||
$itemTotal = $basePrice * $length * $quantity;
|
||||
} elseif ($type === 'mural') {
|
||||
$width = is_array($cartItem) ? ($cartItem['width'] ?? 0) : 0;
|
||||
$height = is_array($cartItem) ? ($cartItem['height'] ?? 0) : 0;
|
||||
$m2 = $width * $height;
|
||||
$itemTotal = $basePrice * $m2 * $quantity;
|
||||
} else {
|
||||
$itemTotal = $basePrice * $quantity;
|
||||
}
|
||||
|
||||
$total += $itemTotal;
|
||||
$items[] = [
|
||||
'key' => $itemKey,
|
||||
'product' => $product,
|
||||
'stock' => $stock,
|
||||
'quantity' => $quantity,
|
||||
'type' => $type,
|
||||
'length' => is_array($cartItem) ? ($cartItem['length'] ?? null) : null,
|
||||
'width' => is_array($cartItem) ? ($cartItem['width'] ?? null) : null,
|
||||
'height' => is_array($cartItem) ? ($cartItem['height'] ?? null) : null,
|
||||
'subtotal' => $itemTotal
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return view('cart', [
|
||||
'items' => $items,
|
||||
'total' => $total,
|
||||
'itemCount' => count($cart)
|
||||
]);
|
||||
}
|
||||
|
||||
public function add(Request $request, Product $product)
|
||||
{
|
||||
$request->validate([
|
||||
'print_stock_id' => 'required|exists:print_stocks,id',
|
||||
'length' => $product->type === 'wallpaper' ? 'required|numeric|min:0.5' : 'nullable',
|
||||
'width' => $product->type === 'mural' ? 'required|numeric|min:0.5' : 'nullable',
|
||||
'height' => $product->type === 'mural' ? 'required|numeric|min:0.5' : 'nullable',
|
||||
]);
|
||||
|
||||
$cart = session()->get('cart', []);
|
||||
$cartItemKey = $product->id . '_' . $request->input('print_stock_id') . '_' . uniqid();
|
||||
|
||||
// Create a unique cart item with dimensions and stock
|
||||
$cartItem = [
|
||||
'product_id' => $product->id,
|
||||
'print_stock_id' => $request->input('print_stock_id'),
|
||||
'quantity' => 1,
|
||||
'type' => $product->type,
|
||||
];
|
||||
|
||||
if ($product->type === 'wallpaper') {
|
||||
$cartItem['length'] = $request->input('length');
|
||||
} elseif ($product->type === 'mural') {
|
||||
$cartItem['width'] = $request->input('width');
|
||||
$cartItem['height'] = $request->input('height');
|
||||
}
|
||||
|
||||
$cart[$cartItemKey] = $cartItem;
|
||||
session()->put('cart', $cart);
|
||||
|
||||
return redirect()->back()->with('success', $product->name . ' added to cart!');
|
||||
}
|
||||
|
||||
public function update(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'item_key' => 'required',
|
||||
'quantity' => 'required|integer|min:1'
|
||||
]);
|
||||
|
||||
$cart = session()->get('cart', []);
|
||||
$itemKey = $request->input('item_key');
|
||||
$quantity = $request->input('quantity');
|
||||
|
||||
if (isset($cart[$itemKey])) {
|
||||
$cart[$itemKey]['quantity'] = $quantity;
|
||||
}
|
||||
|
||||
session()->put('cart', $cart);
|
||||
|
||||
return redirect()->back()->with('success', 'Cart updated!');
|
||||
}
|
||||
|
||||
public function remove(Request $request)
|
||||
{
|
||||
$itemKey = $request->input('item_key');
|
||||
$cart = session()->get('cart', []);
|
||||
|
||||
if (isset($cart[$itemKey])) {
|
||||
unset($cart[$itemKey]);
|
||||
}
|
||||
|
||||
session()->put('cart', $cart);
|
||||
|
||||
return redirect()->back()->with('success', 'Product removed from cart!');
|
||||
}
|
||||
|
||||
public function clear()
|
||||
{
|
||||
session()->forget('cart');
|
||||
return redirect()->back()->with('success', 'Cart cleared!');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
abstract class Controller
|
||||
{
|
||||
//
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Product;
|
||||
|
||||
class FabricsController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$fabrics = Product::take(6)->get();
|
||||
|
||||
return view('fabrics', [
|
||||
'fabrics' => $fabrics
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Product;
|
||||
use App\Models\Category;
|
||||
use App\Models\HeroImage;
|
||||
|
||||
class HomeController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$categories = Category::all();
|
||||
$featuredWallpapers = Product::where('type', 'wallpaper')->where('featured', true)->take(3)->get();
|
||||
$featuredMurals = Product::where('type', 'mural')->where('featured', true)->take(3)->get();
|
||||
$heroImages = HeroImage::where('page', 'home')->where('is_active', true)->orderBy('sort_order')->get();
|
||||
|
||||
return view('home', [
|
||||
'categories' => $categories,
|
||||
'featuredWallpapers' => $featuredWallpapers,
|
||||
'featuredMurals' => $featuredMurals,
|
||||
'heroImages' => $heroImages
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Product;
|
||||
use App\Models\Category;
|
||||
use App\Models\HeroImage;
|
||||
|
||||
class MuralsController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$products = Product::where('type', 'mural')->get();
|
||||
$categories = Category::all();
|
||||
$heroImages = HeroImage::where('page', 'mural')->where('is_active', true)->orderBy('sort_order')->get();
|
||||
|
||||
return view('murals', [
|
||||
'products' => $products,
|
||||
'categories' => $categories,
|
||||
'heroImages' => $heroImages
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,373 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Order;
|
||||
use App\Models\OrderItem;
|
||||
use App\Models\Product;
|
||||
use App\Models\PrintStock;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class OrderController extends Controller
|
||||
{
|
||||
public function checkout()
|
||||
{
|
||||
$cart = session()->get('cart', []);
|
||||
|
||||
if (empty($cart)) {
|
||||
return redirect()->route('cart')->with('error', 'Your cart is empty!');
|
||||
}
|
||||
|
||||
$items = [];
|
||||
$total = 0;
|
||||
|
||||
foreach ($cart as $itemKey => $cartItem) {
|
||||
// Handle both old and new cart formats
|
||||
if (is_array($cartItem)) {
|
||||
$productId = $cartItem['product_id'] ?? null;
|
||||
$type = $cartItem['type'] ?? 'wallpaper';
|
||||
$printStockId = $cartItem['print_stock_id'] ?? null;
|
||||
} else {
|
||||
$productId = $itemKey;
|
||||
$type = 'wallpaper';
|
||||
$printStockId = null;
|
||||
}
|
||||
|
||||
if ($productId) {
|
||||
$product = Product::find($productId);
|
||||
if ($product) {
|
||||
$quantity = is_array($cartItem) ? ($cartItem['quantity'] ?? 1) : $cartItem;
|
||||
$stockCost = 0;
|
||||
$stock = null;
|
||||
|
||||
// Get print stock if available
|
||||
if ($printStockId) {
|
||||
$stock = $product->printStocks()->find($printStockId);
|
||||
if ($stock) {
|
||||
$stockCost = ($type === 'wallpaper') ? $stock->cost_per_meter : $stock->cost_per_m2;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate price based on stock cost only (no base design cost)
|
||||
$basePrice = $stockCost;
|
||||
|
||||
if ($type === 'wallpaper') {
|
||||
$length = is_array($cartItem) ? ($cartItem['length'] ?? 0) : 0;
|
||||
$subtotal = $basePrice * $length * $quantity;
|
||||
} elseif ($type === 'mural') {
|
||||
$width = is_array($cartItem) ? ($cartItem['width'] ?? 0) : 0;
|
||||
$height = is_array($cartItem) ? ($cartItem['height'] ?? 0) : 0;
|
||||
$m2 = $width * $height;
|
||||
$subtotal = $basePrice * $m2 * $quantity;
|
||||
} else {
|
||||
$subtotal = $basePrice * $quantity;
|
||||
}
|
||||
|
||||
$total += $subtotal;
|
||||
$items[] = [
|
||||
'key' => $itemKey,
|
||||
'product' => $product,
|
||||
'stock' => $stock,
|
||||
'quantity' => $quantity,
|
||||
'type' => $type,
|
||||
'length' => is_array($cartItem) ? ($cartItem['length'] ?? null) : null,
|
||||
'width' => is_array($cartItem) ? ($cartItem['width'] ?? null) : null,
|
||||
'height' => is_array($cartItem) ? ($cartItem['height'] ?? null) : null,
|
||||
'subtotal' => $subtotal
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return view('checkout', [
|
||||
'items' => $items,
|
||||
'total' => $total,
|
||||
'itemCount' => count($cart)
|
||||
]);
|
||||
}
|
||||
|
||||
public function process(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'customer_name' => 'required|string|max:255',
|
||||
'customer_email' => 'required|email',
|
||||
'customer_phone' => 'required|string|max:20',
|
||||
'shipping_address' => 'required|string|max:500',
|
||||
'notes' => 'nullable|string|max:500'
|
||||
]);
|
||||
|
||||
$cart = session()->get('cart', []);
|
||||
|
||||
if (empty($cart)) {
|
||||
return redirect()->route('cart')->with('error', 'Your cart is empty!');
|
||||
}
|
||||
|
||||
// Calculate total and validate stock
|
||||
$total = 0;
|
||||
$orderItems = [];
|
||||
|
||||
foreach ($cart as $itemKey => $cartItem) {
|
||||
// Handle both old and new cart formats
|
||||
if (is_array($cartItem)) {
|
||||
$productId = $cartItem['product_id'] ?? null;
|
||||
$quantity = $cartItem['quantity'] ?? 1;
|
||||
$type = $cartItem['type'] ?? 'wallpaper';
|
||||
$printStockId = $cartItem['print_stock_id'] ?? null;
|
||||
} else {
|
||||
$productId = $itemKey;
|
||||
$quantity = $cartItem;
|
||||
$type = 'wallpaper';
|
||||
$printStockId = null;
|
||||
}
|
||||
|
||||
$product = Product::find($productId);
|
||||
if (!$product) {
|
||||
return redirect()->route('cart')->with('error', 'Product not found!');
|
||||
}
|
||||
|
||||
if ($product->stock < $quantity) {
|
||||
return redirect()->route('cart')->with('error', "Insufficient stock for {$product->name}");
|
||||
}
|
||||
|
||||
$stockCost = 0;
|
||||
$stock = null;
|
||||
|
||||
// Get print stock if available
|
||||
if ($printStockId) {
|
||||
$stock = $product->printStocks()->find($printStockId);
|
||||
if ($stock) {
|
||||
$stockCost = ($type === 'wallpaper') ? $stock->cost_per_meter : $stock->cost_per_m2;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate price based on stock cost only (no base design cost)
|
||||
$basePrice = $stockCost;
|
||||
|
||||
if ($type === 'wallpaper') {
|
||||
$length = is_array($cartItem) ? ($cartItem['length'] ?? 0) : 0;
|
||||
$subtotal = $basePrice * $length * $quantity;
|
||||
} elseif ($type === 'mural') {
|
||||
$width = is_array($cartItem) ? ($cartItem['width'] ?? 0) : 0;
|
||||
$height = is_array($cartItem) ? ($cartItem['height'] ?? 0) : 0;
|
||||
$m2 = $width * $height;
|
||||
$subtotal = $basePrice * $m2 * $quantity;
|
||||
} else {
|
||||
$subtotal = $basePrice * $quantity;
|
||||
}
|
||||
|
||||
$total += $subtotal;
|
||||
$orderItems[$itemKey] = [
|
||||
'product_id' => $productId,
|
||||
'quantity' => $quantity,
|
||||
'price' => $product->price,
|
||||
'stock_cost' => $stockCost,
|
||||
'print_stock_id' => $printStockId,
|
||||
'type' => $type,
|
||||
'length' => is_array($cartItem) ? ($cartItem['length'] ?? null) : null,
|
||||
'width' => is_array($cartItem) ? ($cartItem['width'] ?? null) : null,
|
||||
'height' => is_array($cartItem) ? ($cartItem['height'] ?? null) : null,
|
||||
'subtotal' => $subtotal
|
||||
];
|
||||
}
|
||||
|
||||
// Create order
|
||||
$orderNumber = 'ORD-' . date('Ymd') . '-' . strtoupper(uniqid());
|
||||
|
||||
$orderData = [
|
||||
'user_id' => auth()->check() ? auth()->id() : null,
|
||||
'order_number' => $orderNumber,
|
||||
'total' => $total,
|
||||
'status' => 'pending',
|
||||
'payment_method' => 'yoco',
|
||||
'payment_status' => 'pending',
|
||||
'customer_name' => $request->input('customer_name'),
|
||||
'customer_email' => $request->input('customer_email'),
|
||||
'customer_phone' => $request->input('customer_phone'),
|
||||
'shipping_address' => $request->input('shipping_address'),
|
||||
];
|
||||
|
||||
if ($request->filled('notes')) {
|
||||
$orderData['notes'] = $request->input('notes');
|
||||
}
|
||||
|
||||
$order = Order::create($orderData);
|
||||
|
||||
// Create order items
|
||||
foreach ($orderItems as $itemKey => $data) {
|
||||
$product = Product::find($data['product_id']);
|
||||
|
||||
OrderItem::create([
|
||||
'order_id' => $order->uuid,
|
||||
'product_id' => $data['product_id'],
|
||||
'quantity' => $data['quantity'],
|
||||
'price' => $data['price'],
|
||||
'print_stock_id' => $data['print_stock_id'],
|
||||
'type' => $data['type'],
|
||||
'length' => $data['length'],
|
||||
'width' => $data['width'],
|
||||
'height' => $data['height']
|
||||
]);
|
||||
}
|
||||
|
||||
// Store order UUID in session for payment
|
||||
session(['pending_order_uuid' => $order->uuid]);
|
||||
|
||||
// Redirect to Yoco payment
|
||||
return redirect()->route('yoco-payment', ['order' => $order->uuid]);
|
||||
}
|
||||
|
||||
public function success(Order $order)
|
||||
{
|
||||
// Authorization: only allow viewing own orders or admin
|
||||
if (auth()->check() && auth()->user()->id !== $order->user_id && !auth()->user()->is_admin) {
|
||||
abort(403, 'Unauthorized access to this order.');
|
||||
}
|
||||
|
||||
// For guest orders, verify via session
|
||||
if (!auth()->check() && session('pending_order_uuid') !== $order->uuid) {
|
||||
abort(403, 'Unauthorized access to this order.');
|
||||
}
|
||||
|
||||
return view('order-success', ['order' => $order]);
|
||||
}
|
||||
|
||||
public function history()
|
||||
{
|
||||
$orders = Order::orderBy('created_at', 'desc')->get();
|
||||
|
||||
return view('order-history', ['orders' => $orders]);
|
||||
}
|
||||
|
||||
public function yocoPayment(Order $order)
|
||||
{
|
||||
// Verify this is a pending payment
|
||||
if ($order->payment_status !== 'pending') {
|
||||
abort(400, 'This order has already been paid.');
|
||||
}
|
||||
|
||||
// Create Yoco checkout
|
||||
$secretKey = config('services.yoco.secret_key');
|
||||
$mode = config('services.yoco.mode');
|
||||
|
||||
// Check if API key is configured
|
||||
if (empty($secretKey) || $secretKey === 'sk_test_your_key_here') {
|
||||
return redirect()->route('checkout')->with('error', 'Payment gateway not configured. Please contact support.');
|
||||
}
|
||||
|
||||
$baseUrl = $mode === 'live'
|
||||
? 'https://payments.yoco.com/api/checkouts'
|
||||
: 'https://payments.yoco.com/api/checkouts';
|
||||
|
||||
$checkoutData = [
|
||||
'amount' => (int)($order->total * 100), // Amount in cents
|
||||
'currency' => 'ZAR',
|
||||
'successUrl' => route('yoco-success', ['order' => $order->uuid]),
|
||||
'cancelUrl' => route('yoco-cancel', ['order' => $order->uuid]),
|
||||
'failureUrl' => route('yoco-failure', ['order' => $order->uuid]),
|
||||
'metadata' => [
|
||||
'order_uuid' => $order->uuid,
|
||||
'order_number' => $order->order_number,
|
||||
],
|
||||
];
|
||||
|
||||
try {
|
||||
$response = \Illuminate\Support\Facades\Http::withHeaders([
|
||||
'Authorization' => 'Bearer ' . $secretKey,
|
||||
'Content-Type' => 'application/json',
|
||||
])->post($baseUrl, $checkoutData);
|
||||
|
||||
if ($response->successful()) {
|
||||
$checkout = $response->json();
|
||||
return redirect($checkout['redirectUrl']);
|
||||
} else {
|
||||
\Log::error('Yoco API Error', [
|
||||
'status' => $response->status(),
|
||||
'body' => $response->body()
|
||||
]);
|
||||
return redirect()->route('checkout')->with('error', 'Unable to initialize payment: ' . $response->body());
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
\Log::error('Yoco Payment Exception', ['message' => $e->getMessage()]);
|
||||
return redirect()->route('checkout')->with('error', 'Payment error: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function yocoSuccess(Order $order)
|
||||
{
|
||||
// Verify order is pending payment
|
||||
if ($order->payment_status === 'paid') {
|
||||
return redirect()->route('order-success', ['order' => $order])
|
||||
->with('success', 'Payment was already processed for this order.');
|
||||
}
|
||||
|
||||
// Update order status
|
||||
$order->update([
|
||||
'payment_status' => 'paid',
|
||||
'status' => 'processing',
|
||||
]);
|
||||
|
||||
// Reduce stock
|
||||
foreach ($order->items as $item) {
|
||||
$product = $item->product;
|
||||
$product->stock -= $item->quantity;
|
||||
$product->save();
|
||||
}
|
||||
|
||||
// Clear pending order from session
|
||||
session()->forget('pending_order_uuid');
|
||||
session()->forget('cart');
|
||||
|
||||
return redirect()->route('order-success', ['order' => $order])
|
||||
->with('success', 'Payment successful! Your order has been confirmed.');
|
||||
}
|
||||
|
||||
public function yocoCancel(Order $order)
|
||||
{
|
||||
return redirect()->route('checkout')
|
||||
->with('error', 'Payment was cancelled. Your order is still pending.');
|
||||
}
|
||||
|
||||
public function yocoFailure(Order $order)
|
||||
{
|
||||
$order->update([
|
||||
'payment_status' => 'failed',
|
||||
]);
|
||||
|
||||
return redirect()->route('checkout')
|
||||
->with('error', 'Payment failed. Please try again or use a different payment method.');
|
||||
}
|
||||
|
||||
public function yocoWebhook(Request $request)
|
||||
{
|
||||
// Verify webhook signature
|
||||
$payload = $request->getContent();
|
||||
$signature = $request->header('X-Yoco-Signature');
|
||||
|
||||
// Process webhook event
|
||||
$event = $request->all();
|
||||
|
||||
if (isset($event['type']) && $event['type'] === 'checkout.succeeded') {
|
||||
$metadata = $event['payload']['metadata'] ?? [];
|
||||
$orderId = $metadata['order_id'] ?? null;
|
||||
|
||||
if ($orderId) {
|
||||
$order = Order::find($orderId);
|
||||
if ($order && $order->payment_status !== 'paid') {
|
||||
$order->update([
|
||||
'payment_status' => 'paid',
|
||||
'status' => 'processing',
|
||||
]);
|
||||
|
||||
// Reduce stock
|
||||
foreach ($order->items as $item) {
|
||||
$product = $item->product;
|
||||
$product->stock -= $item->quantity;
|
||||
$product->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return response()->json(['status' => 'success']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Product;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ProductController extends Controller
|
||||
{
|
||||
public function show(Product $product)
|
||||
{
|
||||
return view('product-detail', ['product' => $product]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Product;
|
||||
use App\Models\Category;
|
||||
use App\Models\HeroImage;
|
||||
|
||||
class WallpapersController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$products = Product::where('type', 'wallpaper')->get();
|
||||
$categories = Category::all();
|
||||
$heroImages = HeroImage::where('page', 'wallpaper')->where('is_active', true)->orderBy('sort_order')->get();
|
||||
|
||||
return view('wallpapers', [
|
||||
'products' => $products,
|
||||
'categories' => $categories,
|
||||
'heroImages' => $heroImages
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class Category extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'slug',
|
||||
'description',
|
||||
'image'
|
||||
];
|
||||
|
||||
protected static function boot()
|
||||
{
|
||||
parent::boot();
|
||||
|
||||
static::creating(function ($category) {
|
||||
if (empty($category->slug)) {
|
||||
$category->slug = Str::slug($category->name);
|
||||
}
|
||||
});
|
||||
|
||||
static::updating(function ($category) {
|
||||
if ($category->isDirty('name') && !$category->isDirty('slug')) {
|
||||
$category->slug = Str::slug($category->name);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function products()
|
||||
{
|
||||
return $this->hasMany(Product::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class HeroImage extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'page',
|
||||
'image_path',
|
||||
'title',
|
||||
'description',
|
||||
'button_text',
|
||||
'button_link',
|
||||
'sort_order',
|
||||
'is_active',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'is_active' => 'boolean',
|
||||
'sort_order' => 'integer',
|
||||
];
|
||||
|
||||
public static function getPages()
|
||||
{
|
||||
return [
|
||||
'home' => 'Home Page',
|
||||
'wallpaper' => 'Wallpaper Product Page',
|
||||
'mural' => 'Mural Product Page',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
||||
|
||||
class Order extends Model
|
||||
{
|
||||
use HasUuids;
|
||||
|
||||
protected $primaryKey = 'uuid';
|
||||
protected $keyType = 'string';
|
||||
public $incrementing = false;
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'order_number',
|
||||
'total',
|
||||
'status',
|
||||
'payment_method',
|
||||
'payment_status',
|
||||
'customer_name',
|
||||
'customer_email',
|
||||
'customer_phone',
|
||||
'shipping_address',
|
||||
'notes'
|
||||
];
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function items()
|
||||
{
|
||||
return $this->hasMany(OrderItem::class, 'order_id', 'uuid');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class OrderItem extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'order_id',
|
||||
'product_id',
|
||||
'print_stock_id',
|
||||
'quantity',
|
||||
'price',
|
||||
'type',
|
||||
'length',
|
||||
'width',
|
||||
'height'
|
||||
];
|
||||
|
||||
public function order()
|
||||
{
|
||||
return $this->belongsTo(Order::class);
|
||||
}
|
||||
|
||||
public function product()
|
||||
{
|
||||
return $this->belongsTo(Product::class);
|
||||
}
|
||||
|
||||
public function printStock()
|
||||
{
|
||||
return $this->belongsTo(PrintStock::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class PrintStock extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'description',
|
||||
'cost_per_meter',
|
||||
'cost_per_m2',
|
||||
'type',
|
||||
'width'
|
||||
];
|
||||
|
||||
public function products()
|
||||
{
|
||||
return $this->belongsToMany(Product::class, 'product_print_stocks');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class Product extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'category_id',
|
||||
'name',
|
||||
'slug',
|
||||
'description',
|
||||
'price',
|
||||
'image',
|
||||
'stock',
|
||||
'featured',
|
||||
'type'
|
||||
];
|
||||
|
||||
protected static function boot()
|
||||
{
|
||||
parent::boot();
|
||||
|
||||
static::creating(function ($model) {
|
||||
if (empty($model->slug)) {
|
||||
$model->slug = Str::slug($model->name);
|
||||
}
|
||||
});
|
||||
|
||||
static::updating(function ($model) {
|
||||
if (empty($model->slug)) {
|
||||
$model->slug = Str::slug($model->name);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function category()
|
||||
{
|
||||
return $this->belongsTo(Category::class);
|
||||
}
|
||||
|
||||
public function orderItems()
|
||||
{
|
||||
return $this->hasMany(OrderItem::class);
|
||||
}
|
||||
|
||||
public function printStocks()
|
||||
{
|
||||
return $this->belongsToMany(PrintStock::class, 'product_print_stocks');
|
||||
}
|
||||
|
||||
public function images()
|
||||
{
|
||||
return $this->hasMany(ProductImage::class)->orderBy('sort_order');
|
||||
}
|
||||
|
||||
public function getRouteKeyName()
|
||||
{
|
||||
return 'slug';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ProductImage extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'product_id',
|
||||
'image_path',
|
||||
'sort_order',
|
||||
];
|
||||
|
||||
public function product()
|
||||
{
|
||||
return $this->belongsTo(Product::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
|
||||
class User extends Authenticatable
|
||||
{
|
||||
/** @use HasFactory<\Database\Factories\UserFactory> */
|
||||
use HasFactory, Notifiable;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'email',
|
||||
'password',
|
||||
'is_admin',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be hidden for serialization.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $hidden = [
|
||||
'password',
|
||||
'remember_token',
|
||||
];
|
||||
|
||||
/**
|
||||
* 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,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\Facades\URL;
|
||||
use Illuminate\Support\Facades\View;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
if (config('app.env') === 'production' || request()->server('HTTP_X_FORWARDED_PROTO') === 'https') {
|
||||
URL::forceScheme('https');
|
||||
}
|
||||
|
||||
// Share cart count with all views
|
||||
View::composer('*', function ($view) {
|
||||
$cartCount = session('cart') ? count(session('cart')) : 0;
|
||||
$view->with('cartCount', $cartCount);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers\Filament;
|
||||
|
||||
use Filament\Http\Middleware\Authenticate;
|
||||
use Filament\Http\Middleware\DisableBladeIconComponents;
|
||||
use Filament\Http\Middleware\DispatchServingFilamentEvent;
|
||||
use Filament\Pages;
|
||||
use Filament\Panel;
|
||||
use Filament\PanelProvider;
|
||||
use Filament\Support\Colors\Color;
|
||||
use Filament\Widgets;
|
||||
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
|
||||
use Illuminate\Cookie\Middleware\EncryptCookies;
|
||||
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
|
||||
use Illuminate\Routing\Middleware\SubstituteBindings;
|
||||
use Illuminate\Session\Middleware\AuthenticateSession;
|
||||
use Illuminate\Session\Middleware\StartSession;
|
||||
use Illuminate\View\Middleware\ShareErrorsFromSession;
|
||||
|
||||
class AdminPanelProvider extends PanelProvider
|
||||
{
|
||||
public function panel(Panel $panel): Panel
|
||||
{
|
||||
return $panel
|
||||
->default()
|
||||
->id('admin')
|
||||
->path('admin')
|
||||
->login()
|
||||
->colors([
|
||||
'primary' => Color::Amber,
|
||||
])
|
||||
->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources')
|
||||
->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages')
|
||||
->pages([
|
||||
Pages\Dashboard::class,
|
||||
])
|
||||
->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets')
|
||||
->widgets([
|
||||
Widgets\AccountWidget::class,
|
||||
Widgets\FilamentInfoWidget::class,
|
||||
])
|
||||
->middleware([
|
||||
EncryptCookies::class,
|
||||
AddQueuedCookiesToResponse::class,
|
||||
StartSession::class,
|
||||
AuthenticateSession::class,
|
||||
ShareErrorsFromSession::class,
|
||||
VerifyCsrfToken::class,
|
||||
SubstituteBindings::class,
|
||||
DisableBladeIconComponents::class,
|
||||
DispatchServingFilamentEvent::class,
|
||||
])
|
||||
->authMiddleware([
|
||||
Authenticate::class,
|
||||
])
|
||||
->authGuard('web');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user