Shopper Documentation

repository·2.x·Indexed 22 days ago

https://github.com/shopperlabs/shopper

A headless e-commerce admin panel built for Laravel 11.0+. Shopper provides a robust foundation for online store applications, featuring a customizable sidebar system with Livewire and Blade components, an Alpine.js state management store, and TypeScript definitions via the @shopper/types package derived from its OpenAPI Spec.

Tokens
11.9K
Snippets
34
Records
46
Agent score
79%

What's inside Shopper

  1. How the Swappable Model Pattern works

    2.x

    Shopper uses a 'Swappable Model Pattern' that allows you to replace core Shopper models with your own custom implementations. This is achieved by mapping model keys to specific classes in the config/shopper/models.php configuration file.

    To ensure compatibility when swapping models, your custom model must:

    1. Implement the corresponding Shopper Model Contract.
    2. Use the HasModelContract trait.
    3. Implement the configuredClass() method to return the configured class from the config file.

    When you swap a model, all internal Shopper logic that resolves models via the contract or the configuredClass() method will automatically use your custom implementation.

  2. Understand the Shopper Monorepo Structure

    2.x

    The Shopper project is organized into several specialized packages. Knowing which namespace to use is critical for locating logic and defining new components:

    PackageNamespacePurpose
    packages/adminShopper\Livewire components, views, routes, assets
    packages/coreShopper\Core\Models, Actions, Enums, Contracts
    packages/cartShopper\Cart\Cart management, pipeline-based calculation
    packages/paymentShopper\Payment\Payment processing, driver architecture
    packages/shippingShopper\Shipping\Shipping providers, driver architecture
    packages/sidebarShopper\Sidebar\Sidebar navigation builder
    packages/stripeShopper\Stripe\Stripe payment driver
    packages/typesTypeScript type definitions (NPM package)
    packages/upgradeUpgrade utilities
  3. Configure Sidebar CSS Variables and Attributes

    2.x

    Use the provided helper functions to inject sidebar dimensions and attributes into your layout's CSS and HTML. This ensures your custom CSS stays in sync with the package configuration.

    <style>
        :root {
            --sidebar-width: {{ \Shopper\Sidebar\sidebar_width() }};
            --sidebar-collapsed-width: {{ \Shopper\Sidebar\sidebar_collapsed_width() }};
        }
    </style>
    
    <body
        data-sidebar-breakpoint="{{ \Shopper\Sidebar\sidebar_breakpoint() }}"
        data-sidebar-collapsible="{{ \Shopper\Sidebar\sidebar_is_collapsible() ? 'true' : 'false' }}"
    >
  4. Create a custom Swappable Model

    2.x

    To create a custom model that replaces a core Shopper model, follow these three steps:

    1. Implement the Contract

    Your model must implement the interface defined in Shopper\Core\Models\Contracts\{ModelName}.

    2. Define the Model Class

    Create your class, implement the contract, and include the following:

    • Use the HasModelContract trait.
    • Implement configuredClass() to return the value from config('shopper.models.{key}', static::class).
    • Use config() for all relationships to ensure they point to the swapped models.

    3. Register the Model

    Add your new class to config/shopper/models.php using the appropriate key.

    Example Implementation:

    /**
     * @property-read int $id
     * @property-read string $name
     * @property-read bool $is_default
     */
    class Warehouse extends Model implements WarehouseContract
    {
        use HasFactory;
        use HasModelContract;
    
        protected $guarded = [];
    
        public static function configuredClass(): string
        {
            return config('shopper.models.warehouse', static::class);
        }
    
        public function getTable(): string
        {
            return shopper_table('warehouses');
        }
    
        public function isDefault(): bool
        {
            return $this->is_default;
        }
    
        /**
         * @return HasMany<Inventory, $this>
         */
        public function inventories(): HasMany
        {
            return $this->hasMany(config('shopper.models.inventory'), 'warehouse_id');
        }
    
        protected function casts(): array
        {
            return ['is_default' => 'boolean'];
        }
    }

    Registration in config/shopper/models.php:

    'warehouse' => Models\Warehouse::class,
    // 1. Contract
    interface Warehouse
    {
        public function isDefault(): bool;
        public function inventories(): HasMany;
    }
    
    // 2. Model
    class Warehouse extends Model implements WarehouseContract
    {
        use HasFactory;
        use HasModelContract;
    
        protected $guarded = [];
    
        public static function configuredClass(): string
        {
            return config('shopper.models.warehouse', static::class);
        }
    
        public function getTable(): string
        {
            return shopper_table('warehouses');
        }
    
        public function isDefault(): bool
        {
            return $this->is_default;
        }
    
        /**
         * @return HasMany<Inventory, $this>
         */
        public function inventories(): HasMany
        {
            return $this->hasMany(config('shopper.models.inventory'), 'warehouse_id');
        }
    
        protected function casts(): array
        {
            return ['is_default' => 'boolean'];
        }
    }
    
    // 3. Register
    'warehouse' => Models\Warehouse::class,
  5. Install Shopper via Composer and Artisan

    2.x

    To install the Shopper framework in your Laravel project, first require the package via Composer, then run the Shopper installation command using Artisan. This setup is designed for Laravel 11.0+.

    composer require shopper/framework
    php artisan shopper:install
  6. Create Migrations using Shopper Helpers

    2.x

    When creating migrations, extend \Shopper\Core\Helpers\Migration. This provides helper methods to add common Shopper fields like SEO, shipping, and soft deletes automatically.

    return new class extends \Shopper\Core\Helpers\Migration
    {
        public function up(): void
        {
            Schema::create($this->getTableName('products'), function (Blueprint $table): void {
                $this->addCommonFields($table, hasSoftDelete: true);
                $this->addSeoFields($table);
                $this->addShippingFields($table);
                $this->addForeignKey($table, 'brand_id', $this->getTableName('brands'));
            });
        }
    };
  7. Override Shopper components and pages

    2.x

    You can override core Shopper pages or specific components by defining custom Livewire classes in configuration files located in config/shopper/components/.

    1. Configure Overrides: Create a file like config/shopper/components/product.php to map core keys to your custom classes.

    2. Extend Base Components: When creating your custom class, extend the original Shopper Livewire component to maintain core functionality while adding your own logic (e.g., adding columns to a table).

    // config/shopper/components/product.php
    return [
        'pages' => [
            'product-index' => App\Livewire\Shopper\Products\Index::class,
            'product-edit' => \Shopper\Livewire\Pages\Product\Edit::class,
        ],
        'components' => [
            'products.form.edit' => App\Livewire\Shopper\Products\EditForm::class,
        ],
    ];
    namespace App\Livewire\Shopper\Products;
    
    use Shopper\Livewire\Pages\Product\Index as BaseIndex;
    
    class Index extends BaseIndex
    {
        public function table(Table $table): Table
        {
            return parent::table($table)
                ->columns([
                    ...parent::table($table)->getColumns(),
                    TextColumn::make('custom_field'),
                ]);
        }
    }
  8. Register Sidebar Middleware

    2.x

    You must add the \Shopper\Sidebar\Middleware\ResolveSidebars::class middleware to your route group or global middleware stack to ensure sidebars are correctly resolved during the request lifecycle.

    // In a route group
    Route::middleware(['web', \Shopper\Sidebar\Middleware\ResolveSidebars::class])
        ->group(function () {
            // Your routes
        });
    
    // Or in bootstrap/app.php (Laravel 11+)
    ->withMiddleware(function (Middleware $middleware) {
        $middleware->appendToGroup('web', [
            \Shopper\Sidebar\Middleware\ResolveSidebars::class,
        ]);
    })
  9. Render Sidebar using Livewire or Blade

    2.x

    The package offers two rendering strategies:

    1. Livewire Component: A ready-to-use component with built-in Alpine.js state management, collapse functionality, and LocalStorage persistence.
    2. Blade Components: For full layout control, use the SidebarRenderer directly in your views. The rendered HTML is available via the $sidebar variable.
    {{-- Option 1: Livewire Component --}}
    @livewire('sidebar', [
        'sidebarClass' => \App\Sidebar\AdminSidebar::class,
        'class' => 'your-sidebar-classes',
        'collapsible' => true,
    ])
    
    {{-- Option 2: Blade Components --}}
    <aside
        class="sidebar"
        x-bind:class="{ 'sidebar-collapsed': $store.sidebar.isCollapsed }"
    >
        <nav class="sidebar-nav">
            {!! $sidebar !!}
        </nav>
    </aside>
  10. Resolve Shopper models via Contracts or Classes

    2.x

    When interacting with Shopper models, you can resolve them using their Contract (recommended for compatibility with the swapping pattern) or via the class name (where static calls are proxied to the configured class).

    Recommended approach (via Contract): Use the resolve() helper with the Contract interface to ensure you are always working with the currently configured model implementation.

    Alternative approach (via Class): You can call static methods directly on the base class, as these calls are proxied to the configured class.

    use Shopper\Core\Models\Contracts\Product as ProductContract;
    
    // Via contract (recommended)
    resolve(ProductContract::class)::query()->get();
    
    // Via class (static calls are proxied)
    Product::query()->get();
    Product::find(1);