New Initial Commit

This commit is contained in:
twotalesanimation
2025-12-09 12:04:54 +02:00
commit 6de01c13c5
350 changed files with 42032 additions and 0 deletions
@@ -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'),
];
}
}
+141
View File
@@ -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(),
];
}
}
+128
View File
@@ -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(),
];
}
}
+62
View File
@@ -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';
}
}
+69
View File
@@ -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,
],
],
];
}
}
+75
View File
@@ -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'),
];
}
}
+154
View File
@@ -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!');
}
}
+8
View File
@@ -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
]);
}
}
+26
View File
@@ -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
]);
}
}
+24
View File
@@ -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
]);
}
}
+373
View File
@@ -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
]);
}
}
+38
View File
@@ -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);
}
}
+33
View File
@@ -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',
];
}
}
+39
View File
@@ -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');
}
}
+35
View File
@@ -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);
}
}
+22
View File
@@ -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');
}
}
+63
View File
@@ -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';
}
}
+19
View File
@@ -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);
}
}
+49
View File
@@ -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',
];
}
}
+34
View File
@@ -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');
}
}