diff --git a/.github/workflows/table_rate_shipping_tests.yml b/.github/workflows/table_rate_shipping_tests.yml
index dba54c38f1..2d80589c67 100644
--- a/.github/workflows/table_rate_shipping_tests.yml
+++ b/.github/workflows/table_rate_shipping_tests.yml
@@ -43,4 +43,4 @@ jobs:
           APP_ENV: testing
           DB_CONNECTION: testing
           DB_DATABASE: ":memory:"
-        run: vendor/bin/pest  --testsuite shipping --parallel
+        run: vendor/bin/pest  --testsuite shipping
diff --git a/composer.json b/composer.json
index 91a731ba04..e4ae1a1887 100644
--- a/composer.json
+++ b/composer.json
@@ -23,7 +23,7 @@
         "kalnoy/nestedset": "^6.0",
         "laravel/framework": "^10.0",
         "laravel/scout": "^10.0",
-        "leandrocfe/filament-apex-charts": "^3.0",
+        "leandrocfe/filament-apex-charts": "^3.1.2",
         "lukascivil/treewalker": "0.9.1",
         "marvinosswald/filament-input-select-affix": "^0.1.0",
         "php": "^8.1",
@@ -122,4 +122,4 @@
     },
     "minimum-stability": "dev",
     "prefer-stable": true
-}
\ No newline at end of file
+}
diff --git a/docs/admin/extending/pages.md b/docs/admin/extending/pages.md
index 6e31510c2a..037fd84c37 100644
--- a/docs/admin/extending/pages.md
+++ b/docs/admin/extending/pages.md
@@ -31,9 +31,20 @@ use Filament\Actions;
 use Filament\Forms\Components\TextInput;
 use Filament\Forms\Form;
 use Lunar\Admin\Support\Extending\CreatePageExtension;
+use Lunar\Admin\Filament\Widgets;
 
 class MyCreateExtension extends CreatePageExtension
 {
+    public function headerWidgets(array $widgets): array
+    {
+        $widgets = [
+            ...$widgets,
+            Widgets\Dashboard\Orders\OrderStatsOverview::make(),
+        ];
+
+        return $widgets;
+    }
+
     public function headerActions(array $actions): array
     {
         $actions = [
@@ -54,6 +65,16 @@ class MyCreateExtension extends CreatePageExtension
         return $actions;
     }
 
+    public function footerWidgets(array $widgets): array
+    {
+        $widgets = [
+            ...$widgets,
+            Widgets\Dashboard\Orders\LatestOrdersTable::make(),
+        ];
+
+        return $widgets;
+    }
+
     public function beforeCreate(array $data): array
     {
         $data['model_code'] .= 'ABC';
@@ -85,9 +106,20 @@ use Filament\Actions;
 use Filament\Forms\Components\TextInput;
 use Filament\Forms\Form;
 use Lunar\Admin\Support\Extending\EditPageExtension;
+use Lunar\Admin\Filament\Widgets;
 
 class MyEditExtension extends EditPageExtension
 {
+    public function headerWidgets(array $widgets): array
+    {
+        $widgets = [
+            ...$widgets,
+            Widgets\Dashboard\Orders\OrderStatsOverview::make(),
+        ];
+
+        return $widgets;
+    }
+
     public function headerActions(array $actions): array
     {
         $actions = [
@@ -112,6 +144,16 @@ class MyEditExtension extends EditPageExtension
         return $actions;
     }
 
+     public function footerWidgets(array $widgets): array
+    {
+        $widgets = [
+            ...$widgets,
+            Widgets\Dashboard\Orders\LatestOrdersTable::make(),
+        ];
+
+        return $widgets;
+    }
+
     public function beforeFill(array $data): array
     {
         $data['model_code'] .= 'ABC';
@@ -153,9 +195,20 @@ use Filament\Actions;
 use Filament\Tables\Columns\TextColumn;
 use Filament\Tables\Table;
 use Lunar\Admin\Support\Extending\ListPageExtension;
+use Lunar\Admin\Filament\Widgets;
 
 class MyListExtension extends ListPageExtension
 {
+    public function headerWidgets(array $widgets): array
+    {
+        $widgets = [
+            ...$widgets,
+            Widgets\Dashboard\Orders\OrderStatsOverview::make(),
+        ];
+
+        return $widgets;
+    }
+
     public function headerActions(array $actions): array
     {
         $actions = [
@@ -169,13 +222,52 @@ class MyListExtension extends ListPageExtension
 
         return $actions;
     }
-  
+
+    public function footerWidgets(array $widgets): array
+    {
+        $widgets = [
+            ...$widgets,
+            Widgets\Dashboard\Orders\LatestOrdersTable::make(),
+        ];
+
+        return $widgets;
+    }
 }
 
 // Typically placed in your AppServiceProvider file...
 LunarPanel::registerExtension(new MyListExtension, \Lunar\Admin\Filament\Resources\ProductResource\Pages\ListProducts::class);
 ```
 
+## ViewPageExtension
+
+An example of extending a view page.
+
+```php
+use Filament\Actions;
+use Filament\Tables\Columns\TextColumn;
+use Filament\Tables\Table;
+use Lunar\Admin\Support\Extending\ViewPageExtension;
+
+class MyViewExtension extends ViewPageExtension
+{
+    public function headerActions(array $actions): array
+    {
+        $actions = [
+            ...$actions,
+            Actions\ActionGroup::make([
+                Actions\Action::make('Download PDF')
+            ])
+        ];
+
+        return $actions;
+    }
+  
+}
+
+// Typically placed in your AppServiceProvider file...
+LunarPanel::registerExtension(new MyViewExtension, \Lunar\Admin\Filament\Resources\OrderResource\Pages\ManageOrder::class);
+```
+
 ## Extending Pages In Addons
 
 If you are building an addon for Lunar, you may need to take a slightly different approach when modifying forms, etc.
diff --git a/docs/admin/extending/resources.md b/docs/admin/extending/resources.md
index eaf712a4a6..38b76c80dd 100644
--- a/docs/admin/extending/resources.md
+++ b/docs/admin/extending/resources.md
@@ -31,12 +31,10 @@ class MyProductResourceExtension extends \Lunar\Panel\Support\Extending\Resource
     
     public function extendTable(\Filament\Tables\Table $table): \Filament\Tables\Table
     {
-        $actions = [
+        return $table->columns([
             ...$table->getColumns(),
             \Filament\Tables\Columns\TextColumn::make('product_code')
-        ];
-
-        return $table;
+        ]);
     }
     
     public function getRelations(array $managers) : array
diff --git a/docs/core/reference/pricing.md b/docs/core/reference/pricing.md
index d7e74d8564..0c7d20c871 100644
--- a/docs/core/reference/pricing.md
+++ b/docs/core/reference/pricing.md
@@ -55,7 +55,7 @@ them, first we'll create a standard price model.
 $priceModel = \Lunar\Models\Price::create([
     // ...
     'price' => 1000, // Price is an int and should be in the lowest common denominator
-    'tier' => 1,
+    'quantity_break' => 1,
 ]);
 
 // Lunar\DataTypes\Price
diff --git a/docs/core/reference/products.md b/docs/core/reference/products.md
index 727ead25a7..599d08deef 100644
--- a/docs/core/reference/products.md
+++ b/docs/core/reference/products.md
@@ -440,7 +440,7 @@ front end.
 | `price`             | A integer value for the price                                                        | `null`  | yes      |
 | `compare_price`     | For display purposes, allows you to show a comparison price, e.g. RRP.               | `null`  | no       |
 | `currency_id`       | The ID of the related currency                                                       | `null`  | yes      |
-| `tier`              | The lower limit to get this price, 1 is the default for base pricing.                | `1`     | no       |
+| `quantity_break`    | The lower limit to get this price, 1 is the default for base pricing.                | `1`     | no       |
 | `customer_group_id` | The customer group this price relates to, leaving as `null` means any customer group | `null`  | no       |
 | `priceable_type`    | This is the class reference to the related model which owns the price                | `null`  | yes      |
 | `priceable_id`      | This is the id of the related model which owns the price                             | `null`  | yes      |
@@ -450,7 +450,7 @@ $price = \Lunar\Models\Price::create([
     'price' => 199,
     'compare_price' => 299,
     'currency_id' => 1,
-    'tier' => 1,
+    'quantity_break' => 1,
     'customer_group_id' => null,
     'priceable_type' => 'Lunar\Models\ProductVariant',
     'priceable_id' => 1,
@@ -476,7 +476,7 @@ relationship method.
     'price' => 199,
     'compare_price' => 299,
     'currency_id' => 1,
-    'tier' => 1,
+    'quantity_break' => 1,
     'customer_group_id' => null,
     'priceable_type' => 'Lunar\Models\ProductVariant',
     'priceable_id' => 1,
@@ -491,26 +491,26 @@ $variant->prices()->create([/* .. */]);
 
 You can specify which customer group the price applies to by setting the `customer_group_id` column. If left as `null`
 the price will apply to all customer groups. This is useful if you want to have different pricing for certain customer
-groups and also different price tiers per customer group.
+groups and also different price quantity breaks per customer group.
 
-### Tiered Pricing
+### Quantity Break Pricing
 
-Tiered pricing is a concept in which when you buy in bulk, the cost per item will change (usually go down). With Pricing
-on Lunar, this is determined by the `tier` column when creating prices. For example:
+Quantity Break pricing is a concept in which when you buy in bulk, the cost per item will change (usually go down). With Pricing
+on Lunar, this is determined by the `quantity_break` column when creating prices. For example:
 
 ```php
 Price::create([
     // ...
     'price' => 199,
     'compare_price' => 399,
-    'tier' => 1,
+    'quantity_break' => 1,
 ]);
 
 Price::create([
     // ...
     'price' => 150,
     'compare_price' => 399,
-    'tier' => 10,
+    'quantity_break' => 10,
 ]);
 ```
 
@@ -522,7 +522,7 @@ will pay `1.50` per item.
 Once you've got your pricing all set up, you're likely going to want to display it on your storefront. We've created
 a `PricingManager` which is available via a facade to make this process as painless as possible.
 
-To get the pricing for a product you can simple use the following helpers:
+To get the pricing for a product you can simply use the following helpers:
 
 #### Minimum example
 
@@ -605,9 +605,9 @@ $pricing->matched;
 $pricing->base;
 
 /**
- * A collection of all the price tiers available for the given criteria.
+ * A collection of all the price quantity breaks available for the given criteria.
  */
-$pricing->tiers;
+$pricing->quantityBreaks;
 
 /**
  * All customer group pricing available for the given criteria.
diff --git a/docs/core/upgrading.md b/docs/core/upgrading.md
index 91ac3e14f9..b879138ebc 100644
--- a/docs/core/upgrading.md
+++ b/docs/core/upgrading.md
@@ -27,6 +27,37 @@ The `lunar.media.conversions` configuration has been removed, in favour of regis
 Media definition classes allow you to register media collections, conversions and much more. See [Media Collections](/core/reference/media.html#media-collections)
 for further information.
 
+#### Product Options
+The `position` field has been removed from the `product_options` table and is now found on the `product_product_option` 
+pivot table. Any position data will be automatically adjusted when running migrations.
+
+#### Tiers renamed to Quantity Breaks
+
+The `tier` column on pricing has been renamed to `quantity_break`, any references in code to `tiers` needs to be updated.
+
+##### Price Model
+
+```php
+// Old
+$priceModel->tier
+// New
+$priceModel->quantity_break
+
+// Old
+$priceModel->tiers
+// New
+$priceModel->quantityBreaks
+```
+
+##### Lunar\Base\DataTransferObjects\PricingResponse
+
+```php
+// Old
+public Collection $tiered,
+// New
+public Collection $quantityBreaks,
+```
+
 ## 0.7
 
 ### High Impact
diff --git a/packages/admin/composer.json b/packages/admin/composer.json
index e728cf3af1..10316f504d 100644
--- a/packages/admin/composer.json
+++ b/packages/admin/composer.json
@@ -15,13 +15,13 @@
     "minimum-stability": "dev",
     "require": {
         "lunarphp/core": "self.version",
-        "filament/filament": "^3.1.23",
+        "filament/filament": "^3.2.25",
         "filament/spatie-laravel-media-library-plugin": "^3.0-stable",
         "spatie/laravel-permission": "^5.10",
         "barryvdh/laravel-dompdf": "^2.0",
         "technikermathe/blade-lucide-icons": "^v2.24.0",
         "marvinosswald/filament-input-select-affix": "^0.1.0",
-        "leandrocfe/filament-apex-charts": "^3.0",
+        "leandrocfe/filament-apex-charts": "^3.1.2",
         "awcodes/shout": "^2.0.2"
     },
     "extra": {
diff --git a/packages/admin/resources/css/index.css b/packages/admin/resources/css/index.css
index 29244ec365..b3949d56cd 100644
--- a/packages/admin/resources/css/index.css
+++ b/packages/admin/resources/css/index.css
@@ -1 +1 @@
-@import '../../vendor/filament/filament/resources/css/theme.css';
\ No newline at end of file
+@import '../../vendor/filament/filament/resources/css/theme.css';
diff --git a/packages/admin/resources/dist/lunar-panel.css b/packages/admin/resources/dist/lunar-panel.css
index e606e06e68..c9914e7c44 100644
--- a/packages/admin/resources/dist/lunar-panel.css
+++ b/packages/admin/resources/dist/lunar-panel.css
@@ -1 +1,2057 @@
-/*! tailwindcss v3.4.0 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border-width:0;border-style:solid;border-color:rgba(var(--gray-200),1)}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:var(--font-family),ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:rgba(var(--gray-400),1)}input::placeholder,textarea::placeholder{opacity:1;color:rgba(var(--gray-400),1)}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}[multiple],[type=date],[type=datetime-local],[type=email],[type=month],[type=number],[type=password],[type=search],[type=tel],[type=text],[type=time],[type=url],[type=week],input:where(:not([type])),select,textarea{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:rgba(var(--gray-500),var(--tw-border-opacity,1));border-width:1px;border-radius:0;padding:.5rem .75rem;font-size:1rem;line-height:1.5rem;--tw-shadow:0 0 #0000}[multiple]:focus,[type=date]:focus,[type=datetime-local]:focus,[type=email]:focus,[type=month]:focus,[type=number]:focus,[type=password]:focus,[type=search]:focus,[type=tel]:focus,[type=text]:focus,[type=time]:focus,[type=url]:focus,[type=week]:focus,input:where(:not([type])):focus,select:focus,textarea:focus{outline:2px solid #0000;outline-offset:2px;--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);border-color:#2563eb}input::-moz-placeholder,textarea::-moz-placeholder{color:rgba(var(--gray-500),var(--tw-text-opacity,1));opacity:1}input::placeholder,textarea::placeholder{color:rgba(var(--gray-500),var(--tw-text-opacity,1));opacity:1}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-date-and-time-value{min-height:1.5em;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-meridiem-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-year-field{padding-top:0;padding-bottom:0}select{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='rgba(var(--gray-500), var(--tw-stroke-opacity, 1))' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3E%3C/svg%3E");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem;-webkit-print-color-adjust:exact;print-color-adjust:exact}[multiple],[size]:where(select:not([size="1"])){background-image:none;background-position:0 0;background-repeat:unset;background-size:initial;padding-right:.75rem;-webkit-print-color-adjust:unset;print-color-adjust:unset}[type=checkbox],[type=radio]{-webkit-appearance:none;-moz-appearance:none;appearance:none;padding:0;-webkit-print-color-adjust:exact;print-color-adjust:exact;display:inline-block;vertical-align:middle;background-origin:border-box;-webkit-user-select:none;-moz-user-select:none;user-select:none;flex-shrink:0;height:1rem;width:1rem;color:#2563eb;background-color:#fff;border-color:rgba(var(--gray-500),var(--tw-border-opacity,1));border-width:1px;--tw-shadow:0 0 #0000}[type=checkbox]{border-radius:0}[type=radio]{border-radius:100%}[type=checkbox]:focus,[type=radio]:focus{outline:2px solid #0000;outline-offset:2px;--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:2px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}[type=checkbox]:checked,[type=radio]:checked{border-color:#0000;background-color:currentColor;background-size:100% 100%;background-position:50%;background-repeat:no-repeat}[type=checkbox]:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 16 16'%3E%3Cpath d='M12.207 4.793a1 1 0 0 1 0 1.414l-5 5a1 1 0 0 1-1.414 0l-2-2a1 1 0 0 1 1.414-1.414L6.5 9.086l4.293-4.293a1 1 0 0 1 1.414 0z'/%3E%3C/svg%3E")}@media (forced-colors:active){[type=checkbox]:checked{-webkit-appearance:auto;-moz-appearance:auto;appearance:auto}}[type=radio]:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='3'/%3E%3C/svg%3E")}@media (forced-colors:active){[type=radio]:checked{-webkit-appearance:auto;-moz-appearance:auto;appearance:auto}}[type=checkbox]:checked:focus,[type=checkbox]:checked:hover,[type=checkbox]:indeterminate,[type=radio]:checked:focus,[type=radio]:checked:hover{border-color:#0000;background-color:currentColor}[type=checkbox]:indeterminate{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3E%3Cpath stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3E%3C/svg%3E");background-size:100% 100%;background-position:50%;background-repeat:no-repeat}@media (forced-colors:active){[type=checkbox]:indeterminate{-webkit-appearance:auto;-moz-appearance:auto;appearance:auto}}[type=checkbox]:indeterminate:focus,[type=checkbox]:indeterminate:hover{border-color:#0000;background-color:currentColor}[type=file]{background:unset;border-color:inherit;border-width:0;border-radius:0;padding:0;font-size:unset;line-height:inherit}[type=file]:focus{outline:1px solid ButtonText;outline:1px auto -webkit-focus-ring-color}:root.dark{color-scheme:dark}[data-field-wrapper]{scroll-margin-top:8rem}*,::backdrop,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.-left-\[calc\(0\.5rem_-_1px\)\]{left:calc(-.5rem - -1px)}.-left-\[calc\(0\.75rem_-_1px\)\]{left:calc(-.75rem - -1px)}.left-0{left:0}.left-5{left:1.25rem}.right-0{right:0}.top-\[2px\]{top:2px}.-my-8{margin-top:-2rem;margin-bottom:-2rem}.-ml-\[5px\]{margin-left:-5px}.ml-2{margin-left:.5rem}.ml-3{margin-left:.75rem}.ml-4{margin-left:1rem}.ml-5{margin-left:1.25rem}.ml-8{margin-left:2rem}.mt-4{margin-top:1rem}.flow-root{display:flow-root}.w-1\/3{width:33.333333%}.w-\[2px\]{width:2px}.min-w-\[50vw\]{min-width:50vw}.min-w-full{min-width:100%}.flex-shrink-0,.shrink-0{flex-shrink:0}.grow{flex-grow:1}.-translate-y-2{--tw-translate-y:-0.5rem}.-translate-y-2,.-translate-y-px{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-y-px{--tw-translate-y:-1px}.\!cursor-default{cursor:default!important}.cursor-grab{cursor:grab}.scroll-mt-32{scroll-margin-top:8rem}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.gap-0{gap:0}.gap-0\.5{gap:.125rem}.gap-2\.5{gap:.625rem}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.5rem*var(--tw-space-x-reverse));margin-left:calc(.5rem*(1 - var(--tw-space-x-reverse)))}.divide-y-2>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0;border-top-width:calc(2px*(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(2px*var(--tw-divide-y-reverse))}.\!overflow-auto{overflow:auto!important}.rounded-b-lg{border-bottom-right-radius:.5rem;border-bottom-left-radius:.5rem}.border-green-300{--tw-border-opacity:1;border-color:rgb(134 239 172/var(--tw-border-opacity))}.border-orange-300{--tw-border-opacity:1;border-color:rgb(253 186 116/var(--tw-border-opacity))}.border-sky-300{--tw-border-opacity:1;border-color:rgb(125 211 252/var(--tw-border-opacity))}.border-white\/10{border-color:#ffffff1a}.bg-gray-300\/20{background-color:rgba(var(--gray-300),.2)}.bg-green-50{--tw-bg-opacity:1;background-color:rgb(240 253 244/var(--tw-bg-opacity))}.bg-orange-50{--tw-bg-opacity:1;background-color:rgb(255 247 237/var(--tw-bg-opacity))}.bg-purple-500{--tw-bg-opacity:1;background-color:rgb(168 85 247/var(--tw-bg-opacity))}.bg-sky-50{--tw-bg-opacity:1;background-color:rgb(240 249 255/var(--tw-bg-opacity))}.bg-sky-500{--tw-bg-opacity:1;background-color:rgb(14 165 233/var(--tw-bg-opacity))}.bg-teal-500{--tw-bg-opacity:1;background-color:rgb(20 184 166/var(--tw-bg-opacity))}.bg-white\/70{background-color:#ffffffb3}.\!p-0{padding:0!important}.\!p-3{padding:.75rem!important}.\!ps-6{padding-inline-start:1.5rem!important}.pl-2{padding-left:.5rem}.pl-8{padding-left:2rem}.pt-8{padding-top:2rem}.pt-\[1px\]{padding-top:1px}.pt-\[5px\]{padding-top:5px}.\!text-red-400\/60{color:#f8717199!important}.\!text-red-400\/80{color:#f87171cc!important}.text-gray-900{--tw-text-opacity:1;color:rgba(var(--gray-900),var(--tw-text-opacity))}.text-green-500{--tw-text-opacity:1;color:rgb(34 197 94/var(--tw-text-opacity))}.text-green-600{--tw-text-opacity:1;color:rgb(22 163 74/var(--tw-text-opacity))}.text-orange-500{--tw-text-opacity:1;color:rgb(249 115 22/var(--tw-text-opacity))}.text-orange-600{--tw-text-opacity:1;color:rgb(234 88 12/var(--tw-text-opacity))}.text-red-500{--tw-text-opacity:1;color:rgb(239 68 68/var(--tw-text-opacity))}.text-sky-600{--tw-text-opacity:1;color:rgb(2 132 199/var(--tw-text-opacity))}.shadow-md{--tw-shadow:0 4px 6px -1px #0000001a,0 2px 4px -2px #0000001a;--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color)}.shadow-md,.shadow-sm{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.ring-1,.ring-4{box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-gray-100{--tw-ring-opacity:1;--tw-ring-color:rgba(var(--gray-100),var(--tw-ring-opacity))}.ring-purple-100{--tw-ring-opacity:1;--tw-ring-color:rgb(243 232 255/var(--tw-ring-opacity))}.ring-sky-100{--tw-ring-opacity:1;--tw-ring-color:rgb(224 242 254/var(--tw-ring-opacity))}.ring-teal-100{--tw-ring-opacity:1;--tw-ring-color:rgb(204 251 241/var(--tw-ring-opacity))}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.hover\:cursor-pointer:hover{cursor:pointer}.hover\:bg-danger-100\/80:hover{background-color:rgba(var(--danger-100),.8)}.hover\:bg-primary-400\/10:hover{background-color:rgba(var(--primary-400),.1)}.hover\:bg-primary-50:hover{--tw-bg-opacity:1;background-color:rgba(var(--primary-50),var(--tw-bg-opacity))}.hover\:bg-primary-50\/50:hover{background-color:rgba(var(--primary-50),.5)}.focus\:outline-none:focus{outline:2px solid #0000;outline-offset:2px}.group:hover .group-hover\:flex{display:flex}.group:hover .group-hover\:scale-110{--tw-scale-x:1.1;--tw-scale-y:1.1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group\/button:hover .group-hover\/button\:text-gray-500{--tw-text-opacity:1;color:rgba(var(--gray-500),var(--tw-text-opacity))}.group:hover .group-hover\:text-green-600{--tw-text-opacity:1;color:rgb(22 163 74/var(--tw-text-opacity))}.group:hover .group-hover\:text-red-600{--tw-text-opacity:1;color:rgb(220 38 38/var(--tw-text-opacity))}@media (min-width:768px){.md\:min-w-\[32rem\]{min-width:32rem}.md\:justify-between{justify-content:space-between}}:is(:where([dir=ltr]) .ltr\:rotate-90){--tw-rotate:90deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}:is(:where([dir=rtl]) .rtl\:\!rotate-90){--tw-rotate:90deg!important;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))!important}:is(:where([dir=rtl]) .rtl\:rotate-180){--tw-rotate:180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}:is(:where([dir=rtl]) .rtl\:space-x-reverse)>:not([hidden])~:not([hidden]){--tw-space-x-reverse:1}:is(:where([dir=rtl]) .rtl\:text-right){text-align:right}:is(:where(.dark) .dark\:divide-gray-600)>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgba(var(--gray-600),var(--tw-divide-opacity))}:is(:where(.dark) .dark\:divide-white\/10)>:not([hidden])~:not([hidden]){border-color:#ffffff1a}:is(:where(.dark) .dark\:border-b){border-bottom-width:1px}:is(:where(.dark) .dark\:border-gray-600){--tw-border-opacity:1;border-color:rgba(var(--gray-600),var(--tw-border-opacity))}:is(:where(.dark) .dark\:border-white\/10){border-color:#ffffff1a}:is(:where(.dark) .dark\:bg-custom-400\/10){background-color:rgba(var(--c-400),.1)}:is(:where(.dark) .dark\:bg-gray-400\/10){background-color:rgba(var(--gray-400),.1)}:is(:where(.dark) .dark\:bg-gray-600){--tw-bg-opacity:1;background-color:rgba(var(--gray-600),var(--tw-bg-opacity))}:is(:where(.dark) .dark\:bg-gray-800){--tw-bg-opacity:1;background-color:rgba(var(--gray-800),var(--tw-bg-opacity))}:is(:where(.dark) .dark\:bg-gray-900){--tw-bg-opacity:1;background-color:rgba(var(--gray-900),var(--tw-bg-opacity))}:is(:where(.dark) .dark\:bg-green-400\/10){background-color:#4ade801a}:is(:where(.dark) .dark\:bg-orange-400\/10){background-color:#fb923c1a}:is(:where(.dark) .dark\:bg-sky-400\/10){background-color:#38bdf81a}:is(:where(.dark) .dark\:bg-white\/10){background-color:#ffffff1a}:is(:where(.dark) .dark\:bg-white\/5){background-color:#ffffff0d}:is(:where(.dark) .dark\:\!text-red-400\/60){color:#f8717199!important}:is(:where(.dark) .dark\:text-custom-400){--tw-text-opacity:1;color:rgba(var(--c-400),var(--tw-text-opacity))}:is(:where(.dark) .dark\:text-gray-100){--tw-text-opacity:1;color:rgba(var(--gray-100),var(--tw-text-opacity))}:is(:where(.dark) .dark\:text-gray-200){--tw-text-opacity:1;color:rgba(var(--gray-200),var(--tw-text-opacity))}:is(:where(.dark) .dark\:text-gray-300){--tw-text-opacity:1;color:rgba(var(--gray-300),var(--tw-text-opacity))}:is(:where(.dark) .dark\:text-gray-400){--tw-text-opacity:1;color:rgba(var(--gray-400),var(--tw-text-opacity))}:is(:where(.dark) .dark\:text-gray-500){--tw-text-opacity:1;color:rgba(var(--gray-500),var(--tw-text-opacity))}:is(:where(.dark) .dark\:text-green-400){--tw-text-opacity:1;color:rgb(74 222 128/var(--tw-text-opacity))}:is(:where(.dark) .dark\:text-green-400\/80){color:#4ade80cc}:is(:where(.dark) .dark\:text-orange-400){--tw-text-opacity:1;color:rgb(251 146 60/var(--tw-text-opacity))}:is(:where(.dark) .dark\:text-primary-400){--tw-text-opacity:1;color:rgba(var(--primary-400),var(--tw-text-opacity))}:is(:where(.dark) .dark\:text-primary-400\/80){color:rgba(var(--primary-400),.8)}:is(:where(.dark) .dark\:text-red-400\/80){color:#f87171cc}:is(:where(.dark) .dark\:text-sky-400){--tw-text-opacity:1;color:rgb(56 189 248/var(--tw-text-opacity))}:is(:where(.dark) .dark\:text-white){--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}:is(:where(.dark) .dark\:ring-custom-400\/30){--tw-ring-color:rgba(var(--c-400),0.3)}:is(:where(.dark) .dark\:ring-gray-400\/20){--tw-ring-color:rgba(var(--gray-400),0.2)}:is(:where(.dark) .dark\:ring-gray-600){--tw-ring-opacity:1;--tw-ring-color:rgba(var(--gray-600),var(--tw-ring-opacity))}:is(:where(.dark) .dark\:ring-gray-700){--tw-ring-opacity:1;--tw-ring-color:rgba(var(--gray-700),var(--tw-ring-opacity))}:is(:where(.dark) .dark\:ring-purple-800){--tw-ring-opacity:1;--tw-ring-color:rgb(107 33 168/var(--tw-ring-opacity))}:is(:where(.dark) .dark\:ring-sky-800){--tw-ring-opacity:1;--tw-ring-color:rgb(7 89 133/var(--tw-ring-opacity))}:is(:where(.dark) .dark\:ring-teal-800){--tw-ring-opacity:1;--tw-ring-color:rgb(17 94 89/var(--tw-ring-opacity))}:is(:where(.dark) .dark\:ring-white\/10){--tw-ring-color:#ffffff1a}:is(:where(.dark) .dark\:ring-white\/20){--tw-ring-color:#fff3}:is(:where(.dark) .dark\:hover\:bg-danger-300\/20:hover){background-color:rgba(var(--danger-300),.2)}:is(:where(.dark) .dark\:hover\:bg-white\/5:hover){background-color:#ffffff0d}:is(:where(.dark) .dark\:focus-visible\:ring-primary-500:focus-visible){--tw-ring-opacity:1;--tw-ring-color:rgba(var(--primary-500),var(--tw-ring-opacity))}:is(:where(.dark) .group\/button:hover .dark\:group-hover\/button\:text-gray-400){--tw-text-opacity:1;color:rgba(var(--gray-400),var(--tw-text-opacity))}:is(:where(.dark) .group:hover .dark\:group-hover\:text-green-400){--tw-text-opacity:1;color:rgb(74 222 128/var(--tw-text-opacity))}:is(:where(.dark) .group:hover .dark\:group-hover\:text-red-400){--tw-text-opacity:1;color:rgb(248 113 113/var(--tw-text-opacity))}.\[\&_table\]\:h-\[1px\] table{height:1px}
\ No newline at end of file
+/*
+! tailwindcss v3.4.1 | MIT License | https://tailwindcss.com
+*/
+
+/*
+1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
+2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
+*/
+
+*,
+::before,
+::after {
+  box-sizing: border-box;
+  /* 1 */
+  border-width: 0;
+  /* 2 */
+  border-style: solid;
+  /* 2 */
+  border-color: rgba(var(--gray-200), 1);
+  /* 2 */
+}
+
+::before,
+::after {
+  --tw-content: '';
+}
+
+/*
+1. Use a consistent sensible line-height in all browsers.
+2. Prevent adjustments of font size after orientation changes in iOS.
+3. Use a more readable tab size.
+4. Use the user's configured `sans` font-family by default.
+5. Use the user's configured `sans` font-feature-settings by default.
+6. Use the user's configured `sans` font-variation-settings by default.
+7. Disable tap highlights on iOS
+*/
+
+html,
+:host {
+  line-height: 1.5;
+  /* 1 */
+  -webkit-text-size-adjust: 100%;
+  /* 2 */
+  -moz-tab-size: 4;
+  /* 3 */
+  -o-tab-size: 4;
+     tab-size: 4;
+  /* 3 */
+  font-family: var(--font-family), ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+  /* 4 */
+  font-feature-settings: normal;
+  /* 5 */
+  font-variation-settings: normal;
+  /* 6 */
+  -webkit-tap-highlight-color: transparent;
+  /* 7 */
+}
+
+/*
+1. Remove the margin in all browsers.
+2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
+*/
+
+body {
+  margin: 0;
+  /* 1 */
+  line-height: inherit;
+  /* 2 */
+}
+
+/*
+1. Add the correct height in Firefox.
+2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
+3. Ensure horizontal rules are visible by default.
+*/
+
+hr {
+  height: 0;
+  /* 1 */
+  color: inherit;
+  /* 2 */
+  border-top-width: 1px;
+  /* 3 */
+}
+
+/*
+Add the correct text decoration in Chrome, Edge, and Safari.
+*/
+
+abbr:where([title]) {
+  -webkit-text-decoration: underline dotted;
+          text-decoration: underline dotted;
+}
+
+/*
+Remove the default font size and weight for headings.
+*/
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+  font-size: inherit;
+  font-weight: inherit;
+}
+
+/*
+Reset links to optimize for opt-in styling instead of opt-out.
+*/
+
+a {
+  color: inherit;
+  text-decoration: inherit;
+}
+
+/*
+Add the correct font weight in Edge and Safari.
+*/
+
+b,
+strong {
+  font-weight: bolder;
+}
+
+/*
+1. Use the user's configured `mono` font-family by default.
+2. Use the user's configured `mono` font-feature-settings by default.
+3. Use the user's configured `mono` font-variation-settings by default.
+4. Correct the odd `em` font sizing in all browsers.
+*/
+
+code,
+kbd,
+samp,
+pre {
+  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
+  /* 1 */
+  font-feature-settings: normal;
+  /* 2 */
+  font-variation-settings: normal;
+  /* 3 */
+  font-size: 1em;
+  /* 4 */
+}
+
+/*
+Add the correct font size in all browsers.
+*/
+
+small {
+  font-size: 80%;
+}
+
+/*
+Prevent `sub` and `sup` elements from affecting the line height in all browsers.
+*/
+
+sub,
+sup {
+  font-size: 75%;
+  line-height: 0;
+  position: relative;
+  vertical-align: baseline;
+}
+
+sub {
+  bottom: -0.25em;
+}
+
+sup {
+  top: -0.5em;
+}
+
+/*
+1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
+2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
+3. Remove gaps between table borders by default.
+*/
+
+table {
+  text-indent: 0;
+  /* 1 */
+  border-color: inherit;
+  /* 2 */
+  border-collapse: collapse;
+  /* 3 */
+}
+
+/*
+1. Change the font styles in all browsers.
+2. Remove the margin in Firefox and Safari.
+3. Remove default padding in all browsers.
+*/
+
+button,
+input,
+optgroup,
+select,
+textarea {
+  font-family: inherit;
+  /* 1 */
+  font-feature-settings: inherit;
+  /* 1 */
+  font-variation-settings: inherit;
+  /* 1 */
+  font-size: 100%;
+  /* 1 */
+  font-weight: inherit;
+  /* 1 */
+  line-height: inherit;
+  /* 1 */
+  color: inherit;
+  /* 1 */
+  margin: 0;
+  /* 2 */
+  padding: 0;
+  /* 3 */
+}
+
+/*
+Remove the inheritance of text transform in Edge and Firefox.
+*/
+
+button,
+select {
+  text-transform: none;
+}
+
+/*
+1. Correct the inability to style clickable types in iOS and Safari.
+2. Remove default button styles.
+*/
+
+button,
+[type='button'],
+[type='reset'],
+[type='submit'] {
+  -webkit-appearance: button;
+  /* 1 */
+  background-color: transparent;
+  /* 2 */
+  background-image: none;
+  /* 2 */
+}
+
+/*
+Use the modern Firefox focus style for all focusable elements.
+*/
+
+:-moz-focusring {
+  outline: auto;
+}
+
+/*
+Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
+*/
+
+:-moz-ui-invalid {
+  box-shadow: none;
+}
+
+/*
+Add the correct vertical alignment in Chrome and Firefox.
+*/
+
+progress {
+  vertical-align: baseline;
+}
+
+/*
+Correct the cursor style of increment and decrement buttons in Safari.
+*/
+
+::-webkit-inner-spin-button,
+::-webkit-outer-spin-button {
+  height: auto;
+}
+
+/*
+1. Correct the odd appearance in Chrome and Safari.
+2. Correct the outline style in Safari.
+*/
+
+[type='search'] {
+  -webkit-appearance: textfield;
+  /* 1 */
+  outline-offset: -2px;
+  /* 2 */
+}
+
+/*
+Remove the inner padding in Chrome and Safari on macOS.
+*/
+
+::-webkit-search-decoration {
+  -webkit-appearance: none;
+}
+
+/*
+1. Correct the inability to style clickable types in iOS and Safari.
+2. Change font properties to `inherit` in Safari.
+*/
+
+::-webkit-file-upload-button {
+  -webkit-appearance: button;
+  /* 1 */
+  font: inherit;
+  /* 2 */
+}
+
+/*
+Add the correct display in Chrome and Safari.
+*/
+
+summary {
+  display: list-item;
+}
+
+/*
+Removes the default spacing and border for appropriate elements.
+*/
+
+blockquote,
+dl,
+dd,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+hr,
+figure,
+p,
+pre {
+  margin: 0;
+}
+
+fieldset {
+  margin: 0;
+  padding: 0;
+}
+
+legend {
+  padding: 0;
+}
+
+ol,
+ul,
+menu {
+  list-style: none;
+  margin: 0;
+  padding: 0;
+}
+
+/*
+Reset default styling for dialogs.
+*/
+
+dialog {
+  padding: 0;
+}
+
+/*
+Prevent resizing textareas horizontally by default.
+*/
+
+textarea {
+  resize: vertical;
+}
+
+/*
+1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
+2. Set the default placeholder color to the user's configured gray 400 color.
+*/
+
+input::-moz-placeholder, textarea::-moz-placeholder {
+  opacity: 1;
+  /* 1 */
+  color: rgba(var(--gray-400), 1);
+  /* 2 */
+}
+
+input::placeholder,
+textarea::placeholder {
+  opacity: 1;
+  /* 1 */
+  color: rgba(var(--gray-400), 1);
+  /* 2 */
+}
+
+/*
+Set the default cursor for buttons.
+*/
+
+button,
+[role="button"] {
+  cursor: pointer;
+}
+
+/*
+Make sure disabled buttons don't get the pointer cursor.
+*/
+
+:disabled {
+  cursor: default;
+}
+
+/*
+1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
+2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
+   This can trigger a poorly considered lint error in some tools but is included by design.
+*/
+
+img,
+svg,
+video,
+canvas,
+audio,
+iframe,
+embed,
+object {
+  display: block;
+  /* 1 */
+  vertical-align: middle;
+  /* 2 */
+}
+
+/*
+Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
+*/
+
+img,
+video {
+  max-width: 100%;
+  height: auto;
+}
+
+/* Make elements with the HTML hidden attribute stay hidden by default */
+
+[hidden] {
+  display: none;
+}
+
+[type='text'],input:where(:not([type])),[type='email'],[type='url'],[type='password'],[type='number'],[type='date'],[type='datetime-local'],[type='month'],[type='search'],[type='tel'],[type='time'],[type='week'],[multiple],textarea,select {
+  -webkit-appearance: none;
+     -moz-appearance: none;
+          appearance: none;
+  background-color: #fff;
+  border-color: rgba(var(--gray-500), var(--tw-border-opacity, 1));
+  border-width: 1px;
+  border-radius: 0px;
+  padding-top: 0.5rem;
+  padding-right: 0.75rem;
+  padding-bottom: 0.5rem;
+  padding-left: 0.75rem;
+  font-size: 1rem;
+  line-height: 1.5rem;
+  --tw-shadow: 0 0 #0000;
+}
+
+[type='text']:focus, input:where(:not([type])):focus, [type='email']:focus, [type='url']:focus, [type='password']:focus, [type='number']:focus, [type='date']:focus, [type='datetime-local']:focus, [type='month']:focus, [type='search']:focus, [type='tel']:focus, [type='time']:focus, [type='week']:focus, [multiple]:focus, textarea:focus, select:focus {
+  outline: 2px solid transparent;
+  outline-offset: 2px;
+  --tw-ring-inset: var(--tw-empty,/*!*/ /*!*/);
+  --tw-ring-offset-width: 0px;
+  --tw-ring-offset-color: #fff;
+  --tw-ring-color: #2563eb;
+  --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
+  --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);
+  box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+  border-color: #2563eb;
+}
+
+input::-moz-placeholder, textarea::-moz-placeholder {
+  color: rgba(var(--gray-500), var(--tw-text-opacity, 1));
+  opacity: 1;
+}
+
+input::placeholder,textarea::placeholder {
+  color: rgba(var(--gray-500), var(--tw-text-opacity, 1));
+  opacity: 1;
+}
+
+::-webkit-datetime-edit-fields-wrapper {
+  padding: 0;
+}
+
+::-webkit-date-and-time-value {
+  min-height: 1.5em;
+  text-align: inherit;
+}
+
+::-webkit-datetime-edit {
+  display: inline-flex;
+}
+
+::-webkit-datetime-edit,::-webkit-datetime-edit-year-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-meridiem-field {
+  padding-top: 0;
+  padding-bottom: 0;
+}
+
+select {
+  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='rgba(var(--gray-500)%2c var(--tw-stroke-opacity%2c 1))' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
+  background-position: right 0.5rem center;
+  background-repeat: no-repeat;
+  background-size: 1.5em 1.5em;
+  padding-right: 2.5rem;
+  -webkit-print-color-adjust: exact;
+          print-color-adjust: exact;
+}
+
+[multiple],[size]:where(select:not([size="1"])) {
+  background-image: initial;
+  background-position: initial;
+  background-repeat: unset;
+  background-size: initial;
+  padding-right: 0.75rem;
+  -webkit-print-color-adjust: unset;
+          print-color-adjust: unset;
+}
+
+[type='checkbox'],[type='radio'] {
+  -webkit-appearance: none;
+     -moz-appearance: none;
+          appearance: none;
+  padding: 0;
+  -webkit-print-color-adjust: exact;
+          print-color-adjust: exact;
+  display: inline-block;
+  vertical-align: middle;
+  background-origin: border-box;
+  -webkit-user-select: none;
+     -moz-user-select: none;
+          user-select: none;
+  flex-shrink: 0;
+  height: 1rem;
+  width: 1rem;
+  color: #2563eb;
+  background-color: #fff;
+  border-color: rgba(var(--gray-500), var(--tw-border-opacity, 1));
+  border-width: 1px;
+  --tw-shadow: 0 0 #0000;
+}
+
+[type='checkbox'] {
+  border-radius: 0px;
+}
+
+[type='radio'] {
+  border-radius: 100%;
+}
+
+[type='checkbox']:focus,[type='radio']:focus {
+  outline: 2px solid transparent;
+  outline-offset: 2px;
+  --tw-ring-inset: var(--tw-empty,/*!*/ /*!*/);
+  --tw-ring-offset-width: 2px;
+  --tw-ring-offset-color: #fff;
+  --tw-ring-color: #2563eb;
+  --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
+  --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
+  box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+}
+
+[type='checkbox']:checked,[type='radio']:checked {
+  border-color: transparent;
+  background-color: currentColor;
+  background-size: 100% 100%;
+  background-position: center;
+  background-repeat: no-repeat;
+}
+
+[type='checkbox']:checked {
+  background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e");
+}
+
+@media (forced-colors: active)  {
+  [type='checkbox']:checked {
+    -webkit-appearance: auto;
+       -moz-appearance: auto;
+            appearance: auto;
+  }
+}
+
+[type='radio']:checked {
+  background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e");
+}
+
+@media (forced-colors: active)  {
+  [type='radio']:checked {
+    -webkit-appearance: auto;
+       -moz-appearance: auto;
+            appearance: auto;
+  }
+}
+
+[type='checkbox']:checked:hover,[type='checkbox']:checked:focus,[type='radio']:checked:hover,[type='radio']:checked:focus {
+  border-color: transparent;
+  background-color: currentColor;
+}
+
+[type='checkbox']:indeterminate {
+  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3e%3cpath stroke='white' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3e%3c/svg%3e");
+  border-color: transparent;
+  background-color: currentColor;
+  background-size: 100% 100%;
+  background-position: center;
+  background-repeat: no-repeat;
+}
+
+@media (forced-colors: active)  {
+  [type='checkbox']:indeterminate {
+    -webkit-appearance: auto;
+       -moz-appearance: auto;
+            appearance: auto;
+  }
+}
+
+[type='checkbox']:indeterminate:hover,[type='checkbox']:indeterminate:focus {
+  border-color: transparent;
+  background-color: currentColor;
+}
+
+[type='file'] {
+  background: unset;
+  border-color: inherit;
+  border-width: 0;
+  border-radius: 0;
+  padding: 0;
+  font-size: unset;
+  line-height: inherit;
+}
+
+[type='file']:focus {
+  outline: 1px solid ButtonText;
+  outline: 1px auto -webkit-focus-ring-color;
+}
+
+:root.dark {
+  color-scheme: dark;
+}
+
+/* When scrolling to validation error, do not hide element behind the top bar */
+
+[data-field-wrapper] {
+  scroll-margin-top: 8rem;
+}
+
+*, ::before, ::after {
+  --tw-border-spacing-x: 0;
+  --tw-border-spacing-y: 0;
+  --tw-translate-x: 0;
+  --tw-translate-y: 0;
+  --tw-rotate: 0;
+  --tw-skew-x: 0;
+  --tw-skew-y: 0;
+  --tw-scale-x: 1;
+  --tw-scale-y: 1;
+  --tw-pan-x:  ;
+  --tw-pan-y:  ;
+  --tw-pinch-zoom:  ;
+  --tw-scroll-snap-strictness: proximity;
+  --tw-gradient-from-position:  ;
+  --tw-gradient-via-position:  ;
+  --tw-gradient-to-position:  ;
+  --tw-ordinal:  ;
+  --tw-slashed-zero:  ;
+  --tw-numeric-figure:  ;
+  --tw-numeric-spacing:  ;
+  --tw-numeric-fraction:  ;
+  --tw-ring-inset:  ;
+  --tw-ring-offset-width: 0px;
+  --tw-ring-offset-color: #fff;
+  --tw-ring-color: rgb(59 130 246 / 0.5);
+  --tw-ring-offset-shadow: 0 0 #0000;
+  --tw-ring-shadow: 0 0 #0000;
+  --tw-shadow: 0 0 #0000;
+  --tw-shadow-colored: 0 0 #0000;
+  --tw-blur:  ;
+  --tw-brightness:  ;
+  --tw-contrast:  ;
+  --tw-grayscale:  ;
+  --tw-hue-rotate:  ;
+  --tw-invert:  ;
+  --tw-saturate:  ;
+  --tw-sepia:  ;
+  --tw-drop-shadow:  ;
+  --tw-backdrop-blur:  ;
+  --tw-backdrop-brightness:  ;
+  --tw-backdrop-contrast:  ;
+  --tw-backdrop-grayscale:  ;
+  --tw-backdrop-hue-rotate:  ;
+  --tw-backdrop-invert:  ;
+  --tw-backdrop-opacity:  ;
+  --tw-backdrop-saturate:  ;
+  --tw-backdrop-sepia:  ;
+}
+
+::backdrop {
+  --tw-border-spacing-x: 0;
+  --tw-border-spacing-y: 0;
+  --tw-translate-x: 0;
+  --tw-translate-y: 0;
+  --tw-rotate: 0;
+  --tw-skew-x: 0;
+  --tw-skew-y: 0;
+  --tw-scale-x: 1;
+  --tw-scale-y: 1;
+  --tw-pan-x:  ;
+  --tw-pan-y:  ;
+  --tw-pinch-zoom:  ;
+  --tw-scroll-snap-strictness: proximity;
+  --tw-gradient-from-position:  ;
+  --tw-gradient-via-position:  ;
+  --tw-gradient-to-position:  ;
+  --tw-ordinal:  ;
+  --tw-slashed-zero:  ;
+  --tw-numeric-figure:  ;
+  --tw-numeric-spacing:  ;
+  --tw-numeric-fraction:  ;
+  --tw-ring-inset:  ;
+  --tw-ring-offset-width: 0px;
+  --tw-ring-offset-color: #fff;
+  --tw-ring-color: rgb(59 130 246 / 0.5);
+  --tw-ring-offset-shadow: 0 0 #0000;
+  --tw-ring-shadow: 0 0 #0000;
+  --tw-shadow: 0 0 #0000;
+  --tw-shadow-colored: 0 0 #0000;
+  --tw-blur:  ;
+  --tw-brightness:  ;
+  --tw-contrast:  ;
+  --tw-grayscale:  ;
+  --tw-hue-rotate:  ;
+  --tw-invert:  ;
+  --tw-saturate:  ;
+  --tw-sepia:  ;
+  --tw-drop-shadow:  ;
+  --tw-backdrop-blur:  ;
+  --tw-backdrop-brightness:  ;
+  --tw-backdrop-contrast:  ;
+  --tw-backdrop-grayscale:  ;
+  --tw-backdrop-hue-rotate:  ;
+  --tw-backdrop-invert:  ;
+  --tw-backdrop-opacity:  ;
+  --tw-backdrop-saturate:  ;
+  --tw-backdrop-sepia:  ;
+}
+
+.pointer-events-none {
+  pointer-events: none;
+}
+
+.absolute {
+  position: absolute;
+}
+
+.relative {
+  position: relative;
+}
+
+.sticky {
+  position: sticky;
+}
+
+.inset-y-0 {
+  top: 0px;
+  bottom: 0px;
+}
+
+.-left-\[calc\(0\.5rem_-_1px\)\] {
+  left: calc(calc(0.5rem - 1px) * -1);
+}
+
+.-left-\[calc\(0\.75rem_-_1px\)\] {
+  left: calc(calc(0.75rem - 1px) * -1);
+}
+
+.bottom-0 {
+  bottom: 0px;
+}
+
+.left-0 {
+  left: 0px;
+}
+
+.left-5 {
+  left: 1.25rem;
+}
+
+.right-0 {
+  right: 0px;
+}
+
+.top-\[2px\] {
+  top: 2px;
+}
+
+.z-10 {
+  z-index: 10;
+}
+
+.z-20 {
+  z-index: 20;
+}
+
+.-my-8 {
+  margin-top: -2rem;
+  margin-bottom: -2rem;
+}
+
+.mx-1 {
+  margin-left: 0.25rem;
+  margin-right: 0.25rem;
+}
+
+.my-2 {
+  margin-top: 0.5rem;
+  margin-bottom: 0.5rem;
+}
+
+.-ml-\[5px\] {
+  margin-left: -5px;
+}
+
+.-mt-3 {
+  margin-top: -0.75rem;
+}
+
+.-mt-3\.5 {
+  margin-top: -0.875rem;
+}
+
+.ml-2 {
+  margin-left: 0.5rem;
+}
+
+.ml-4 {
+  margin-left: 1rem;
+}
+
+.ml-5 {
+  margin-left: 1.25rem;
+}
+
+.ml-7 {
+  margin-left: 1.75rem;
+}
+
+.ml-8 {
+  margin-left: 2rem;
+}
+
+.mt-0 {
+  margin-top: 0px;
+}
+
+.mt-0\.5 {
+  margin-top: 0.125rem;
+}
+
+.mt-1 {
+  margin-top: 0.25rem;
+}
+
+.mt-2 {
+  margin-top: 0.5rem;
+}
+
+.mt-4 {
+  margin-top: 1rem;
+}
+
+.block {
+  display: block;
+}
+
+.inline-block {
+  display: inline-block;
+}
+
+.flex {
+  display: flex;
+}
+
+.table {
+  display: table;
+}
+
+.flow-root {
+  display: flow-root;
+}
+
+.grid {
+  display: grid;
+}
+
+.hidden {
+  display: none;
+}
+
+.h-10 {
+  height: 2.5rem;
+}
+
+.h-3 {
+  height: 0.75rem;
+}
+
+.h-3\.5 {
+  height: 0.875rem;
+}
+
+.h-4 {
+  height: 1rem;
+}
+
+.h-5 {
+  height: 1.25rem;
+}
+
+.h-6 {
+  height: 1.5rem;
+}
+
+.h-8 {
+  height: 2rem;
+}
+
+.h-full {
+  height: 100%;
+}
+
+.w-1\/3 {
+  width: 33.333333%;
+}
+
+.w-10 {
+  width: 2.5rem;
+}
+
+.w-3 {
+  width: 0.75rem;
+}
+
+.w-3\.5 {
+  width: 0.875rem;
+}
+
+.w-32 {
+  width: 8rem;
+}
+
+.w-4 {
+  width: 1rem;
+}
+
+.w-5 {
+  width: 1.25rem;
+}
+
+.w-6 {
+  width: 1.5rem;
+}
+
+.w-8 {
+  width: 2rem;
+}
+
+.w-\[2px\] {
+  width: 2px;
+}
+
+.w-full {
+  width: 100%;
+}
+
+.min-w-\[50vw\] {
+  min-width: 50vw;
+}
+
+.min-w-full {
+  min-width: 100%;
+}
+
+.flex-shrink-0 {
+  flex-shrink: 0;
+}
+
+.shrink-0 {
+  flex-shrink: 0;
+}
+
+.grow {
+  flex-grow: 1;
+}
+
+.table-auto {
+  table-layout: auto;
+}
+
+.-translate-y-px {
+  --tw-translate-y: -1px;
+  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+.\!cursor-default {
+  cursor: default !important;
+}
+
+.cursor-default {
+  cursor: default;
+}
+
+.cursor-grab {
+  cursor: grab;
+}
+
+.scroll-mt-32 {
+  scroll-margin-top: 8rem;
+}
+
+.appearance-none {
+  -webkit-appearance: none;
+     -moz-appearance: none;
+          appearance: none;
+}
+
+.grid-cols-2 {
+  grid-template-columns: repeat(2, minmax(0, 1fr));
+}
+
+.grid-cols-3 {
+  grid-template-columns: repeat(3, minmax(0, 1fr));
+}
+
+.flex-col {
+  flex-direction: column;
+}
+
+.flex-wrap {
+  flex-wrap: wrap;
+}
+
+.items-start {
+  align-items: flex-start;
+}
+
+.items-end {
+  align-items: flex-end;
+}
+
+.items-center {
+  align-items: center;
+}
+
+.justify-start {
+  justify-content: flex-start;
+}
+
+.justify-end {
+  justify-content: flex-end;
+}
+
+.justify-center {
+  justify-content: center;
+}
+
+.justify-between {
+  justify-content: space-between;
+}
+
+.gap-0 {
+  gap: 0px;
+}
+
+.gap-0\.5 {
+  gap: 0.125rem;
+}
+
+.gap-1 {
+  gap: 0.25rem;
+}
+
+.gap-2 {
+  gap: 0.5rem;
+}
+
+.gap-2\.5 {
+  gap: 0.625rem;
+}
+
+.gap-3 {
+  gap: 0.75rem;
+}
+
+.gap-4 {
+  gap: 1rem;
+}
+
+.gap-6 {
+  gap: 1.5rem;
+}
+
+.gap-x-3 {
+  -moz-column-gap: 0.75rem;
+       column-gap: 0.75rem;
+}
+
+.gap-y-1 {
+  row-gap: 0.25rem;
+}
+
+.space-x-2 > :not([hidden]) ~ :not([hidden]) {
+  --tw-space-x-reverse: 0;
+  margin-right: calc(0.5rem * var(--tw-space-x-reverse));
+  margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse)));
+}
+
+.space-x-4 > :not([hidden]) ~ :not([hidden]) {
+  --tw-space-x-reverse: 0;
+  margin-right: calc(1rem * var(--tw-space-x-reverse));
+  margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
+}
+
+.space-y-1 > :not([hidden]) ~ :not([hidden]) {
+  --tw-space-y-reverse: 0;
+  margin-top: calc(0.25rem * calc(1 - var(--tw-space-y-reverse)));
+  margin-bottom: calc(0.25rem * var(--tw-space-y-reverse));
+}
+
+.space-y-2 > :not([hidden]) ~ :not([hidden]) {
+  --tw-space-y-reverse: 0;
+  margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse)));
+  margin-bottom: calc(0.5rem * var(--tw-space-y-reverse));
+}
+
+.space-y-4 > :not([hidden]) ~ :not([hidden]) {
+  --tw-space-y-reverse: 0;
+  margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse)));
+  margin-bottom: calc(1rem * var(--tw-space-y-reverse));
+}
+
+.space-y-6 > :not([hidden]) ~ :not([hidden]) {
+  --tw-space-y-reverse: 0;
+  margin-top: calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));
+  margin-bottom: calc(1.5rem * var(--tw-space-y-reverse));
+}
+
+.divide-x > :not([hidden]) ~ :not([hidden]) {
+  --tw-divide-x-reverse: 0;
+  border-right-width: calc(1px * var(--tw-divide-x-reverse));
+  border-left-width: calc(1px * calc(1 - var(--tw-divide-x-reverse)));
+}
+
+.divide-y > :not([hidden]) ~ :not([hidden]) {
+  --tw-divide-y-reverse: 0;
+  border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
+  border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
+}
+
+.divide-y-2 > :not([hidden]) ~ :not([hidden]) {
+  --tw-divide-y-reverse: 0;
+  border-top-width: calc(2px * calc(1 - var(--tw-divide-y-reverse)));
+  border-bottom-width: calc(2px * var(--tw-divide-y-reverse));
+}
+
+.divide-gray-200 > :not([hidden]) ~ :not([hidden]) {
+  --tw-divide-opacity: 1;
+  border-color: rgba(var(--gray-200), var(--tw-divide-opacity));
+}
+
+.divide-gray-950\/10 > :not([hidden]) ~ :not([hidden]) {
+  border-color: rgba(var(--gray-950), 0.1);
+}
+
+.justify-self-end {
+  justify-self: end;
+}
+
+.\!overflow-auto {
+  overflow: auto !important;
+}
+
+.overflow-hidden {
+  overflow: hidden;
+}
+
+.overflow-x-auto {
+  overflow-x: auto;
+}
+
+.whitespace-normal {
+  white-space: normal;
+}
+
+.whitespace-nowrap {
+  white-space: nowrap;
+}
+
+.rounded {
+  border-radius: 0.25rem;
+}
+
+.rounded-full {
+  border-radius: 9999px;
+}
+
+.rounded-lg {
+  border-radius: 0.5rem;
+}
+
+.rounded-xl {
+  border-radius: 0.75rem;
+}
+
+.rounded-b-lg {
+  border-bottom-right-radius: 0.5rem;
+  border-bottom-left-radius: 0.5rem;
+}
+
+.border {
+  border-width: 1px;
+}
+
+.border-2 {
+  border-width: 2px;
+}
+
+.border-x-\[0\.5px\] {
+  border-left-width: 0.5px;
+  border-right-width: 0.5px;
+}
+
+.border-t {
+  border-top-width: 1px;
+}
+
+.border-gray-100 {
+  --tw-border-opacity: 1;
+  border-color: rgba(var(--gray-100), var(--tw-border-opacity));
+}
+
+.border-gray-200 {
+  --tw-border-opacity: 1;
+  border-color: rgba(var(--gray-200), var(--tw-border-opacity));
+}
+
+.border-gray-600 {
+  --tw-border-opacity: 1;
+  border-color: rgba(var(--gray-600), var(--tw-border-opacity));
+}
+
+.border-green-300 {
+  --tw-border-opacity: 1;
+  border-color: rgb(134 239 172 / var(--tw-border-opacity));
+}
+
+.border-orange-300 {
+  --tw-border-opacity: 1;
+  border-color: rgb(253 186 116 / var(--tw-border-opacity));
+}
+
+.border-sky-300 {
+  --tw-border-opacity: 1;
+  border-color: rgb(125 211 252 / var(--tw-border-opacity));
+}
+
+.border-white\/10 {
+  border-color: rgb(255 255 255 / 0.1);
+}
+
+.border-blue-500 {
+  --tw-border-opacity: 1;
+  border-color: rgb(59 130 246 / var(--tw-border-opacity));
+}
+
+.border-primary-500 {
+  --tw-border-opacity: 1;
+  border-color: rgba(var(--primary-500), var(--tw-border-opacity));
+}
+
+.bg-gray-200 {
+  --tw-bg-opacity: 1;
+  background-color: rgba(var(--gray-200), var(--tw-bg-opacity));
+}
+
+.bg-gray-300 {
+  --tw-bg-opacity: 1;
+  background-color: rgba(var(--gray-300), var(--tw-bg-opacity));
+}
+
+.bg-gray-300\/20 {
+  background-color: rgba(var(--gray-300), 0.2);
+}
+
+.bg-gray-50 {
+  --tw-bg-opacity: 1;
+  background-color: rgba(var(--gray-50), var(--tw-bg-opacity));
+}
+
+.bg-green-50 {
+  --tw-bg-opacity: 1;
+  background-color: rgb(240 253 244 / var(--tw-bg-opacity));
+}
+
+.bg-orange-50 {
+  --tw-bg-opacity: 1;
+  background-color: rgb(255 247 237 / var(--tw-bg-opacity));
+}
+
+.bg-purple-500 {
+  --tw-bg-opacity: 1;
+  background-color: rgb(168 85 247 / var(--tw-bg-opacity));
+}
+
+.bg-sky-50 {
+  --tw-bg-opacity: 1;
+  background-color: rgb(240 249 255 / var(--tw-bg-opacity));
+}
+
+.bg-sky-500 {
+  --tw-bg-opacity: 1;
+  background-color: rgb(14 165 233 / var(--tw-bg-opacity));
+}
+
+.bg-teal-500 {
+  --tw-bg-opacity: 1;
+  background-color: rgb(20 184 166 / var(--tw-bg-opacity));
+}
+
+.bg-white {
+  --tw-bg-opacity: 1;
+  background-color: rgb(255 255 255 / var(--tw-bg-opacity));
+}
+
+.bg-white\/70 {
+  background-color: rgb(255 255 255 / 0.7);
+}
+
+.bg-custom-600 {
+  --tw-bg-opacity: 1;
+  background-color: rgba(var(--c-600), var(--tw-bg-opacity));
+}
+
+.\!p-0 {
+  padding: 0px !important;
+}
+
+.\!p-3 {
+  padding: 0.75rem !important;
+}
+
+.p-0 {
+  padding: 0px;
+}
+
+.p-0\.5 {
+  padding: 0.125rem;
+}
+
+.p-1 {
+  padding: 0.25rem;
+}
+
+.p-2 {
+  padding: 0.5rem;
+}
+
+.p-4 {
+  padding: 1rem;
+}
+
+.px-1 {
+  padding-left: 0.25rem;
+  padding-right: 0.25rem;
+}
+
+.px-1\.5 {
+  padding-left: 0.375rem;
+  padding-right: 0.375rem;
+}
+
+.px-2 {
+  padding-left: 0.5rem;
+  padding-right: 0.5rem;
+}
+
+.px-3 {
+  padding-left: 0.75rem;
+  padding-right: 0.75rem;
+}
+
+.px-4 {
+  padding-left: 1rem;
+  padding-right: 1rem;
+}
+
+.py-1 {
+  padding-top: 0.25rem;
+  padding-bottom: 0.25rem;
+}
+
+.py-2 {
+  padding-top: 0.5rem;
+  padding-bottom: 0.5rem;
+}
+
+.py-2\.5 {
+  padding-top: 0.625rem;
+  padding-bottom: 0.625rem;
+}
+
+.py-3 {
+  padding-top: 0.75rem;
+  padding-bottom: 0.75rem;
+}
+
+.py-3\.5 {
+  padding-top: 0.875rem;
+  padding-bottom: 0.875rem;
+}
+
+.py-4 {
+  padding-top: 1rem;
+  padding-bottom: 1rem;
+}
+
+.py-8 {
+  padding-top: 2rem;
+  padding-bottom: 2rem;
+}
+
+.\!ps-6 {
+  padding-inline-start: 1.5rem !important;
+}
+
+.pb-4 {
+  padding-bottom: 1rem;
+}
+
+.pe-2 {
+  padding-inline-end: 0.5rem;
+}
+
+.pl-2 {
+  padding-left: 0.5rem;
+}
+
+.pl-8 {
+  padding-left: 2rem;
+}
+
+.pt-4 {
+  padding-top: 1rem;
+}
+
+.pt-8 {
+  padding-top: 2rem;
+}
+
+.pt-\[1px\] {
+  padding-top: 1px;
+}
+
+.pt-\[5px\] {
+  padding-top: 5px;
+}
+
+.text-left {
+  text-align: left;
+}
+
+.text-center {
+  text-align: center;
+}
+
+.text-right {
+  text-align: right;
+}
+
+.text-base {
+  font-size: 1rem;
+  line-height: 1.5rem;
+}
+
+.text-sm {
+  font-size: 0.875rem;
+  line-height: 1.25rem;
+}
+
+.text-xs {
+  font-size: 0.75rem;
+  line-height: 1rem;
+}
+
+.font-bold {
+  font-weight: 700;
+}
+
+.font-medium {
+  font-weight: 500;
+}
+
+.font-normal {
+  font-weight: 400;
+}
+
+.font-semibold {
+  font-weight: 600;
+}
+
+.leading-6 {
+  line-height: 1.5rem;
+}
+
+.\!text-red-400\/60 {
+  color: rgb(248 113 113 / 0.6) !important;
+}
+
+.\!text-red-400\/80 {
+  color: rgb(248 113 113 / 0.8) !important;
+}
+
+.text-gray-200 {
+  --tw-text-opacity: 1;
+  color: rgba(var(--gray-200), var(--tw-text-opacity));
+}
+
+.text-gray-400 {
+  --tw-text-opacity: 1;
+  color: rgba(var(--gray-400), var(--tw-text-opacity));
+}
+
+.text-gray-500 {
+  --tw-text-opacity: 1;
+  color: rgba(var(--gray-500), var(--tw-text-opacity));
+}
+
+.text-gray-600 {
+  --tw-text-opacity: 1;
+  color: rgba(var(--gray-600), var(--tw-text-opacity));
+}
+
+.text-gray-700 {
+  --tw-text-opacity: 1;
+  color: rgba(var(--gray-700), var(--tw-text-opacity));
+}
+
+.text-gray-900 {
+  --tw-text-opacity: 1;
+  color: rgba(var(--gray-900), var(--tw-text-opacity));
+}
+
+.text-gray-950 {
+  --tw-text-opacity: 1;
+  color: rgba(var(--gray-950), var(--tw-text-opacity));
+}
+
+.text-green-500 {
+  --tw-text-opacity: 1;
+  color: rgb(34 197 94 / var(--tw-text-opacity));
+}
+
+.text-green-600 {
+  --tw-text-opacity: 1;
+  color: rgb(22 163 74 / var(--tw-text-opacity));
+}
+
+.text-orange-500 {
+  --tw-text-opacity: 1;
+  color: rgb(249 115 22 / var(--tw-text-opacity));
+}
+
+.text-orange-600 {
+  --tw-text-opacity: 1;
+  color: rgb(234 88 12 / var(--tw-text-opacity));
+}
+
+.text-primary-500 {
+  --tw-text-opacity: 1;
+  color: rgba(var(--primary-500), var(--tw-text-opacity));
+}
+
+.text-primary-600 {
+  --tw-text-opacity: 1;
+  color: rgba(var(--primary-600), var(--tw-text-opacity));
+}
+
+.text-red-500 {
+  --tw-text-opacity: 1;
+  color: rgb(239 68 68 / var(--tw-text-opacity));
+}
+
+.text-sky-600 {
+  --tw-text-opacity: 1;
+  color: rgb(2 132 199 / var(--tw-text-opacity));
+}
+
+.opacity-0 {
+  opacity: 0;
+}
+
+.opacity-50 {
+  opacity: 0.5;
+}
+
+.shadow {
+  --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
+  --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
+  box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
+}
+
+.shadow-md {
+  --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
+  --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
+  box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
+}
+
+.shadow-sm {
+  --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
+  --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
+  box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
+}
+
+.outline-none {
+  outline: 2px solid transparent;
+  outline-offset: 2px;
+}
+
+.ring-1 {
+  --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
+  --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);
+  box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
+}
+
+.ring-4 {
+  --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
+  --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color);
+  box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
+}
+
+.ring-inset {
+  --tw-ring-inset: inset;
+}
+
+.ring-gray-100 {
+  --tw-ring-opacity: 1;
+  --tw-ring-color: rgba(var(--gray-100), var(--tw-ring-opacity));
+}
+
+.ring-gray-200 {
+  --tw-ring-opacity: 1;
+  --tw-ring-color: rgba(var(--gray-200), var(--tw-ring-opacity));
+}
+
+.ring-gray-950\/10 {
+  --tw-ring-color: rgba(var(--gray-950), 0.1);
+}
+
+.ring-gray-950\/5 {
+  --tw-ring-color: rgba(var(--gray-950), 0.05);
+}
+
+.ring-purple-100 {
+  --tw-ring-opacity: 1;
+  --tw-ring-color: rgb(243 232 255 / var(--tw-ring-opacity));
+}
+
+.ring-sky-100 {
+  --tw-ring-opacity: 1;
+  --tw-ring-color: rgb(224 242 254 / var(--tw-ring-opacity));
+}
+
+.ring-teal-100 {
+  --tw-ring-opacity: 1;
+  --tw-ring-color: rgb(204 251 241 / var(--tw-ring-opacity));
+}
+
+.filter {
+  filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
+}
+
+.transition {
+  transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter;
+  transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
+  transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter;
+  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+  transition-duration: 150ms;
+}
+
+.transition-all {
+  transition-property: all;
+  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+  transition-duration: 150ms;
+}
+
+.transition-transform {
+  transition-property: transform;
+  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+  transition-duration: 150ms;
+}
+
+.duration-200 {
+  transition-duration: 200ms;
+}
+
+.duration-75 {
+  transition-duration: 75ms;
+}
+
+.ease-in-out {
+  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+.first\:border-s-0:first-child {
+  border-inline-start-width: 0px;
+}
+
+.last\:border-e-0:last-child {
+  border-inline-end-width: 0px;
+}
+
+.hover\:cursor-pointer:hover {
+  cursor: pointer;
+}
+
+.hover\:bg-danger-100\/80:hover {
+  background-color: rgba(var(--danger-100), 0.8);
+}
+
+.hover\:bg-gray-50:hover {
+  --tw-bg-opacity: 1;
+  background-color: rgba(var(--gray-50), var(--tw-bg-opacity));
+}
+
+.hover\:bg-primary-50:hover {
+  --tw-bg-opacity: 1;
+  background-color: rgba(var(--primary-50), var(--tw-bg-opacity));
+}
+
+.hover\:bg-primary-50\/50:hover {
+  background-color: rgba(var(--primary-50), 0.5);
+}
+
+.hover\:text-gray-500:hover {
+  --tw-text-opacity: 1;
+  color: rgba(var(--gray-500), var(--tw-text-opacity));
+}
+
+.hover\:underline:hover {
+  text-decoration-line: underline;
+}
+
+.focus-visible\:z-10:focus-visible {
+  z-index: 10;
+}
+
+.focus-visible\:ring-2:focus-visible {
+  --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
+  --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
+  box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
+}
+
+.focus-visible\:ring-primary-600:focus-visible {
+  --tw-ring-opacity: 1;
+  --tw-ring-color: rgba(var(--primary-600), var(--tw-ring-opacity));
+}
+
+.group\/item:first-child .group-first\/item\:rounded-s-lg {
+  border-start-start-radius: 0.5rem;
+  border-end-start-radius: 0.5rem;
+}
+
+.group\/item:last-child .group-last\/item\:rounded-e-lg {
+  border-start-end-radius: 0.5rem;
+  border-end-end-radius: 0.5rem;
+}
+
+.group:hover .group-hover\:flex {
+  display: flex;
+}
+
+.group:hover .group-hover\:scale-110 {
+  --tw-scale-x: 1.1;
+  --tw-scale-y: 1.1;
+  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+.group\/button:hover .group-hover\/button\:text-gray-500 {
+  --tw-text-opacity: 1;
+  color: rgba(var(--gray-500), var(--tw-text-opacity));
+}
+
+.group:hover .group-hover\:text-green-600 {
+  --tw-text-opacity: 1;
+  color: rgb(22 163 74 / var(--tw-text-opacity));
+}
+
+.group:hover .group-hover\:text-red-600 {
+  --tw-text-opacity: 1;
+  color: rgb(220 38 38 / var(--tw-text-opacity));
+}
+
+:is(.dark .dark\:divide-gray-600) > :not([hidden]) ~ :not([hidden]) {
+  --tw-divide-opacity: 1;
+  border-color: rgba(var(--gray-600), var(--tw-divide-opacity));
+}
+
+:is(.dark .dark\:divide-white\/10) > :not([hidden]) ~ :not([hidden]) {
+  border-color: rgb(255 255 255 / 0.1);
+}
+
+:is(.dark .dark\:divide-white\/5) > :not([hidden]) ~ :not([hidden]) {
+  border-color: rgb(255 255 255 / 0.05);
+}
+
+:is(.dark .dark\:border-b) {
+  border-bottom-width: 1px;
+}
+
+:is(.dark .dark\:border-gray-600) {
+  --tw-border-opacity: 1;
+  border-color: rgba(var(--gray-600), var(--tw-border-opacity));
+}
+
+:is(.dark .dark\:border-white\/10) {
+  border-color: rgb(255 255 255 / 0.1);
+}
+
+:is(.dark .dark\:border-t-white\/10) {
+  border-top-color: rgb(255 255 255 / 0.1);
+}
+
+:is(.dark .dark\:bg-gray-600) {
+  --tw-bg-opacity: 1;
+  background-color: rgba(var(--gray-600), var(--tw-bg-opacity));
+}
+
+:is(.dark .dark\:bg-gray-800) {
+  --tw-bg-opacity: 1;
+  background-color: rgba(var(--gray-800), var(--tw-bg-opacity));
+}
+
+:is(.dark .dark\:bg-gray-900) {
+  --tw-bg-opacity: 1;
+  background-color: rgba(var(--gray-900), var(--tw-bg-opacity));
+}
+
+:is(.dark .dark\:bg-green-400\/10) {
+  background-color: rgb(74 222 128 / 0.1);
+}
+
+:is(.dark .dark\:bg-orange-400\/10) {
+  background-color: rgb(251 146 60 / 0.1);
+}
+
+:is(.dark .dark\:bg-sky-400\/10) {
+  background-color: rgb(56 189 248 / 0.1);
+}
+
+:is(.dark .dark\:bg-white\/10) {
+  background-color: rgb(255 255 255 / 0.1);
+}
+
+:is(.dark .dark\:bg-white\/5) {
+  background-color: rgb(255 255 255 / 0.05);
+}
+
+:is(.dark .dark\:\!text-red-400\/60) {
+  color: rgb(248 113 113 / 0.6) !important;
+}
+
+:is(.dark .dark\:text-gray-100) {
+  --tw-text-opacity: 1;
+  color: rgba(var(--gray-100), var(--tw-text-opacity));
+}
+
+:is(.dark .dark\:text-gray-200) {
+  --tw-text-opacity: 1;
+  color: rgba(var(--gray-200), var(--tw-text-opacity));
+}
+
+:is(.dark .dark\:text-gray-300) {
+  --tw-text-opacity: 1;
+  color: rgba(var(--gray-300), var(--tw-text-opacity));
+}
+
+:is(.dark .dark\:text-gray-400) {
+  --tw-text-opacity: 1;
+  color: rgba(var(--gray-400), var(--tw-text-opacity));
+}
+
+:is(.dark .dark\:text-gray-500) {
+  --tw-text-opacity: 1;
+  color: rgba(var(--gray-500), var(--tw-text-opacity));
+}
+
+:is(.dark .dark\:text-green-400) {
+  --tw-text-opacity: 1;
+  color: rgb(74 222 128 / var(--tw-text-opacity));
+}
+
+:is(.dark .dark\:text-green-400\/80) {
+  color: rgb(74 222 128 / 0.8);
+}
+
+:is(.dark .dark\:text-orange-400) {
+  --tw-text-opacity: 1;
+  color: rgb(251 146 60 / var(--tw-text-opacity));
+}
+
+:is(.dark .dark\:text-primary-400) {
+  --tw-text-opacity: 1;
+  color: rgba(var(--primary-400), var(--tw-text-opacity));
+}
+
+:is(.dark .dark\:text-primary-400\/80) {
+  color: rgba(var(--primary-400), 0.8);
+}
+
+:is(.dark .dark\:text-red-400\/80) {
+  color: rgb(248 113 113 / 0.8);
+}
+
+:is(.dark .dark\:text-sky-400) {
+  --tw-text-opacity: 1;
+  color: rgb(56 189 248 / var(--tw-text-opacity));
+}
+
+:is(.dark .dark\:text-white) {
+  --tw-text-opacity: 1;
+  color: rgb(255 255 255 / var(--tw-text-opacity));
+}
+
+:is(.dark .dark\:ring-gray-600) {
+  --tw-ring-opacity: 1;
+  --tw-ring-color: rgba(var(--gray-600), var(--tw-ring-opacity));
+}
+
+:is(.dark .dark\:ring-gray-700) {
+  --tw-ring-opacity: 1;
+  --tw-ring-color: rgba(var(--gray-700), var(--tw-ring-opacity));
+}
+
+:is(.dark .dark\:ring-purple-800) {
+  --tw-ring-opacity: 1;
+  --tw-ring-color: rgb(107 33 168 / var(--tw-ring-opacity));
+}
+
+:is(.dark .dark\:ring-sky-800) {
+  --tw-ring-opacity: 1;
+  --tw-ring-color: rgb(7 89 133 / var(--tw-ring-opacity));
+}
+
+:is(.dark .dark\:ring-teal-800) {
+  --tw-ring-opacity: 1;
+  --tw-ring-color: rgb(17 94 89 / var(--tw-ring-opacity));
+}
+
+:is(.dark .dark\:ring-white\/10) {
+  --tw-ring-color: rgb(255 255 255 / 0.1);
+}
+
+:is(.dark .dark\:ring-white\/20) {
+  --tw-ring-color: rgb(255 255 255 / 0.2);
+}
+
+:is(.dark .dark\:hover\:bg-danger-300\/20:hover) {
+  background-color: rgba(var(--danger-300), 0.2);
+}
+
+:is(.dark .dark\:hover\:bg-white\/5:hover) {
+  background-color: rgb(255 255 255 / 0.05);
+}
+
+:is(.dark .dark\:focus-visible\:ring-primary-500:focus-visible) {
+  --tw-ring-opacity: 1;
+  --tw-ring-color: rgba(var(--primary-500), var(--tw-ring-opacity));
+}
+
+:is(.dark .group\/button:hover .dark\:group-hover\/button\:text-gray-400) {
+  --tw-text-opacity: 1;
+  color: rgba(var(--gray-400), var(--tw-text-opacity));
+}
+
+:is(.dark .group:hover .dark\:group-hover\:text-green-400) {
+  --tw-text-opacity: 1;
+  color: rgb(74 222 128 / var(--tw-text-opacity));
+}
+
+:is(.dark .group:hover .dark\:group-hover\:text-red-400) {
+  --tw-text-opacity: 1;
+  color: rgb(248 113 113 / var(--tw-text-opacity));
+}
+
+@media (min-width: 640px) {
+  .sm\:ms-auto {
+    margin-inline-start: auto;
+  }
+
+  .sm\:flex-row {
+    flex-direction: row;
+  }
+
+  .sm\:items-center {
+    align-items: center;
+  }
+
+  .sm\:px-6 {
+    padding-left: 1.5rem;
+    padding-right: 1.5rem;
+  }
+
+  .sm\:first-of-type\:ps-6:first-of-type {
+    padding-inline-start: 1.5rem;
+  }
+
+  .sm\:last-of-type\:pe-6:last-of-type {
+    padding-inline-end: 1.5rem;
+  }
+}
+
+@media (min-width: 768px) {
+  .md\:min-w-\[32rem\] {
+    min-width: 32rem;
+  }
+}
+
+.ltr\:rotate-90:where([dir="ltr"], [dir="ltr"] *) {
+  --tw-rotate: 90deg;
+  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+.rtl\:\!rotate-90:where([dir="rtl"], [dir="rtl"] *) {
+  --tw-rotate: 90deg !important;
+  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)) !important;
+}
+
+.rtl\:rotate-180:where([dir="rtl"], [dir="rtl"] *) {
+  --tw-rotate: 180deg;
+  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
+.rtl\:space-x-reverse:where([dir="rtl"], [dir="rtl"] *) > :not([hidden]) ~ :not([hidden]) {
+  --tw-space-x-reverse: 1;
+}
+
+.rtl\:text-right:where([dir="rtl"], [dir="rtl"] *) {
+  text-align: right;
+}
+
+.\[\&_table\]\:h-\[1px\] table {
+  height: 1px;
+}
diff --git a/packages/admin/resources/dist/lunar-panel.js b/packages/admin/resources/dist/lunar-panel.js
index e69de29bb2..38d4cc9aab 100644
--- a/packages/admin/resources/dist/lunar-panel.js
+++ b/packages/admin/resources/dist/lunar-panel.js
@@ -0,0 +1 @@
+//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFtdLAogICJzb3VyY2VzQ29udGVudCI6IFtdLAogICJtYXBwaW5ncyI6ICIiLAogICJuYW1lcyI6IFtdCn0K
diff --git a/packages/admin/resources/lang/en/attribute.php b/packages/admin/resources/lang/en/attribute.php
index dd629d27d0..7ca5d16abe 100644
--- a/packages/admin/resources/lang/en/attribute.php
+++ b/packages/admin/resources/lang/en/attribute.php
@@ -10,6 +10,9 @@
         'name' => [
             'label' => 'Name',
         ],
+        'description' => [
+            'label' => 'Description',
+        ],
         'handle' => [
             'label' => 'Handle',
         ],
@@ -25,6 +28,10 @@
         'name' => [
             'label' => 'Name',
         ],
+        'description' => [
+            'label' => 'Description',
+            'helper' => 'Use to display the helper text below the entry',
+        ],
         'handle' => [
             'label' => 'Handle',
         ],
diff --git a/packages/admin/resources/lang/en/components.php b/packages/admin/resources/lang/en/components.php
index 4ad2ba1e23..28295b696d 100644
--- a/packages/admin/resources/lang/en/components.php
+++ b/packages/admin/resources/lang/en/components.php
@@ -93,4 +93,25 @@
             ],
         ],
     ],
+
+    'product-options-list' => [
+        'add-option' => [
+            'label' => 'Add Option',
+        ],
+        'delete-option' => [
+            'label' => 'Delete Option',
+        ],
+        'remove-shared-option' => [
+            'label' => 'Remove Shared Option',
+        ],
+        'add-value' => [
+            'label' => 'Add Another Value',
+        ],
+        'name' => [
+            'label' => 'Name',
+        ],
+        'values' => [
+            'label' => 'Values',
+        ],
+    ],
 ];
diff --git a/packages/admin/resources/lang/en/customer.php b/packages/admin/resources/lang/en/customer.php
index 71dc438490..6dfe6c5cd5 100644
--- a/packages/admin/resources/lang/en/customer.php
+++ b/packages/admin/resources/lang/en/customer.php
@@ -28,6 +28,12 @@
         'account_reference' => [
             'label' => 'Account Reference',
         ],
+        'new' => [
+            'label' => 'New',
+        ],
+        'returning' => [
+            'label' => 'Returning',
+        ],
     ],
 
     'form' => [
diff --git a/packages/admin/resources/lang/en/order.php b/packages/admin/resources/lang/en/order.php
index d0fd9cb2ea..25d8db99e2 100644
--- a/packages/admin/resources/lang/en/order.php
+++ b/packages/admin/resources/lang/en/order.php
@@ -44,6 +44,9 @@
         'date' => [
             'label' => 'Date',
         ],
+        'new_customer' => [
+            'label' => 'Customer Type',
+        ],
     ],
 
     'form' => [
diff --git a/packages/admin/resources/lang/en/product.php b/packages/admin/resources/lang/en/product.php
index 52e87ac94c..2b36e83e8a 100644
--- a/packages/admin/resources/lang/en/product.php
+++ b/packages/admin/resources/lang/en/product.php
@@ -87,83 +87,12 @@
         ],
         'identifiers' => [
             'label' => 'Product Identifiers',
-            'form' => [
-                'sku' => [
-                    'label' => 'SKU',
-                ],
-                'gtin' => [
-                    'label' => 'Global Trade Item Number (GTIN)',
-                ],
-                'mpn' => [
-                    'label' => 'Manufacturer Part Number (MPN)',
-                ],
-                'ean' => [
-                    'label' => 'UPC/EAN',
-                ],
-            ],
         ],
         'inventory' => [
             'label' => 'Inventory',
-            'form' => [
-                'stock' => [
-                    'label' => 'In Stock',
-                ],
-                'backorder' => [
-                    'label' => 'On Backorder',
-                ],
-                'purchasable' => [
-                    'label' => 'Purchasability',
-                    'options' => [
-                        'always' => 'Always',
-                        'in_stock' => 'In Stock',
-                        'backorder' => 'Backorder Only',
-                    ],
-                ],
-                'unit_quantity' => [
-                    'label' => 'Unit Quantity',
-                    'helper_text' => 'How many individual items make up 1 unit.',
-                ],
-                'min_quantity' => [
-                    'label' => 'Minimum Quantity',
-                    'helper_text' => 'The minimum quantity of a product variant that can be bought in a single purchase.',
-                ],
-                'quantity_increment' => [
-                    'label' => 'Quantity Increment',
-                    'helper_text' => 'The product variant must be purchased in multiples of this quantity.',
-                ],
-            ],
         ],
         'shipping' => [
             'label' => 'Shipping',
-            'form' => [
-                'shippable' => [
-                    'label' => 'Shippable',
-                ],
-                'length_value' => [
-                    'label' => 'Length',
-                ],
-                'length_unit' => [
-                    'label' => 'Length Unit',
-                ],
-                'width_value' => [
-                    'label' => 'Width',
-                ],
-                'width_unit' => [
-                    'label' => 'Width Unit',
-                ],
-                'height_value' => [
-                    'label' => 'Height',
-                ],
-                'height_unit' => [
-                    'label' => 'Height Unit',
-                ],
-                'weight_value' => [
-                    'label' => 'Weight',
-                ],
-                'weight_unit' => [
-                    'label' => 'Weight Unit',
-                ],
-            ],
         ],
     ],
 
diff --git a/packages/admin/resources/lang/en/productoption.php b/packages/admin/resources/lang/en/productoption.php
index bb025e50d0..7d4d9c799e 100644
--- a/packages/admin/resources/lang/en/productoption.php
+++ b/packages/admin/resources/lang/en/productoption.php
@@ -16,9 +16,6 @@
         'handle' => [
             'label' => 'Handle',
         ],
-        'position' => [
-            'label' => 'Position',
-        ],
     ],
 
     'form' => [
@@ -31,8 +28,93 @@
         'handle' => [
             'label' => 'Handle',
         ],
-        'position' => [
-            'label' => 'Position',
+    ],
+
+    'widgets' => [
+        'product-options' => [
+            'notifications' => [
+                'save-variants' => [
+                    'success' => [
+                        'title' => 'Product Variants Saved',
+                    ],
+                ],
+            ],
+            'actions' => [
+                'cancel' => [
+                    'label' => 'Cancel',
+                ],
+                'save-options' => [
+                    'label' => 'Save Options',
+                ],
+                'add-shared-option' => [
+                    'label' => 'Add Shared Option',
+                    'form' => [
+                        'product_option' => [
+                            'label' => 'Product Option',
+                        ],
+                        'no_shared_components' => [
+                            'label' => 'No shared options are available.',
+                        ],
+                    ],
+                ],
+                'add-restricted-option' => [
+                    'label' => 'Add Option',
+                ],
+            ],
+            'options-list' => [
+                'empty' => [
+                    'heading' => 'There are no product options configured',
+                    'description' => 'Add a shared or restricted product option to start generating some variants.',
+                ],
+            ],
+            'options-table' => [
+                'title' => 'Product Options',
+                'configure-options' => [
+                    'label' => 'Configure Options',
+                ],
+                'table' => [
+                    'option' => [
+                        'label' => 'Option',
+                    ],
+                    'values' => [
+                        'label' => 'Values',
+                    ],
+                ],
+            ],
+            'variants-table' => [
+                'title' => 'Product Variants',
+                'actions' => [
+                    'create' => [
+                        'label' => 'Create Variant',
+                    ],
+                    'edit' => [
+                        'label' => 'Edit',
+                    ],
+                    'delete' => [
+                        'label' => 'Delete',
+                    ],
+                ],
+                'empty' => [
+                    'heading' => 'No Variants Configured',
+                ],
+                'table' => [
+                    'new' => [
+                        'label' => 'NEW',
+                    ],
+                    'option' => [
+                        'label' => 'Option',
+                    ],
+                    'sku' => [
+                        'label' => 'SKU',
+                    ],
+                    'price' => [
+                        'label' => 'Price',
+                    ],
+                    'stock' => [
+                        'label' => 'Stock',
+                    ],
+                ],
+            ],
         ],
     ],
 
diff --git a/packages/admin/resources/lang/en/productvariant.php b/packages/admin/resources/lang/en/productvariant.php
new file mode 100644
index 0000000000..116ecda864
--- /dev/null
+++ b/packages/admin/resources/lang/en/productvariant.php
@@ -0,0 +1,102 @@
+<?php
+
+return [
+    'label' => 'Product Variant',
+    'plural_label' => 'Product Variants',
+    'pages' => [
+        'edit' => [
+            'title' => 'Basic Information',
+        ],
+        'media' => [
+            'title' => 'Media',
+            'form' => [
+                'no_selection' => [
+                    'label' => 'You do not currently have an image selected for this variant.',
+                ],
+                'no_media_available' => [
+                    'label' => 'There is currently no media available on this product.',
+                ],
+                'images' => [
+                    'label' => 'Primary Image',
+                    'helper_text' => 'Select the product image which represents this variant.',
+                ],
+            ],
+        ],
+        'identifiers' => [
+            'title' => 'Identifiers',
+        ],
+        'inventory' => [
+            'title' => 'Inventory',
+        ],
+        'shipping' => [
+            'title' => 'Shipping',
+        ],
+    ],
+    'form' => [
+        'sku' => [
+            'label' => 'SKU',
+        ],
+        'gtin' => [
+            'label' => 'Global Trade Item Number (GTIN)',
+        ],
+        'mpn' => [
+            'label' => 'Manufacturer Part Number (MPN)',
+        ],
+        'ean' => [
+            'label' => 'UPC/EAN',
+        ],
+        'stock' => [
+            'label' => 'In Stock',
+        ],
+        'backorder' => [
+            'label' => 'On Backorder',
+        ],
+        'purchasable' => [
+            'label' => 'Purchasability',
+            'options' => [
+                'always' => 'Always',
+                'in_stock' => 'In Stock',
+                'backorder' => 'Backorder Only',
+            ],
+        ],
+        'unit_quantity' => [
+            'label' => 'Unit Quantity',
+            'helper_text' => 'How many individual items make up 1 unit.',
+        ],
+        'min_quantity' => [
+            'label' => 'Minimum Quantity',
+            'helper_text' => 'The minimum quantity of a product variant that can be bought in a single purchase.',
+        ],
+        'quantity_increment' => [
+            'label' => 'Quantity Increment',
+            'helper_text' => 'The product variant must be purchased in multiples of this quantity.',
+        ],
+        'shippable' => [
+            'label' => 'Shippable',
+        ],
+        'length_value' => [
+            'label' => 'Length',
+        ],
+        'length_unit' => [
+            'label' => 'Length Unit',
+        ],
+        'width_value' => [
+            'label' => 'Width',
+        ],
+        'width_unit' => [
+            'label' => 'Width Unit',
+        ],
+        'height_value' => [
+            'label' => 'Height',
+        ],
+        'height_unit' => [
+            'label' => 'Height Unit',
+        ],
+        'weight_value' => [
+            'label' => 'Weight',
+        ],
+        'weight_unit' => [
+            'label' => 'Weight Unit',
+        ],
+    ],
+];
diff --git a/packages/admin/resources/lang/en/relationmanagers.php b/packages/admin/resources/lang/en/relationmanagers.php
index 1a4a9244ab..3d332b713c 100644
--- a/packages/admin/resources/lang/en/relationmanagers.php
+++ b/packages/admin/resources/lang/en/relationmanagers.php
@@ -131,8 +131,8 @@
             'customer_group' => [
                 'label' => 'Customer Group',
             ],
-            'tier' => [
-                'label' => 'Tier',
+            'quantity_break' => [
+                'label' => 'Quantity Break',
             ],
             'currency' => [
                 'label' => 'Currency',
@@ -145,8 +145,8 @@
             'customer_group_id' => [
                 'label' => 'Customer Group',
             ],
-            'tier' => [
-                'label' => 'Tier',
+            'quantity_break' => [
+                'label' => 'Quantity Break',
             ],
             'currency_id' => [
                 'label' => 'Currency',
diff --git a/packages/admin/resources/lang/en/widgets.php b/packages/admin/resources/lang/en/widgets.php
index 91be2b13ec..71e042746f 100644
--- a/packages/admin/resources/lang/en/widgets.php
+++ b/packages/admin/resources/lang/en/widgets.php
@@ -104,4 +104,15 @@
             ],
         ],
     ],
+    'variant_switcher' => [
+        'label' => 'Switch Variant',
+        'table' => [
+            'sku' => [
+                'label' => 'SKU',
+            ],
+            'values' => [
+                'label' => 'Values',
+            ],
+        ],
+    ],
 ];
diff --git a/packages/admin/resources/views/actions/switch-variant.blade.php b/packages/admin/resources/views/actions/switch-variant.blade.php
new file mode 100644
index 0000000000..4a1e60346e
--- /dev/null
+++ b/packages/admin/resources/views/actions/switch-variant.blade.php
@@ -0,0 +1,5 @@
+<div>
+  @livewire(\Lunar\Admin\Filament\Widgets\Products\VariantSwitcherTable::class, [
+    'record' => $record,
+  ])
+</div>
\ No newline at end of file
diff --git a/packages/admin/resources/views/components/products/variants/product-option-list-values.blade.php b/packages/admin/resources/views/components/products/variants/product-option-list-values.blade.php
new file mode 100644
index 0000000000..d2f1ecec74
--- /dev/null
+++ b/packages/admin/resources/views/components/products/variants/product-option-list-values.blade.php
@@ -0,0 +1,92 @@
+@props(['items', 'statePath', 'key', 'canAddValues', 'readonly' => false])
+<div>
+  <div
+   class="space-y-2"
+   @if(!$readonly)
+     x-ref="sortableListValues"
+     x-data="{
+        items: @js($items)
+      }"
+     x-init="() => {
+          el = $refs.sortableListValues
+
+          el.sortable = Sortable.create(el, {
+              group: 'option_values_{{ $key }}',
+              draggable: '[x-sortable-item]',
+              handle: '[x-sortable-handle]',
+              dataIdAttr: 'x-sortable-item',
+              animation: 300,
+              ghostClass: 'fi-sortable-ghost',
+              onEnd: (event) => {
+                const rows = items
+                console.log(rows)
+                const reorderedRow = rows.splice(event.oldIndex, 1)[0]
+                items.splice(event.newIndex, 0, reorderedRow)
+
+                rows.forEach(
+                  (item, itemIndex) => item.position = itemIndex + 1
+                )
+
+                this.items = rows
+
+                $wire.call('updateValuePositions', '{{ $key }}', rows)
+              }
+          })
+        }"
+     @endif
+  >
+    @foreach($items as $itemIndex => $valueItem)
+      <div x-sortable-item="{{ $itemIndex }}" wire:key="option_{{ $itemIndex }}_value">
+        <div class="flex space-x-2 items-center">
+          @if(!$readonly)
+          <div
+            @class([
+              'flex items-center',
+              'cursor-grab text-gray-400 hover:text-gray-500' => !$readonly,
+              'text-gray-200' => $readonly,
+            ])
+             x-sortable-handle
+          >
+            <x-filament::icon alias="lunar::reorder" class="w-5 h-5" />
+          </div>
+          @endif
+          <div
+            @class([
+              'grow',
+              'opacity-50' => !$valueItem['enabled']
+            ])
+          >
+            <x-filament::input.wrapper :valid="!$errors->has($statePath.'.'.$itemIndex.'.value')">
+              <x-filament::input
+                type="text"
+                wire:model="{{ $statePath }}.{{ $itemIndex }}.value"
+                :disabled="$readonly"
+              />
+            </x-filament::input.wrapper>
+          </div>
+          <div>
+            @if(!$readonly)
+              <div>
+                <button type="button" wire:click.prevent="removeOptionValue('{{ $key }}', '{{ $itemIndex }}')">
+                  <x-filament::icon alias="actions::delete-action" class="w-4 h-4 text-red-500" />
+                </button>
+              </div>
+            @else
+              <div>
+                <x-filament::input.checkbox wire:model.live="{{ $statePath }}.{{ $itemIndex }}.enabled" />
+              </div>
+            @endif
+          </div>
+        </div>
+      </div>
+    @endforeach
+  </div>
+  @if($canAddValues)
+    <div class="text-center mt-4">
+      <x-filament::button color="gray" size="xs" type="button" wire:click.prevent="addOptionValue('{{ $key }}')">
+        {{ __('lunarpanel::components.product-options-list.add-value.label') }}
+      </x-filament::button>
+      <hr class="-mt-3.5" />
+    </div>
+  @endif
+</div>
\ No newline at end of file
diff --git a/packages/admin/resources/views/components/products/variants/product-options-list.blade.php b/packages/admin/resources/views/components/products/variants/product-options-list.blade.php
new file mode 100644
index 0000000000..4cf4f74746
--- /dev/null
+++ b/packages/admin/resources/views/components/products/variants/product-options-list.blade.php
@@ -0,0 +1,98 @@
+@props(['items', 'group', 'statePath', 'context' => 'options', 'optionKey' => null])
+<div
+  class="space-y-4"
+  x-ref="sortableOptionList"
+  x-data="{
+    context: '{{ $context }}',
+    items: @js($items)
+  }"
+  x-init="() => {
+      el = $refs.sortableOptionList
+
+      el.sortable = Sortable.create(el, {
+          group: 'product_options',
+          draggable: '[x-sortable-option-item]',
+          handle: '[x-sortable-option-handle]',
+          dataIdAttr: 'x-sortable-option-item',
+          animation: 300,
+          ghostClass: 'fi-sortable-ghost',
+          onEnd: (event) => {
+            const rows = Alpine.raw(items)
+            const reorderedRow = rows.splice(event.oldIndex, 1)[0]
+            items.splice(event.newIndex, 0, reorderedRow)
+
+            rows.forEach(
+              (item, itemIndex) => item.position = itemIndex + 1
+            )
+
+            this.items = rows
+
+            $wire.call('updateOptionPositions', rows)
+          }
+      })
+    }"
+>
+  @foreach($items as $itemIndex => $item)
+    <div wire:key="option_{{ $itemIndex }}" x-sortable-option-item="option_{{ $itemIndex }}">
+      <div class="grid grid-cols-2 space-x-4">
+        <div>
+            <div>
+              <x-filament-forms::field-wrapper.label class="ml-7">
+                {{ __('lunarpanel::components.product-options-list.name.label') }}
+              </x-filament-forms::field-wrapper.label>
+              <div class="flex w-full space-x-2 mt-1 items-start">
+                <div
+                  @class([
+                    'flex items-center',
+                    'cursor-grab text-gray-400 hover:text-gray-500' => !$item['readonly'] || $context == 'options',
+                    ' text-gray-200' => $item['readonly'] && $context == 'values',
+                  ])
+                  @if(!$item['readonly'] || $context == 'options') x-sortable-option-handle @endif
+                >
+                  <x-filament::icon alias="lunar::reorder" class="w-5 h-5" />
+                </div>
+                <div class="grow">
+                  <x-filament::input.wrapper :valid="!$errors->has($statePath.'.'.$itemIndex.'.value')">
+                    <x-filament::input
+                            type="text"
+                            wire:model="{{ $statePath }}.{{ $itemIndex }}.value"
+                            :disabled="$item['readonly']"
+                    />
+                  </x-filament::input.wrapper>
+                  <button
+                    type="button"
+                    class="text-sm font-semibold text-red-500 hover:underline mt-2"
+                    wire:click.prevent="removeOption('{{ $itemIndex }}')"
+                  >
+                    {{ __(
+                        !$item['readonly'] ?
+                            'lunarpanel::components.product-options-list.delete-option.label' :
+                            'lunarpanel::components.product-options-list.remove-shared-option.label'
+                    ) }}
+                  </button>
+                </div>
+              </div>
+            </div>
+        </div>
+        <div class="space-y-1">
+          <x-filament-forms::field-wrapper.label>
+            {{ __('lunarpanel::components.product-options-list.values.label') }}
+          </x-filament-forms::field-wrapper.label>
+          <div wire:key="option_values_{{ $itemIndex }}">
+            <x-lunarpanel::products.variants.product-option-list-values
+              :items="$item['option_values']"
+              :key="$itemIndex"
+              state-path="configuredOptions.{{ $itemIndex }}.option_values"
+              :can-add-values="!$item['readonly']"
+              :readonly="$item['readonly']"
+            />
+          </div>
+        </div>
+      </div>
+    </div>
+  @endforeach
+
+  <x-filament::button color="gray" size="sm" type="button" wire:click.prevent="addRestrictedOption">
+    {{ __('lunarpanel::components.product-options-list.add-option.label') }}
+  </x-filament::button>
+</div>
\ No newline at end of file
diff --git a/packages/admin/resources/views/forms/components/media-select.blade.php b/packages/admin/resources/views/forms/components/media-select.blade.php
new file mode 100644
index 0000000000..7dd817cacf
--- /dev/null
+++ b/packages/admin/resources/views/forms/components/media-select.blade.php
@@ -0,0 +1,18 @@
+<x-dynamic-component
+        :component="$getFieldWrapperView()"
+        :field="$field"
+>
+  <div class="grid grid-cols-3 gap-4">
+    @foreach($getOptions() as $value => $label)
+      <label
+        @class([
+            'border p-2 rounded hover:cursor-pointer',
+            'border-primary-500 border-2' => $value == $getState()
+        ])
+      >
+        <img src="{{ $label }}" class="rounded">
+        <input type="radio" class="hidden" {{ $applyStateBindingModifiers('wire:model') }}.live="{{ $getStatePath() }}" value="{{ $value }}" />
+      </label>
+    @endforeach
+  </div>
+</x-dynamic-component>
\ No newline at end of file
diff --git a/packages/admin/resources/views/resources/product-resource/widgets/product-options.blade.php b/packages/admin/resources/views/resources/product-resource/widgets/product-options.blade.php
new file mode 100644
index 0000000000..c5e1c1db15
--- /dev/null
+++ b/packages/admin/resources/views/resources/product-resource/widgets/product-options.blade.php
@@ -0,0 +1,229 @@
+<x-filament-widgets::widget>
+
+  @if(!$this->configuringOptions)
+    <div class="space-y-4">
+      <div class="overflow-hidden rounded-xl bg-white shadow-sm ring-1 ring-gray-950/5 dark:divide-white/10 dark:bg-gray-900 dark:ring-white/10">
+        <div class="fi-ta-header flex flex-col gap-3 p-4 sm:px-6 sm:flex-row sm:items-center">
+          <div class="grid gap-y-1">
+            <h3 class="fi-ta-header-heading text-base font-semibold leading-6 text-gray-950 dark:text-white">
+              {{ __('lunarpanel::productoption.widgets.product-options.options-table.title') }}
+            </h3>
+          </div>
+          <div class="fi-ta-actions flex shrink-0 items-center gap-3 flex-wrap justify-start sm:ms-auto">
+            <x-filament::button type="button" wire:click="$set('configuringOptions', true)">
+              {{ __('lunarpanel::productoption.widgets.product-options.options-table.configure-options.label') }}
+            </x-filament::button>
+          </div>
+        </div>
+        <div class="fi-ta-content divide-gray-200 overflow-x-auto">
+          @if(count($this->configuredOptions))
+            <x-filament-tables::table>
+              <thead>
+                <tr class="bg-gray-50 dark:bg-white/5">
+                  <x-filament-tables::header-cell>
+                      <span class="fi-ta-header-cell-label text-sm font-semibold text-gray-950 dark:text-white">
+                        {{ __('lunarpanel::productoption.widgets.product-options.options-table.table.option.label') }}
+                      </span>
+                  </x-filament-tables::header-cell>
+                  <x-filament-tables::header-cell>
+                      <span class="fi-ta-header-cell-label text-sm font-semibold text-gray-950 dark:text-white">
+                        {{ __('lunarpanel::productoption.widgets.product-options.options-table.table.values.label') }}
+                      </span>
+                  </x-filament-tables::header-cell>
+                </tr>
+              </thead>
+              <tbody class="divide-y divide-gray-200 whitespace-nowrap dark:divide-white/5">
+              @foreach($this->configuredOptions as $option)
+                <x-filament-tables::row>
+                  <x-filament-tables::cell>
+                    <div class="fi-ta-text grid w-full gap-y-1 px-3 py-4">
+                      <span class="fi-ta-text-item-label text-sm leading-6 text-gray-950 dark:text-white  ">
+                        {{ $option['value'] }}
+                      </span>
+                    </div>
+                  </x-filament-tables::cell>
+                  <x-filament-tables::cell>
+                    <div class="fi-ta-text grid w-full gap-y-1 px-3 py-4">
+                      <span class="fi-ta-text-item-label text-sm leading-6 text-gray-950 dark:text-white  ">
+                      {{ collect($option['option_values'])
+                        ->filter(
+                            fn ($value) => $value['enabled']
+                        )->map(
+                          fn ($value) => $value['value']
+                      )->join(', ') }}
+                      </span>
+                    </div>
+                  </x-filament-tables::cell>
+                </x-filament-tables::row>
+              @endforeach
+              </tbody>
+            </x-filament-tables::table>
+          @else
+            <x-filament-tables::empty-state heading="No Product Options Configured" icon="lucide-shapes"></x-filament-tables::empty-state>
+          @endif
+        </div>
+      </div>
+
+      <div class="overflow-hidden rounded-xl bg-white shadow-sm ring-1 ring-gray-950/5 dark:divide-white/10 dark:bg-gray-900 dark:ring-white/10">
+        <div class="fi-ta-header flex flex-col gap-3 p-4 sm:px-6 sm:flex-row sm:items-center">
+          <div class="grid gap-y-1">
+            <h3 class="fi-ta-header-heading text-base font-semibold leading-6 text-gray-950 dark:text-white">
+              {{ __('lunarpanel::productoption.widgets.product-options.variants-table.title') }}
+            </h3>
+          </div>
+        </div>
+        <div class="fi-ta-content divide-y divide-gray-200 overflow-x-auto dark:divide-white/10 dark:border-t-white/10">
+          @if(count($this->variants))
+              <x-filament-tables::table>
+                <thead class="divide-y divide-gray-200 dark:divide-white/5">
+                  <tr class="bg-gray-50 dark:bg-white/5">
+                    @if($this->hasNewVariants)
+                      <x-filament-tables::header-cell>
+                      </x-filament-tables::header-cell>
+                    @endif
+                    <x-filament-tables::header-cell class="fi-ta-header-cell px-3 py-3.5 sm:first-of-type:ps-6 sm:last-of-type:pe-6">
+                      <span class="fi-ta-header-cell-label text-sm font-semibold text-gray-950 dark:text-white">
+                        {{ __('lunarpanel::productoption.widgets.product-options.variants-table.table.option.label') }}
+                      </span>
+                    </x-filament-tables::header-cell>
+                    <x-filament-tables::header-cell>
+                      <span class="fi-ta-header-cell-label text-sm font-semibold text-gray-950 dark:text-white">
+                        {{ __('lunarpanel::productoption.widgets.product-options.variants-table.table.sku.label') }}
+                      </span>
+                    </x-filament-tables::header-cell>
+                    <x-filament-tables::header-cell>
+                      <span class="fi-ta-header-cell-label text-sm font-semibold text-gray-950 dark:text-white">
+                        {{ __('lunarpanel::productoption.widgets.product-options.variants-table.table.price.label') }}
+                      </span>
+                    </x-filament-tables::header-cell>
+                    <x-filament-tables::header-cell>
+                      <span class="fi-ta-header-cell-label text-sm font-semibold text-gray-950 dark:text-white">
+                        {{ __('lunarpanel::productoption.widgets.product-options.variants-table.table.stock.label') }}
+                      </span>
+                    </x-filament-tables::header-cell>
+                    <x-filament-tables::header-cell>
+                    </x-filament-tables::header-cell>
+                  </tr>
+                </thead>
+                <tbody class="divide-y divide-gray-200 whitespace-nowrap dark:divide-white/5">
+
+                @foreach($this->variants as $permutationIndex => $permutation)
+                  <x-filament-tables::row wire:key="permutation_{{ $permutation['key'] }}">
+                    @if($this->hasNewVariants)
+                      <x-filament-tables::cell class="fi-ta-text grid w-full gap-y-1 px-3 py-4">
+                        <div class="fi-ta-text grid w-full gap-y-1 px-3 py-4">
+                          @if(!$permutation['variant_id'])
+                            <x-filament::badge color="info">
+                              {{ __('lunarpanel::productoption.widgets.product-options.variants-table.table.new.label') }}
+                            </x-filament::badge>
+                          @endif
+                        </div>
+                      </x-filament-tables::cell>
+                    @endif
+                    <x-filament-tables::cell>
+                      <div class="fi-ta-text grid w-full gap-y-1 px-3 py-4">
+                        <span class="fi-ta-text-item-label text-sm leading-6 text-gray-950 dark:text-white  ">
+                          @foreach($permutation['values'] as $option => $value)
+                            <small><strong>{{ $option }}:</strong> {{ $value }}</small>
+                          @endforeach
+                        </span>
+                      </div>
+                    </x-filament-tables::cell>
+                    <x-filament-tables::cell>
+                      <div class="fi-ta-text grid w-full gap-y-1 px-3 py-4">
+                        <x-filament::input.wrapper>
+                          <x-filament::input
+                                  type="text"
+                                  wire:model="variants.{{ $permutationIndex }}.sku"
+                          />
+                        </x-filament::input.wrapper>
+                      </div>
+                    </x-filament-tables::cell>
+                    <x-filament-tables::cell class="w-32">
+                      <div class="fi-ta-text grid w-full gap-y-1 px-3 py-4">
+                        <x-filament::input.wrapper>
+                          <x-filament::input
+                                  type="text"
+                                  wire:model="variants.{{ $permutationIndex }}.price"
+                          />
+                        </x-filament::input.wrapper>
+                      </div>
+                    </x-filament-tables::cell>
+                    <x-filament-tables::cell class="w-32">
+                      <div class="fi-ta-text grid w-full gap-y-1 px-3 py-4">
+                        <x-filament::input.wrapper>
+                          <x-filament::input
+                                  type="text"
+                                  wire:model="variants.{{ $permutationIndex }}.stock"
+                          />
+                        </x-filament::input.wrapper>
+                      </div>
+                    </x-filament-tables::cell>
+                    <x-filament-tables::cell>
+                      <div class="flex items-center space-x-2">
+                        @if($permutation['variant_id'])
+                          <x-filament::link :href="$this->getVariantLink($permutation['variant_id'])">
+                            {{ __('lunarpanel::productoption.widgets.product-options.variants-table.actions.edit.label') }}
+                          </x-filament::link>
+                        @endif
+                        <button type="button" wire:click="removeVariant('{{ $permutationIndex }}')" class="text-red-500 font-semibold text-sm hover:underline">
+                          {{ __('lunarpanel::productoption.widgets.product-options.variants-table.actions.delete.label') }}
+                        </button>
+                      </div>
+                    </x-filament-tables::cell>
+
+                  </x-filament-tables::row>
+                @endforeach
+                </tbody>
+              </x-filament-tables::table>
+            @else
+              <x-filament-tables::empty-state :heading="__('lunarpanel::productoption.widgets.product-options.variants-table.empty.heading')" icon="lucide-shapes"></x-filament-tables::empty-state>
+            @endif
+        </div>
+      </div>
+    </div>
+
+    <div class="mt-4 flex">
+      {{ $this->saveVariantsAction }}
+    </div>
+
+  @else
+    <div class="space-y-4">
+      <div class="text-right">
+        <div class="flex space-x-2 items-end justify-end">
+        <x-filament::button color="gray" wire:click="addRestrictedOption">
+          {{ __('lunarpanel::productoption.widgets.product-options.actions.add-restricted-option.label') }}
+        </x-filament::button>
+        {{ $this->addSharedOptionAction }}
+        </div>
+      </div>
+      @if(!count($this->configuredOptions))
+        <div wire:key="product_options">
+          <x-filament-tables::empty-state
+            :heading="__('lunarpanel::productoption.widgets.product-options.options-list.empty.heading')"
+            :description="__('lunarpanel::productoption.widgets.product-options.options-list.empty.description')"
+            icon="lucide-shapes"
+          ></x-filament-tables::empty-state>
+        </div>
+      @else
+        <div>
+          <x-lunarpanel::products.variants.product-options-list
+            :items="$configuredOptions"
+            group="product_options"
+            state-path="configuredOptions"
+          />
+        </div>
+      @endif
+
+      <div class="flex space-x-2 border-t dark:border-white/10 pt-4">
+        <x-filament::button type="button" wire:click="updateConfiguredOptions">
+          {{ __('lunarpanel::productoption.widgets.product-options.actions.save-options.label') }}
+        </x-filament::button>
+        <x-filament::button color="gray" wire:click="cancelOptionConfiguring">
+          {{ __('lunarpanel::productoption.widgets.product-options.actions.cancel.label') }}
+        </x-filament::button>
+      </div>
+    </div>
+    <x-filament-actions::modals />
+  @endif
+</x-filament-widgets::widget>
diff --git a/packages/admin/src/Actions/Products/MapVariantsToProductOptions.php b/packages/admin/src/Actions/Products/MapVariantsToProductOptions.php
new file mode 100644
index 0000000000..69bb4fbcb2
--- /dev/null
+++ b/packages/admin/src/Actions/Products/MapVariantsToProductOptions.php
@@ -0,0 +1,82 @@
+<?php
+
+namespace Lunar\Admin\Actions\Products;
+
+use Illuminate\Support\Str;
+use Lunar\Utils\Arr;
+
+class MapVariantsToProductOptions
+{
+    public static function map(array $options, array $variants, bool $fillMissing = true): array
+    {
+        $permutations = Arr::permutate($options);
+
+        if (count($options) == 1) {
+            $newPermutations = [];
+            foreach ($permutations as $p) {
+                $newPermutations[] = [
+                    array_key_first($options) => $p,
+                ];
+            }
+            $permutations = $newPermutations;
+        }
+
+        $variantPermutations = [];
+
+        foreach ($permutations as $permutation) {
+            $variantIndex = collect($variants)->search(function ($variant) use ($permutation) {
+                $valueDifference = array_diff_assoc($permutation, $variant['values']);
+
+                if (! count($valueDifference)) {
+                    return $variant;
+                }
+
+                $amountMatched = count($permutation) - count($valueDifference);
+
+                return $amountMatched == count($variant['values']);
+            });
+
+            $variant = $variants[$variantIndex] ?? null;
+
+            $variantId = $variant['id'] ?? null;
+            $sku = $variant['sku'] ?? null;
+            $copiedFrom = null;
+            $shouldFill = true;
+
+            if ($variant) {
+                // Does this variant already exist in our permutations?
+                // if so we want to mark it as new but
+                $existing = collect($variantPermutations)
+                    ->first(
+                        fn ($p) => $p['variant_id'] == $variant['id']
+                    );
+
+                // Now what?
+                if ($existing) {
+                    $diff = array_diff_assoc($permutation, $variant['values']);
+                    $sku = $existing['sku'].'-'.implode('-', array_values($diff));
+                    $variantId = null;
+                    $copiedFrom = $variant['id'];
+                }
+
+                if ($existing && ! $fillMissing) {
+                    $shouldFill = false;
+                }
+            }
+
+            if ($shouldFill) {
+                $variantPermutations[] = [
+                    'key' => Str::random(),
+                    'variant_id' => $variantId,
+                    'copied_id' => $copiedFrom,
+                    'sku' => $sku,
+                    'price' => $variant['price'] ?? 0,
+                    'stock' => $variant['stock'] ?? 0,
+                    'values' => $permutation,
+                ];
+            }
+        }
+
+        return $variantPermutations;
+    }
+}
diff --git a/packages/admin/src/Filament/Resources/ActivityResource/Pages/ViewActivity.php b/packages/admin/src/Filament/Resources/ActivityResource/Pages/ViewActivity.php
index 44cab11384..e0b7d61175 100644
--- a/packages/admin/src/Filament/Resources/ActivityResource/Pages/ViewActivity.php
+++ b/packages/admin/src/Filament/Resources/ActivityResource/Pages/ViewActivity.php
@@ -2,10 +2,10 @@
 
 namespace Lunar\Admin\Filament\Resources\ActivityResource\Pages;
 
-use Filament\Resources\Pages\ViewRecord;
 use Lunar\Admin\Filament\Resources\ActivityResource;
+use Lunar\Admin\Support\Pages\BaseViewRecord;
 
-class ViewActivity extends ViewRecord
+class ViewActivity extends BaseViewRecord
 {
     protected static string $resource = ActivityResource::class;
 }
diff --git a/packages/admin/src/Filament/Resources/AttributeGroupResource/RelationManagers/AttributesRelationManager.php b/packages/admin/src/Filament/Resources/AttributeGroupResource/RelationManagers/AttributesRelationManager.php
index 6a1c83824a..332703a358 100644
--- a/packages/admin/src/Filament/Resources/AttributeGroupResource/RelationManagers/AttributesRelationManager.php
+++ b/packages/admin/src/Filament/Resources/AttributeGroupResource/RelationManagers/AttributesRelationManager.php
@@ -27,7 +27,7 @@ public function form(Form $form): Form
     {
         return $form
             ->schema([
-                Forms\Components\TextInput::make('name.en')
+                Forms\Components\TextInput::make('name.en') // TODO: localise
                     ->label(
                         __('lunarpanel::attribute.form.name.label')
                     )
@@ -40,6 +40,14 @@ public function form(Form $form): Form
                         }
                         $set('handle', Str::slug($state));
                     }),
+                Forms\Components\TextInput::make('description.en') // TODO: localise
+                    ->label(
+                        __('lunarpanel::attribute.form.description.label')
+                    )
+                    ->helperText(
+                        __('lunarpanel::attribute.form.description.helper')
+                    )
+                    ->maxLength(255),
                 Forms\Components\TextInput::make('handle')
                     ->label(
                         __('lunarpanel::attribute.form.handle.label')
@@ -96,6 +104,9 @@ public function table(Table $table): Table
                 Tables\Columns\TextColumn::make('name.en')->label(
                     __('lunarpanel::attribute.table.name.label')
                 ),
+                Tables\Columns\TextColumn::make('description.en')->label(
+                    __('lunarpanel::attribute.table.description.label')
+                ),
                 Tables\Columns\TextColumn::make('handle')
                     ->label(
                         __('lunarpanel::attribute.table.handle.label')
diff --git a/packages/admin/src/Filament/Resources/BrandResource/Pages/EditBrand.php b/packages/admin/src/Filament/Resources/BrandResource/Pages/EditBrand.php
index a3b6a9cbfe..686829ca4e 100644
--- a/packages/admin/src/Filament/Resources/BrandResource/Pages/EditBrand.php
+++ b/packages/admin/src/Filament/Resources/BrandResource/Pages/EditBrand.php
@@ -16,9 +16,4 @@ protected function getDefaultHeaderActions(): array
             Actions\DeleteAction::make(),
         ];
     }
-
-    protected function getRedirectUrl(): string
-    {
-        return $this->getResource()::getUrl('index');
-    }
 }
diff --git a/packages/admin/src/Filament/Resources/ChannelResource/Pages/ListChannels.php b/packages/admin/src/Filament/Resources/ChannelResource/Pages/ListChannels.php
index 06fffa13d4..d4ce4bc81d 100644
--- a/packages/admin/src/Filament/Resources/ChannelResource/Pages/ListChannels.php
+++ b/packages/admin/src/Filament/Resources/ChannelResource/Pages/ListChannels.php
@@ -10,7 +10,7 @@ class ListChannels extends BaseListRecords
 {
     protected static string $resource = ChannelResource::class;
 
-    protected function getHeaderActions(): array
+    protected function getDefaultHeaderActions(): array
     {
         return [
             Actions\CreateAction::make(),
diff --git a/packages/admin/src/Filament/Resources/CollectionGroupResource/Pages/ListCollectionGroups.php b/packages/admin/src/Filament/Resources/CollectionGroupResource/Pages/ListCollectionGroups.php
index d370a75189..64537bdd73 100644
--- a/packages/admin/src/Filament/Resources/CollectionGroupResource/Pages/ListCollectionGroups.php
+++ b/packages/admin/src/Filament/Resources/CollectionGroupResource/Pages/ListCollectionGroups.php
@@ -3,14 +3,14 @@
 namespace Lunar\Admin\Filament\Resources\CollectionGroupResource\Pages;
 
 use Filament\Actions;
-use Filament\Resources\Pages\ListRecords;
 use Lunar\Admin\Filament\Resources\CollectionGroupResource;
+use Lunar\Admin\Support\Pages\BaseListRecords;
 
-class ListCollectionGroups extends ListRecords
+class ListCollectionGroups extends BaseListRecords
 {
     protected static string $resource = CollectionGroupResource::class;
 
-    protected function getHeaderActions(): array
+    protected function getDefaultHeaderActions(): array
     {
         return [
             Actions\CreateAction::make(),
diff --git a/packages/admin/src/Filament/Resources/CollectionResource/Pages/ListCollections.php b/packages/admin/src/Filament/Resources/CollectionResource/Pages/ListCollections.php
index ce671637d4..e71d6b519d 100644
--- a/packages/admin/src/Filament/Resources/CollectionResource/Pages/ListCollections.php
+++ b/packages/admin/src/Filament/Resources/CollectionResource/Pages/ListCollections.php
@@ -2,10 +2,10 @@
 
 namespace Lunar\Admin\Filament\Resources\CollectionResource\Pages;
 
-use Filament\Resources\Pages\ListRecords;
 use Lunar\Admin\Filament\Resources\CollectionResource;
+use Lunar\Admin\Support\Pages\BaseListRecords;
 
-class ListCollections extends ListRecords
+class ListCollections extends BaseListRecords
 {
     protected static string $resource = CollectionResource::class;
 
@@ -14,7 +14,7 @@ public function mount(): void
         abort(404);
     }
 
-    protected function getHeaderActions(): array
+    protected function getDefaultHeaderActions(): array
     {
         return [];
     }
diff --git a/packages/admin/src/Filament/Resources/CurrencyResource/Pages/ListCurrencies.php b/packages/admin/src/Filament/Resources/CurrencyResource/Pages/ListCurrencies.php
index eed3a7fdd3..b399e3eb1a 100644
--- a/packages/admin/src/Filament/Resources/CurrencyResource/Pages/ListCurrencies.php
+++ b/packages/admin/src/Filament/Resources/CurrencyResource/Pages/ListCurrencies.php
@@ -10,7 +10,7 @@ class ListCurrencies extends BaseListRecords
 {
     protected static string $resource = CurrencyResource::class;
 
-    protected function getHeaderActions(): array
+    protected function getDefaultHeaderActions(): array
     {
         return [
             Actions\CreateAction::make(),
diff --git a/packages/admin/src/Filament/Resources/CustomerGroupResource/Pages/ListCustomerGroups.php b/packages/admin/src/Filament/Resources/CustomerGroupResource/Pages/ListCustomerGroups.php
index 2c638d4276..30a8ff9ff4 100644
--- a/packages/admin/src/Filament/Resources/CustomerGroupResource/Pages/ListCustomerGroups.php
+++ b/packages/admin/src/Filament/Resources/CustomerGroupResource/Pages/ListCustomerGroups.php
@@ -10,7 +10,7 @@ class ListCustomerGroups extends BaseListRecords
 {
     protected static string $resource = CustomerGroupResource::class;
 
-    protected function getHeaderActions(): array
+    protected function getDefaultHeaderActions(): array
     {
         return [
             Actions\CreateAction::make(),
diff --git a/packages/admin/src/Filament/Resources/CustomerResource.php b/packages/admin/src/Filament/Resources/CustomerResource.php
index c9036fc822..0419f0ff6a 100644
--- a/packages/admin/src/Filament/Resources/CustomerResource.php
+++ b/packages/admin/src/Filament/Resources/CustomerResource.php
@@ -195,7 +195,8 @@ protected static function getDefaultTable(Table $table): Table
                 Tables\Actions\BulkActionGroup::make([
                     Tables\Actions\DeleteBulkAction::make(),
                 ]),
-            ]);
+            ])
+            ->selectCurrentPageOnly();
     }
 
     public static function getDefaultRelations(): array
diff --git a/packages/admin/src/Filament/Resources/OrderResource.php b/packages/admin/src/Filament/Resources/OrderResource.php
index 07a9cedfd4..257c45cee0 100644
--- a/packages/admin/src/Filament/Resources/OrderResource.php
+++ b/packages/admin/src/Filament/Resources/OrderResource.php
@@ -11,6 +11,7 @@
 use Lunar\Admin\Filament\Resources\OrderResource\Pages;
 use Lunar\Admin\Filament\Resources\OrderResource\Pages\ManageOrder;
 use Lunar\Admin\Support\Actions\Orders\UpdateStatusBulkAction;
+use Lunar\Admin\Support\CustomerStatus;
 use Lunar\Admin\Support\OrderStatus;
 use Lunar\Admin\Support\Resources\BaseResource;
 use Lunar\Models\Order;
@@ -89,6 +90,12 @@ public static function getTableColumns(): array
                 ->label(__('lunarpanel::order.table.customer_reference.label')),
             Tables\Columns\TextColumn::make('shippingAddress.fullName')
                 ->label(__('lunarpanel::order.table.customer.label')),
+            Tables\Columns\TextColumn::make('new_customer')
+                ->label(__('lunarpanel::order.table.new_customer.label'))
+                ->formatStateUsing(fn (bool $state) => CustomerStatus::getLabel($state))
+                ->color(fn (bool $state) => CustomerStatus::getColor($state))
+                ->icon(fn (bool $state) => CustomerStatus::getIcon($state))
+                ->badge(),
             Tables\Columns\TextColumn::make('shippingAddress.postcode')
                 ->label(__('lunarpanel::order.table.postcode.label')),
             Tables\Columns\TextColumn::make('shippingAddress.contact_email')
diff --git a/packages/admin/src/Filament/Resources/OrderResource/Pages/ListOrders.php b/packages/admin/src/Filament/Resources/OrderResource/Pages/ListOrders.php
index 19c1d16669..203dcafe4d 100644
--- a/packages/admin/src/Filament/Resources/OrderResource/Pages/ListOrders.php
+++ b/packages/admin/src/Filament/Resources/OrderResource/Pages/ListOrders.php
@@ -13,7 +13,7 @@ class ListOrders extends BaseListRecords
 {
     protected static string $resource = OrderResource::class;
 
-    protected function getHeaderActions(): array
+    protected function getDefaultHeaderActions(): array
     {
         return [
             // Actions\CreateAction::make(),
diff --git a/packages/admin/src/Filament/Resources/OrderResource/Pages/ManageOrder.php b/packages/admin/src/Filament/Resources/OrderResource/Pages/ManageOrder.php
index 6e9e5746b8..74c7473c2d 100644
--- a/packages/admin/src/Filament/Resources/OrderResource/Pages/ManageOrder.php
+++ b/packages/admin/src/Filament/Resources/OrderResource/Pages/ManageOrder.php
@@ -14,7 +14,6 @@
 use Filament\Infolists\Components\TextEntry\TextEntrySize;
 use Filament\Infolists\Infolist;
 use Filament\Notifications\Notification;
-use Filament\Resources\Pages\ViewRecord;
 use Filament\Support\Colors\Color;
 use Filament\Support\Enums\ActionSize;
 use Filament\Support\Enums\FontWeight;
@@ -35,6 +34,7 @@
 use Lunar\Admin\Support\Infolists\Components\Timeline;
 use Lunar\Admin\Support\Infolists\Components\Transaction as InfolistsTransaction;
 use Lunar\Admin\Support\OrderStatus;
+use Lunar\Admin\Support\Pages\BaseViewRecord;
 use Lunar\DataTypes\Price;
 use Lunar\Models\Country;
 use Lunar\Models\State;
@@ -55,7 +55,7 @@
  * @property float $availableToRefund
  * @property bool $canBeRefunded
  */
-class ManageOrder extends ViewRecord
+class ManageOrder extends BaseViewRecord
 {
     use CanDispatchActivityUpdated;
 
@@ -655,7 +655,7 @@ public function getEditAddressAction(string $type): Action
             ->slideOver();
     }
 
-    protected function getHeaderActions(): array
+    protected function getDefaultHeaderActions(): array
     {
         return [
             $this->getCaptureAction(),
diff --git a/packages/admin/src/Filament/Resources/ProductOptionResource.php b/packages/admin/src/Filament/Resources/ProductOptionResource.php
index 1d5a2f7804..871ee8e6b0 100644
--- a/packages/admin/src/Filament/Resources/ProductOptionResource.php
+++ b/packages/admin/src/Filament/Resources/ProductOptionResource.php
@@ -46,7 +46,6 @@ protected static function getMainFormComponents(): array
             static::getNameFormComponent(),
             static::getLabelFormComponent(),
             static::getHandleFormComponent(),
-            static::getPositionFormComponent(),
         ];
     }
 
@@ -76,29 +75,21 @@ protected static function getHandleFormComponent(): Component
             ->maxLength(255);
     }
 
-    protected static function getPositionFormComponent(): Component
-    {
-        return Forms\Components\TextInput::make('position')
-            ->label(__('lunarpanel::productoption.form.position.label'))
-            ->numeric()
-            ->minValue(1)
-            ->maxValue(100)
-            ->required();
-    }
-
     public static function getDefaultTable(Table $table): Table
     {
         return $table
             ->columns([
-                Tables\Columns\TextColumn::make('name.en')  // TODO: Need to determine correct way to localise, maybe custom column type?
-                    ->label(__('lunarpanel::productoption.table.name.label')),
-                Tables\Columns\TextColumn::make('label.en')  // TODO: Need to determine correct way to localise, maybe custom column type?
+                Tables\Columns\TextColumn::make('name')
+                    ->formatStateUsing(
+                        fn (ProductOption $option) => $option->translate('name'),
+                    )->label(__('lunarpanel::productoption.table.name.label')),
+                Tables\Columns\TextColumn::make('label')
+                    ->formatStateUsing(
+                        fn (ProductOption $option) => $option->translate('label'),
+                    )
                     ->label(__('lunarpanel::productoption.table.label.label')),
                 Tables\Columns\TextColumn::make('handle')
                     ->label(__('lunarpanel::productoption.table.handle.label')),
-                Tables\Columns\TextColumn::make('position')
-                    ->label(__('lunarpanel::productoption.table.position.label'))
-                    ->sortable(),
             ])
             ->filters([
                 //
@@ -111,9 +102,10 @@ public static function getDefaultTable(Table $table): Table
                     Tables\Actions\DeleteBulkAction::make(),
                 ]),
             ])
-            ->searchable()
-            ->defaultSort('position', 'asc')
-            ->reorderable('position');
+            ->modifyQueryUsing(
+                fn ($query) => $query->shared()
+            )
+            ->searchable();
     }
 
     public static function getRelations(): array
diff --git a/packages/admin/src/Filament/Resources/ProductOptionResource/Pages/CreateProductOption.php b/packages/admin/src/Filament/Resources/ProductOptionResource/Pages/CreateProductOption.php
index bde975dca9..b362ee385c 100644
--- a/packages/admin/src/Filament/Resources/ProductOptionResource/Pages/CreateProductOption.php
+++ b/packages/admin/src/Filament/Resources/ProductOptionResource/Pages/CreateProductOption.php
@@ -8,4 +8,11 @@
 class CreateProductOption extends BaseCreateRecord
 {
     protected static string $resource = ProductOptionResource::class;
+
+    protected function mutateFormDataBeforeCreate(array $data): array
+    {
+        $data['shared'] = true;
+
+        return $data;
+    }
 }
diff --git a/packages/admin/src/Filament/Resources/ProductOptionResource/RelationManagers/ValuesRelationManager.php b/packages/admin/src/Filament/Resources/ProductOptionResource/RelationManagers/ValuesRelationManager.php
index 16f3f66016..12e130a4e6 100644
--- a/packages/admin/src/Filament/Resources/ProductOptionResource/RelationManagers/ValuesRelationManager.php
+++ b/packages/admin/src/Filament/Resources/ProductOptionResource/RelationManagers/ValuesRelationManager.php
@@ -7,12 +7,17 @@
 use Filament\Resources\RelationManagers\RelationManager;
 use Filament\Tables;
 use Filament\Tables\Table;
+use Illuminate\Database\Eloquent\Model;
+use Lunar\Models\ProductOptionValue;
 
 class ValuesRelationManager extends RelationManager
 {
     protected static string $relationship = 'values';
 
-    protected static ?string $recordTitleAttribute = 'name.en';  // TODO: localise somehow
+    public function getTableRecordTitle(Model $record): ?string
+    {
+        return $record->translate('name');
+    }
 
     public function form(Form $form): Form
     {
@@ -29,8 +34,10 @@ public function table(Table $table): Table
         return $table
 
             ->columns([
-                Tables\Columns\TextColumn::make('name.en'),
-                Tables\Columns\TextColumn::make('handle'),
+                Tables\Columns\TextColumn::make('name')
+                    ->formatStateUsing(
+                        fn (ProductOptionValue $productOption) => $productOption->translate('name')
+                    ),
                 Tables\Columns\TextColumn::make('position'),
             ])
             ->filters([
diff --git a/packages/admin/src/Filament/Resources/ProductResource.php b/packages/admin/src/Filament/Resources/ProductResource.php
index e884be8afe..9c7369c9bf 100644
--- a/packages/admin/src/Filament/Resources/ProductResource.php
+++ b/packages/admin/src/Filament/Resources/ProductResource.php
@@ -18,6 +18,8 @@
 use Illuminate\Database\Eloquent\Model;
 use Lunar\Admin\Filament\Resources\ProductResource\Pages;
 use Lunar\Admin\Filament\Resources\ProductResource\RelationManagers\CustomerGroupRelationManager;
+use Lunar\Admin\Filament\Resources\ProductResource\Widgets\ProductOptionsWidget;
+use Lunar\Admin\Filament\Widgets\Products\VariantSwitcherTable;
 use Lunar\Admin\Support\Forms\Components\Attributes;
 use Lunar\Admin\Support\Forms\Components\Tags as TagsComponent;
 use Lunar\Admin\Support\RelationManagers\ChannelRelationManager;
@@ -34,6 +36,8 @@ class ProductResource extends BaseResource
 
     protected static ?string $model = Product::class;
 
+    protected static ?string $recordTitleAttribute = 'recordTitle';
+
     protected static ?int $navigationSort = 1;
 
     protected static int $globalSearchResultsLimit = 5;
@@ -77,6 +81,14 @@ public static function getRecordSubNavigation(Page $page): array
         ]);
     }
 
+    public static function getWidgets(): array
+    {
+        return [
+            ProductOptionsWidget::class,
+            VariantSwitcherTable::class,
+        ];
+    }
+
     public static function getDefaultForm(Form $form): Form
     {
         return $form
@@ -236,7 +248,6 @@ protected static function getTableColumns(): array
                 ->formatStateUsing(fn (Model $record): string => $record->translateAttribute('name'))
                 ->limit(50)
                 ->tooltip(function (Tables\Columns\TextColumn $column, Model $record): ?string {
-                    $state = $column->getState();
 
                     if (strlen($record->translateAttribute('name')) <= $column->getCharacterLimit()) {
                         return null;
@@ -252,6 +263,22 @@ protected static function getTableColumns(): array
                 ->searchable(),
             Tables\Columns\TextColumn::make('variants.sku')
                 ->label(__('lunarpanel::product.table.sku.label'))
+                ->tooltip(function (Tables\Columns\TextColumn $column, Model $record): ?string {
+
+                    if ($record->variants->count() <= $column->getListLimit()) {
+                        return null;
+                    }
+
+                    if ($record->variants->count() > 30) {
+                        $record->variants = $record->variants->slice(0, 30);
+                    }
+
+                    return $record->variants
+                        ->map(fn ($variant) => $variant->sku)
+                        ->implode(', ');
+                })
+                ->listWithLineBreaks()
+                ->limitList(1)
                 ->toggleable(),
             Tables\Columns\TextColumn::make('variants_sum_stock')
                 ->label(__('lunarpanel::product.table.stock.label'))
diff --git a/packages/admin/src/Filament/Resources/ProductResource/Pages/ListProducts.php b/packages/admin/src/Filament/Resources/ProductResource/Pages/ListProducts.php
index 22d11b789f..3434203ecd 100644
--- a/packages/admin/src/Filament/Resources/ProductResource/Pages/ListProducts.php
+++ b/packages/admin/src/Filament/Resources/ProductResource/Pages/ListProducts.php
@@ -78,7 +78,8 @@ public static function createRecord(array $data, string $model): Model
             'sku' => $data['sku'],
         ]);
         $variant->prices()->create([
-            'tier' => 1,
+            'quantity_break' => 1,
+            'currency_id' => $currency->id,
             'price' => (int) bcmul($data['base_price'], $currency->factor),
         ]);
         DB::commit();
diff --git a/packages/admin/src/Filament/Resources/ProductResource/Pages/ManageProductIdentifiers.php b/packages/admin/src/Filament/Resources/ProductResource/Pages/ManageProductIdentifiers.php
index 91499b384b..6e2215dbd3 100644
--- a/packages/admin/src/Filament/Resources/ProductResource/Pages/ManageProductIdentifiers.php
+++ b/packages/admin/src/Filament/Resources/ProductResource/Pages/ManageProductIdentifiers.php
@@ -3,12 +3,12 @@
 namespace Lunar\Admin\Filament\Resources\ProductResource\Pages;
 
 use Filament\Forms\Components\Section;
-use Filament\Forms\Components\TextInput;
 use Filament\Forms\Form;
 use Filament\Support\Facades\FilamentIcon;
 use Illuminate\Contracts\Support\Htmlable;
 use Illuminate\Database\Eloquent\Model;
 use Lunar\Admin\Filament\Resources\ProductResource;
+use Lunar\Admin\Filament\Resources\ProductVariantResource;
 use Lunar\Admin\Support\Pages\BaseEditRecord;
 use Lunar\Models\ProductVariant;
 
@@ -94,24 +94,15 @@ public function form(Form $form): Form
 
         return $form->schema([
             Section::make()->schema([
-                TextInput::make('sku')
-                    ->label(
-                        __('lunarpanel::product.pages.identifiers.form.sku.label')
-                    )
+                ProductVariantResource::getSkuFormComponent()
                     ->live()->unique(
                         table: fn () => $variant->getTable(),
                         ignorable: $variant,
                         ignoreRecord: true,
                     ),
-                TextInput::make('gtin')->label(
-                    __('lunarpanel::product.pages.identifiers.form.gtin.label')
-                ),
-                TextInput::make('mpn')->label(
-                    __('lunarpanel::product.pages.identifiers.form.mpn.label')
-                ),
-                TextInput::make('ean')->label(
-                    __('lunarpanel::product.pages.identifiers.form.ean.label')
-                ),
+                ProductVariantResource::getGtinFormComponent(),
+                ProductVariantResource::getMpnFormComponent(),
+                ProductVariantResource::getEanFormComponent(),
             ])->columns(1),
         ])->statePath('');
     }
diff --git a/packages/admin/src/Filament/Resources/ProductResource/Pages/ManageProductInventory.php b/packages/admin/src/Filament/Resources/ProductResource/Pages/ManageProductInventory.php
index 405ab713a5..893a899b25 100644
--- a/packages/admin/src/Filament/Resources/ProductResource/Pages/ManageProductInventory.php
+++ b/packages/admin/src/Filament/Resources/ProductResource/Pages/ManageProductInventory.php
@@ -2,14 +2,12 @@
 
 namespace Lunar\Admin\Filament\Resources\ProductResource\Pages;
 
-use Filament\Forms\Components\Section;
-use Filament\Forms\Components\Select;
-use Filament\Forms\Components\TextInput;
 use Filament\Forms\Form;
 use Filament\Support\Facades\FilamentIcon;
 use Illuminate\Contracts\Support\Htmlable;
 use Illuminate\Database\Eloquent\Model;
 use Lunar\Admin\Filament\Resources\ProductResource;
+use Lunar\Admin\Filament\Resources\ProductVariantResource\Pages\ManageVariantInventory;
 use Lunar\Admin\Support\Pages\BaseEditRecord;
 use Lunar\Models\ProductVariant;
 
@@ -96,50 +94,7 @@ protected function getFormActions(): array
 
     public function form(Form $form): Form
     {
-        $variant = $this->getVariant();
-
-        return $form->schema([
-            Section::make()->schema([
-                TextInput::make('stock')
-                    ->label(
-                        __('lunarpanel::product.pages.inventory.form.stock.label')
-                    )->numeric(),
-                TextInput::make('backorder')
-                    ->label(
-                        __('lunarpanel::product.pages.inventory.form.backorder.label')
-                    )->numeric(),
-                Select::make('purchasable')
-                    ->options([
-                        'always' => __('lunarpanel::product.pages.inventory.form.purchasable.options.always'),
-                        'in_stock' => __('lunarpanel::product.pages.inventory.form.purchasable.options.in_stock'),
-                        'backorder' => __('lunarpanel::product.pages.inventory.form.purchasable.options.backorder'),
-                    ])
-                    ->label(
-                        __('lunarpanel::product.pages.inventory.form.purchasable.label')
-                    ),
-                TextInput::make('unit_quantity')
-                    ->label(
-                        __('lunarpanel::product.pages.inventory.form.unit_quantity.label')
-                    )->helperText(
-                        __('lunarpanel::product.pages.inventory.form.unit_quantity.helper_text')
-                    )->numeric(),
-                TextInput::make('quantity_increment')
-                    ->label(
-                        __('lunarpanel::product.pages.inventory.form.quantity_increment.label')
-                    )->helperText(
-                        __('lunarpanel::product.pages.inventory.form.quantity_increment.helper_text')
-                    )->numeric(),
-                TextInput::make('min_quantity')
-                    ->label(
-                        __('lunarpanel::product.pages.inventory.form.min_quantity.label')
-                    )->helperText(
-                        __('lunarpanel::product.pages.inventory.form.min_quantity.helper_text')
-                    )->numeric(),
-            ])->columns([
-                'sm' => 1,
-                'xl' => 3,
-            ]),
-        ])->statePath('');
+        return (new ManageVariantInventory())->form($form)->statePath('');
     }
 
     public function getRelationManagers(): array
diff --git a/packages/admin/src/Filament/Resources/ProductResource/Pages/ManageProductPricing.php b/packages/admin/src/Filament/Resources/ProductResource/Pages/ManageProductPricing.php
index a7e344cf37..5a674cefed 100644
--- a/packages/admin/src/Filament/Resources/ProductResource/Pages/ManageProductPricing.php
+++ b/packages/admin/src/Filament/Resources/ProductResource/Pages/ManageProductPricing.php
@@ -59,15 +59,15 @@ public function form(Form $form): Form
                         $owner = $this->getOwnerRecord();
 
                         return $rule->where('customer_group_id', $get('customer_group_id'))
-                            ->where('tier', $get('tier'))
+                            ->where('quantity_break', $get('quantity_break'))
                             ->where('currency_id', $get('currency_id'))
                             ->where('priceable_type', get_class($owner))
                             ->where('priceable_id', $owner->id);
                     }
                 )->required(),
-                Forms\Components\TextInput::make('tier')
+                Forms\Components\TextInput::make('quantity_break')
                     ->label(
-                        __('lunarpanel::relationmanagers.pricing.form.tier.label')
+                        __('lunarpanel::relationmanagers.pricing.form.quantity_break.label')
                     )->numeric()->minValue(1)->required(),
                 Forms\Components\Select::make('currency_id')
                     ->label(
@@ -85,7 +85,7 @@ public function table(Table $table): Table
         return $table
             ->recordTitleAttribute('name')
             ->modifyQueryUsing(
-                fn ($query) => $query->orderBy('tier', 'asc')
+                fn ($query) => $query->orderBy('quantity_break', 'asc')
             )
             ->columns([
                 Tables\Columns\TextColumn::make('price')
@@ -97,8 +97,8 @@ public function table(Table $table): Table
                 Tables\Columns\TextColumn::make('currency.code')->label(
                     __('lunarpanel::relationmanagers.pricing.table.currency.label')
                 ),
-                Tables\Columns\TextColumn::make('tier')->label(
-                    __('lunarpanel::relationmanagers.pricing.table.tier.label')
+                Tables\Columns\TextColumn::make('quantity_break')->label(
+                    __('lunarpanel::relationmanagers.pricing.table.quantity_break.label')
                 ),
                 Tables\Columns\TextColumn::make('customerGroup.name')->label(
                     __('lunarpanel::relationmanagers.pricing.table.customer_group.label')
@@ -108,11 +108,11 @@ public function table(Table $table): Table
                 Tables\Filters\SelectFilter::make('currency')
                     ->relationship(name: 'currency', titleAttribute: 'name')
                     ->preload(),
-                Tables\Filters\SelectFilter::make('tier')->options(
+                Tables\Filters\SelectFilter::make('quantity_break')->options(
                     Price::where('priceable_id', $this->getOwnerRecord()->id)
                         ->where('priceable_type', get_class($this->getOwnerRecord()))
                         ->get()
-                        ->pluck('tier', 'tier')
+                        ->pluck('quantity_break', 'quantity_break')
                 ),
             ])
             ->headerActions([
diff --git a/packages/admin/src/Filament/Resources/ProductResource/Pages/ManageProductShipping.php b/packages/admin/src/Filament/Resources/ProductResource/Pages/ManageProductShipping.php
index cbd5c20ad1..fcf9429219 100644
--- a/packages/admin/src/Filament/Resources/ProductResource/Pages/ManageProductShipping.php
+++ b/packages/admin/src/Filament/Resources/ProductResource/Pages/ManageProductShipping.php
@@ -11,6 +11,7 @@
 use Illuminate\Contracts\Support\Htmlable;
 use Illuminate\Database\Eloquent\Model;
 use Lunar\Admin\Filament\Resources\ProductResource;
+use Lunar\Admin\Filament\Resources\ProductVariantResource\Pages\ManageVariantShipping;
 use Lunar\Admin\Support\Pages\BaseEditRecord;
 use Lunar\Models\ProductVariant;
 use Marvinosswald\FilamentInputSelectAffix\TextInputSelectAffix;
@@ -83,7 +84,20 @@ protected function handleRecordUpdate(Model $record, array $data): Model
             ...[
                 'shippable' => $this->shippable,
                 'volume_unit' => 'l',
-                'volume_value' => $this->volume,
+                'volume_value' => ManageVariantShipping::getVolume(
+                    [
+                        'value' => $this->dimensions['width_value'],
+                        'unit' => $this->dimensions['width_unit'],
+                    ],
+                    [
+                        'value' => $this->dimensions['length_value'],
+                        'unit' => $this->dimensions['length_unit'],
+                    ],
+                    [
+                        'value' => $this->dimensions['height_value'],
+                        'unit' => $this->dimensions['height_unit'],
+                    ]
+                ),
             ],
             ...$this->dimensions,
         ]);
@@ -103,30 +117,6 @@ protected function getFormActions(): array
         ];
     }
 
-    public function getVolumeProperty()
-    {
-        $dimensions = $this->dimensions;
-
-        $width = Converter::value($dimensions['width_value'])
-            ->from('length.'.$dimensions['width_unit'])
-            ->to('length.cm')
-            ->convert()
-            ->getValue();
-        $length = Converter::value($dimensions['length_value'])
-            ->from('length.'.$dimensions['length_unit'])
-            ->to('length.cm')
-            ->convert()
-            ->getValue();
-
-        $height = Converter::value($dimensions['height_value'])
-            ->from('length.'.$dimensions['height_unit'])
-            ->to('length.cm')
-            ->convert()
-            ->getValue();
-
-        return Converter::from('volume.ml')->to('volume.l')->value($length * $width * $height)->convert()->getValue();
-    }
-
     public function form(Form $form): Form
     {
         $measurements = Converter::getMeasurements();
diff --git a/packages/admin/src/Filament/Resources/ProductResource/Pages/ManageProductVariants.php b/packages/admin/src/Filament/Resources/ProductResource/Pages/ManageProductVariants.php
index 4c7e47b78e..79ef65c030 100644
--- a/packages/admin/src/Filament/Resources/ProductResource/Pages/ManageProductVariants.php
+++ b/packages/admin/src/Filament/Resources/ProductResource/Pages/ManageProductVariants.php
@@ -18,6 +18,13 @@ class ManageProductVariants extends ManageRelatedRecords
 
     protected static ?string $title = 'Variants';
 
+    protected function getHeaderWidgets(): array
+    {
+        return [
+            ProductResource\Widgets\ProductOptionsWidget::class,
+        ];
+    }
+
     public static function getNavigationIcon(): ?string
     {
         return FilamentIcon::resolve('lunar::product-variants');
@@ -54,6 +61,8 @@ public function form(Form $form): Form
 
     public function table(Table $table): Table
     {
+        return $table;
+
         return $table
             ->recordTitleAttribute('name')
             ->columns([
diff --git a/packages/admin/src/Filament/Resources/ProductResource/RelationManagers/CustomerGroupRelationManager.php b/packages/admin/src/Filament/Resources/ProductResource/RelationManagers/CustomerGroupRelationManager.php
index 77e71a254c..8c4ca12437 100644
--- a/packages/admin/src/Filament/Resources/ProductResource/RelationManagers/CustomerGroupRelationManager.php
+++ b/packages/admin/src/Filament/Resources/ProductResource/RelationManagers/CustomerGroupRelationManager.php
@@ -10,6 +10,8 @@
 
 class CustomerGroupRelationManager extends RelationManager
 {
+    protected static bool $isLazy = false;
+
     protected static string $relationship = 'customerGroups';
 
     public function isReadOnly(): bool
diff --git a/packages/admin/src/Filament/Resources/ProductResource/Widgets/ProductOptionsWidget.php b/packages/admin/src/Filament/Resources/ProductResource/Widgets/ProductOptionsWidget.php
new file mode 100644
index 0000000000..541b93204d
--- /dev/null
+++ b/packages/admin/src/Filament/Resources/ProductResource/Widgets/ProductOptionsWidget.php
@@ -0,0 +1,465 @@
+<?php
+
+namespace Lunar\Admin\Filament\Resources\ProductResource\Widgets;
+
+use Awcodes\Shout\Components\Shout;
+use Filament\Actions\Action;
+use Filament\Actions\Concerns\InteractsWithActions;
+use Filament\Actions\Contracts\HasActions;
+use Filament\Forms\Components\Select;
+use Filament\Forms\Concerns\InteractsWithForms;
+use Filament\Forms\Contracts\HasForms;
+use Filament\Notifications\Notification;
+use Filament\Widgets\StatsOverviewWidget as BaseWidget;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Support\Str;
+use Lunar\Admin\Actions\Products\MapVariantsToProductOptions;
+use Lunar\Admin\Filament\Resources\ProductVariantResource;
+use Lunar\Facades\DB;
+use Lunar\Models\Language;
+use Lunar\Models\ProductOption;
+use Lunar\Models\ProductOptionValue;
+use Lunar\Models\ProductVariant;
+
+class ProductOptionsWidget extends BaseWidget implements HasActions, HasForms
+{
+    use InteractsWithActions;
+    use InteractsWithForms;
+
+    protected static string $view = 'lunarpanel::resources.product-resource.widgets.product-options';
+
+    public ?Model $record;
+
+    public array $variants = [];
+
+    /**
+     * The product options which are being actively configured.
+     */
+    public array $configuredOptions = [];
+
+    public bool $configuringOptions = false;
+
+    protected static bool $isLazy = false;
+
+    public function mount()
+    {
+        $this->configureBaseOptions();
+    }
+
+    public function addSharedOptionAction()
+    {
+        $existing = collect($this->configuredOptions)->pluck('id');
+        $options = ProductOption::whereNotIn('id', $existing)
+            ->shared()
+            ->get();
+
+        return Action::make('addSharedOption')
+            ->form([
+                Shout::make('no_shared_components')
+                    ->content(
+                        __('lunarpanel::productoption.widgets.product-options.actions.add-shared-option.form.no_shared_components.label')
+                    )
+                    ->visible(
+                        $options->isEmpty()
+                    ),
+                Select::make('product_option')
+                    ->options(
+                        fn () => $options->mapWithKeys(
+                            fn ($option) => [$option->id => $option->translate('name')]
+                        )
+                    )->label(
+                        __('lunarpanel::productoption.widgets.product-options.actions.add-shared-option.form.product_option.label')
+                    )->visible(
+                        $options->isNotEmpty()
+                    ),
+            ])->action(function (array $data) {
+                $productOption = ProductOption::with(['values'])->find($data['product_option']);
+                $this->configuredOptions[] = $this->mapOption(
+                    $productOption,
+                    $productOption->values->map(
+                        fn ($value) => $this->mapOptionValue($value, true)
+                    )->toArray()
+                );
+            });
+    }
+
+    public function configureBaseOptions(): void
+    {
+        $productOptions = $this->query()->get();
+
+        $sharedOptionIds = $productOptions->filter(
+            fn ($option) => $option->shared
+        )->pluck('id');
+
+        $disabledSharedOptionValues = ProductOptionValue::whereIn(
+            'product_option_id',
+            $sharedOptionIds
+        )->whereNotIn(
+            'id',
+            $productOptions->pluck('values')->flatten()->pluck('id')
+        )->get();
+
+        $options = [];
+
+        foreach ($productOptions as $productOption) {
+            $values = $productOption->values->map(function ($value) {
+                return $this->mapOptionValue($value, true);
+            })->merge(
+                $disabledSharedOptionValues->filter(
+                    fn ($value) => $value->product_option_id == $productOption->id
+                )->map(
+                    fn ($value) => $this->mapOptionValue($value, false)
+                )
+            )->sortBy('position')->values()->toArray();
+
+            $options[] = $this->mapOption($productOption, $values);
+        }
+
+        $this->configuredOptions = $options;
+
+        $this->mapVariantPermutations(fillMissing: false);
+    }
+
+    public function cancelOptionConfiguring(): void
+    {
+        $this->configuringOptions = false;
+        $this->configureBaseOptions();
+    }
+
+    public function query()
+    {
+        return $this->record->productOptions()
+            ->with('values', function ($query) {
+                $query->whereHas('variants', function ($relation) {
+                    $relation->whereIn($relation->getModel()->getTable().'.id', $this->record->variants()->pluck('id'));
+                });
+            });
+    }
+
+    public function addRestrictedOption()
+    {
+        $this->configuredOptions[] = [
+            'id' => null,
+            'value' => '',
+            'position' => count($this->configuredOptions) + 1,
+            'readonly' => false,
+            'option_values' => [
+                [
+                    'id' => null,
+                    'value' => '',
+                    'position' => 1,
+                    'enabled' => true,
+                ],
+            ],
+        ];
+    }
+
+    public function updateConfiguredOptions()
+    {
+        $this->validate([
+            'configuredOptions' => 'array',
+            'configuredOptions.*.value' => 'required|string',
+            'configuredOptions.*.option_values.*.value' => 'required|string',
+        ]);
+
+        // Go through each one and if a configuration has none enabled, then just
+        // remove it from the array.
+        $options = collect();
+
+        foreach ($this->configuredOptions as $configuredOption) {
+            $enabledCount = collect($configuredOption['option_values'])
+                ->filter(
+                    fn ($value) => $value['enabled']
+                )->count();
+
+            if ($enabledCount) {
+                $options->push($configuredOption);
+            }
+        }
+
+        $this->configuredOptions = $options->values()->toArray();
+
+        $this->mapVariantPermutations();
+
+        $this->configuringOptions = false;
+    }
+
+    public function removeVariant($key): void
+    {
+        unset($this->variants[$key]);
+    }
+
+    public function addOptionValue($path)
+    {
+        $option = $this->configuredOptions[$path];
+
+        if ($option['readonly']) {
+            return;
+        }
+
+        $this->configuredOptions[$path]['option_values'][] = [
+            'value' => '',
+            'position' => count($this->configuredOptions[$path]['option_values']) + 1,
+            'readonly' => false,
+            'enabled' => true,
+        ];
+    }
+
+    public function removeOptionValue($index, $valueIndex)
+    {
+        unset($this->configuredOptions[$index]['option_values'][$valueIndex]);
+    }
+
+    public function removeOption($index)
+    {
+        $options = collect($this->configuredOptions)->forget($index);
+        $this->configuredOptions = $options->values()->toArray();
+    }
+
+    public function updateValuePositions($optionKey, $rows)
+    {
+        $this->configuredOptions[$optionKey]['option_values'] = $rows;
+    }
+
+    public function updateOptionPositions($rows)
+    {
+        $this->configuredOptions = $rows;
+    }
+
+    public function mapVariantPermutations($fillMissing = true): void
+    {
+        $optionValues = collect($this->configuredOptions)
+            ->filter(
+                fn ($option) => $option['value']
+            )
+            ->mapWithKeys(
+                fn ($option) => [$option['value'] => collect($option['option_values'])
+                    ->filter(
+                        fn ($value) => $value['enabled']
+                    )
+                    ->map(
+                        fn ($value) => $value['value']
+                    )]
+            )->toArray();
+
+        $variants = $this->record->variants->load('values.option')->map(function ($variant) {
+            return [
+                'id' => $variant->id,
+                'sku' => $variant->sku,
+                'price' => $variant->basePrices->first()?->price->decimal ?: 0,
+                'stock' => $variant->stock,
+                'values' => $variant->values->mapWithKeys(
+                    fn ($value) => [$value->option->translate('name') => $value->translate('name')]
+                )->toArray(),
+            ];
+        })->toArray();
+
+        $this->variants = MapVariantsToProductOptions::map($optionValues, $variants, $fillMissing);
+    }
+
+    public function getHasNewVariantsProperty()
+    {
+        return collect($this->variants)
+            ->reject(
+                fn ($variant) => $variant['variant_id']
+            )->isNotEmpty();
+    }
+
+    protected function storeConfiguredOptions(): void
+    {
+        $language = Language::getDefault();
+        /**
+         * Go through our configured options and if they don't
+         * exist in the database i.e. they are new, create and map them
+         * so they are ready.
+         */
+        foreach ($this->configuredOptions as $optionIndex => $option) {
+
+            $optionModel = empty($option['id']) ?
+                new ProductOption([
+                    'shared' => false,
+                ]) :
+                ProductOption::find($option['id']);
+
+            $optionValue = $option['value'];
+
+            $optionModel->name = [
+                $language->code => $optionValue,
+            ];
+            $optionModel->label = [
+                $language->code => $optionValue,
+            ];
+            $optionModel->handle = Str::slug($optionValue);
+            $optionModel->save();
+
+            $this->configuredOptions[$optionIndex]['id'] = $optionModel->id;
+            $option['id'] = $optionModel->id;
+
+            foreach ($option['option_values'] as $optionValueIndex => $value) {
+                $optionValueModel = empty($value['id']) ?
+                    new ProductOptionValue([
+                        'product_option_id' => $option['id'],
+                    ]) :
+                    ProductOptionValue::find($value['id']);
+
+                $optionValueModel->name = [
+                    $language->code => $value['value'],
+                ];
+                $optionValueModel->position = $value['position'];
+                $optionValueModel->save();
+
+                $this->configuredOptions[$optionIndex]['option_values'][$optionValueIndex]['id'] =
+                    $optionValueModel->id;
+            }
+        }
+    }
+
+    protected function mapOptionValuesToIds(array $values): array
+    {
+        $valueIds = [];
+        foreach ($values as $option => $value) {
+            $configuredOption = collect(
+                $this->configuredOptions
+            )->first(
+                fn ($o) => $o['value'] == $option
+            );
+
+            $valueId = collect($configuredOption['option_values'])->first(
+                fn ($v) => $v['value'] == $value
+            )['id'];
+            $valueIds[] = $valueId;
+        }
+
+        return $valueIds;
+    }
+
+    public function saveVariantsAction()
+    {
+        return Action::make('saveVariants')
+            ->action(function () {
+                DB::beginTransaction();
+
+                $this->storeConfiguredOptions();
+
+                /**
+                 * If there are no variants, then all the configured option
+                 * have been removed. In this case we still want to keep a
+                 * variant at least one is needed for Lunar to function.
+                 */
+                if (! count($this->variants)) {
+                    $variant = $this->record->variants()->first();
+                    $variant->values()->detach();
+                    $this->record->productOptions()->exclusive()->each(
+                        fn (ProductOption $productOption) => $productOption->delete()
+                    );
+
+                    $this->record->productOptions()->shared()->detach();
+                    $this->record->variants()
+                        ->where('id', '!=', $variant->id)
+                        ->get()
+                        ->each(
+                            fn (ProductVariant $variant) => $variant->delete()
+                        );
+
+                    DB::commit();
+
+                    Notification::make()->title(
+                        __('lunarpanel::productoption.widgets.product-options.notifications.save-variants.success.title')
+                    )->success()->send();
+
+                    return;
+                }
+
+                foreach ($this->variants as $variantIndex => $variantData) {
+                    $variant = new ProductVariant([
+                        'product_id' => $this->record->id,
+                    ]);
+                    $basePrice = null;
+
+                    if (! empty($variantData['variant_id'])) {
+                        $variant = ProductVariant::find($variantData['variant_id']);
+                        $basePrice = $variant->basePrices->first();
+                    }
+
+                    if (! empty($variantData['copied_id'])) {
+                        $copiedVariant = ProductVariant::find(
+                            $variantData['copied_id']
+                        );
+
+                        $variant = $copiedVariant->replicate();
+                        $variant->save();
+
+                        $basePrice = $copiedVariant->basePrices->first()->replicate();
+                        $basePrice->priceable_id = $variant->id;
+                    }
+
+                    $variant->sku = $variantData['sku'];
+                    $variant->stock = $variantData['stock'];
+                    $variant->save();
+
+                    $basePrice->price = (int) bcmul($variantData['price'], $basePrice->currency->factor);
+                    $basePrice->save();
+
+                    $optionsValues = $this->mapOptionValuesToIds($variantData['values']);
+
+                    $variant->values()->sync($optionsValues);
+
+                    $this->variants[$variantIndex]['variant_id'] = $variant->id;
+                }
+
+                $productOptions = collect($this->configuredOptions)
+                    ->mapWithKeys(function ($option) {
+                        return [
+                            $option['id'] => [
+                                'position' => $option['position'],
+                            ],
+                        ];
+                    });
+
+                $this->record->productOptions()->sync($productOptions);
+
+                $variantIds = collect($this->variants)->pluck('variant_id');
+
+                $this->record->variants()->whereNotIn('id', $variantIds)
+                    ->get()
+                    ->each(
+                        fn ($variant) => $variant->delete()
+                    );
+                DB::commit();
+
+                Notification::make()->title(
+                    __('lunarpanel::productoption.widgets.product-options.notifications.save-variants.success.title')
+                )->success()->send();
+            });
+    }
+
+    public function getVariantLink($variantId)
+    {
+        return ProductVariantResource::getUrl('edit', [
+            'product' => $this->record,
+            'record' => $variantId,
+        ]);
+    }
+
+    protected function mapOptionValue(ProductOptionValue $value, bool $enabled = true)
+    {
+        return [
+            'id' => $value->id,
+            'enabled' => $enabled,
+            'value' => $value->translate('name'),
+            'position' => $value->position,
+        ];
+    }
+
+    protected function mapOption(ProductOption $option, array $values = []): array
+    {
+        return [
+            'id' => $option->id,
+            'key' => "option_{$option->id}",
+            'value' => $option->translate('name'),
+            'position' => $option->pivot?->position ?: count($this->configuredOptions) + 1,
+            'readonly' => $option->shared,
+            'option_values' => $values,
+        ];
+    }
+}
diff --git a/packages/admin/src/Filament/Resources/ProductVariantResource.php b/packages/admin/src/Filament/Resources/ProductVariantResource.php
new file mode 100644
index 0000000000..de29b30f15
--- /dev/null
+++ b/packages/admin/src/Filament/Resources/ProductVariantResource.php
@@ -0,0 +1,316 @@
+<?php
+
+namespace Lunar\Admin\Filament\Resources;
+
+use Cartalyst\Converter\Laravel\Facades\Converter;
+use Filament\Actions\Action;
+use Filament\Forms;
+use Filament\Forms\Components\Component;
+use Filament\Forms\Form;
+use Filament\Pages\Page;
+use Filament\Pages\SubNavigationPosition;
+use Filament\Tables\Table;
+use Illuminate\Database\Eloquent\Model;
+use Lunar\Admin\Filament\Resources\ProductVariantResource\Pages;
+use Lunar\Admin\Support\Forms\Components\Attributes;
+use Lunar\Admin\Support\Resources\BaseResource;
+use Lunar\Models\ProductVariant;
+use Marvinosswald\FilamentInputSelectAffix\TextInputSelectAffix;
+
+class ProductVariantResource extends BaseResource
+{
+    protected static ?string $permission = 'catalog:manage-products';
+
+    protected static ?string $model = ProductVariant::class;
+
+    protected static SubNavigationPosition $subNavigationPosition = SubNavigationPosition::End;
+
+    public static function getLabel(): string
+    {
+        return __('lunarpanel::productvariant.label');
+    }
+
+    public static function getPluralLabel(): string
+    {
+        return __('lunarpanel::productvariant.plural_label');
+    }
+
+    public static function shouldRegisterNavigation(array $parameters = []): bool
+    {
+        return false;
+    }
+
+    public static function getRecordSubNavigation(Page $page): array
+    {
+        return $page->generateNavigationItems([
+            Pages\EditProductVariant::class,
+            Pages\ManageVariantMedia::class,
+            Pages\ManageVariantPricing::class,
+            Pages\ManageVariantIdentifiers::class,
+            Pages\ManageVariantInventory::class,
+            Pages\ManageVariantShipping::class,
+        ]);
+    }
+
+    public static function getBaseBreadcrumbs(ProductVariant $productVariant): array
+    {
+        return [
+            ProductResource::getUrl('edit', [
+                'record' => $productVariant->product,
+            ]) => $productVariant->product->attr('name'),
+            ProductResource::getUrl('variants', [
+                'record' => $productVariant->product,
+            ]) => 'Variants',
+            ProductVariantResource::getUrl('edit', [
+                'record' => $productVariant,
+            ]) => $productVariant->sku,
+        ];
+    }
+
+    public static function getDefaultForm(Form $form): Form
+    {
+        return $form
+            ->schema([
+                static::getAttributeDataFormComponent(),
+            ])
+            ->columns(1);
+    }
+
+    protected static function getMainFormComponents(): array
+    {
+        return [
+            static::getSkuFormComponent(),
+        ];
+    }
+
+    public static function getSkuFormComponent(): Forms\Components\TextInput
+    {
+        return Forms\Components\TextInput::make('sku');
+    }
+
+    public static function getGtinFormComponent(): Forms\Components\TextInput
+    {
+        return Forms\Components\TextInput::make('gtin')->label(
+            __('lunarpanel::productvariant.form.gtin.label')
+        );
+    }
+
+    public static function getMpnFormComponent(): Forms\Components\TextInput
+    {
+        return Forms\Components\TextInput::make('mpn')->label(
+            __('lunarpanel::productvariant.form.mpn.label')
+        );
+    }
+
+    public static function getEanFormComponent(): Forms\Components\TextInput
+    {
+        return Forms\Components\TextInput::make('ean')->label(
+            __('lunarpanel::productvariant.form.ean.label')
+        );
+    }
+
+    public static function getStockFormComponent(): Forms\Components\TextInput
+    {
+        return Forms\Components\TextInput::make('stock')
+            ->label(
+                __('lunarpanel::productvariant.form.stock.label')
+            )->numeric();
+    }
+
+    public static function getBackorderFormComponent(): Forms\Components\TextInput
+    {
+        return
+            Forms\Components\TextInput::make('backorder')
+                ->label(
+                    __('lunarpanel::productvariant.form.backorder.label')
+                )->numeric();
+    }
+
+    public static function getPurchasableFormComponent(): Forms\Components\Select
+    {
+        return Forms\Components\Select::make('purchasable')
+            ->options([
+                'always' => __('lunarpanel::productvariant.form.purchasable.options.always'),
+                'in_stock' => __('lunarpanel::productvariant.form.purchasable.options.in_stock'),
+                'backorder' => __('lunarpanel::productvariant.form.purchasable.options.backorder'),
+            ])
+            ->label(
+                __('lunarpanel::productvariant.form.purchasable.label')
+            );
+    }
+
+    public static function getUnitQtyFormComponent(): Forms\Components\TextInput
+    {
+        return Forms\Components\TextInput::make('unit_quantity')
+            ->label(
+                __('lunarpanel::productvariant.form.unit_quantity.label')
+            )->helperText(
+                __('lunarpanel::productvariant.form.unit_quantity.helper_text')
+            )->numeric();
+    }
+
+    public static function getQuantityIncrementFormComponent(): Forms\Components\TextInput
+    {
+        return Forms\Components\TextInput::make('quantity_increment')
+            ->label(
+                __('lunarpanel::productvariant.form.quantity_increment.label')
+            )->helperText(
+                __('lunarpanel::productvariant.form.quantity_increment.helper_text')
+            )->numeric();
+    }
+
+    public static function getMinQuantityFormComponent(): Forms\Components\TextInput
+    {
+        return Forms\Components\TextInput::make('min_quantity')
+            ->label(
+                __('lunarpanel::productvariant.form.min_quantity.label')
+            )->helperText(
+                __('lunarpanel::productvariant.form.min_quantity.helper_text')
+            )->numeric();
+    }
+
+    public static function getShippableFormComponent(): Forms\Components\Toggle
+    {
+        return Forms\Components\Toggle::make('shippable')->label(
+            __('lunarpanel::productvariant.form.shippable.label')
+        )->columnSpan(2);
+    }
+
+    public static function getMeasurements($key = null): array
+    {
+        $measurements = Converter::getMeasurements();
+
+        return collect(
+            array_keys($measurements[$key] ?? [])
+        )->mapWithKeys(
+            fn ($value) => [$value => $value]
+        )->toArray();
+    }
+
+    public static function getLengthFormComponent(): TextInputSelectAffix
+    {
+        return TextInputSelectAffix::make('length_value')
+            ->label(
+                __('lunarpanel::productvariant.form.length_value.label')
+            )
+            ->numeric()
+            ->select(
+                fn () => Forms\Components\Select::make('length_unit')
+                    ->options(
+                        static::getMeasurements('length')
+                    )
+                    ->label(
+                        __('lunarpanel::productvariant.form.length_unit.label')
+                    )->selectablePlaceholder(false)
+            );
+    }
+
+    public static function getWidthFormComponent(): TextInputSelectAffix
+    {
+        return TextInputSelectAffix::make('width_value')
+            ->label(
+                __('lunarpanel::productvariant.form.width_value.label')
+            )
+            ->numeric()
+            ->select(
+                fn () => Forms\Components\Select::make('width_unit')
+                    ->options(
+                        static::getMeasurements('length')
+                    )
+                    ->label(
+                        __('lunarpanel::productvariant.form.width_unit.label')
+                    )->selectablePlaceholder(false)
+            );
+    }
+
+    public static function getHeightFormComponent(): TextInputSelectAffix
+    {
+        return TextInputSelectAffix::make('height_value')
+            ->label(
+                __('lunarpanel::productvariant.form.height_value.label')
+            )
+            ->numeric()
+            ->select(
+                fn () => Forms\Components\Select::make('height_unit')
+                    ->options(
+                        static::getMeasurements('length')
+                    )
+                    ->label(
+                        __('lunarpanel::productvariant.form.height_unit.label')
+                    )->selectablePlaceholder(false)
+            );
+    }
+
+    public static function getWeightFormComponent(): TextInputSelectAffix
+    {
+        return TextInputSelectAffix::make('weight_value')
+            ->label(
+                __('lunarpanel::productvariant.form.weight_value.label')
+            )
+            ->numeric()
+            ->select(
+                fn () => Forms\Components\Select::make('weight_unit')
+                    ->options(
+                        static::getMeasurements('weight')
+                    )
+                    ->label(
+                        __('lunarpanel::productvariant.form.weight_unit.label')
+                    )->selectablePlaceholder(false)
+            );
+    }
+
+    public static function getVariantSwitcherWidget(Model $record): Action
+    {
+        return Action::make('switch_variant')
+            ->label(
+                __('lunarpanel::widgets.variant_switcher.label')
+            )
+            ->modalContent(function () use ($record) {
+                return view('lunarpanel::actions.switch-variant', [
+                    'record' => $record->product,
+                ]);
+            })
+            ->slideOver();
+    }
+
+    protected static function getAttributeDataFormComponent(): Component
+    {
+        return Attributes::make()->statePath('attribute_data');
+    }
+
+    public static function getDefaultTable(Table $table): Table
+    {
+        return $table
+            ->columns(static::getTableColumns())
+            ->filters([])
+            ->actions([])
+            ->bulkActions([])
+            ->selectCurrentPageOnly()
+            ->deferLoading();
+    }
+
+    protected static function getTableColumns(): array
+    {
+        return [
+
+        ];
+    }
+
+    public static function getDefaultRelations(): array
+    {
+        return [];
+    }
+
+    public static function getPages(): array
+    {
+        return [
+            'index' => Pages\ListProductVariants::route('/'),
+            'edit' => Pages\EditProductVariant::route('/{record}/edit'),
+            'pricing' => Pages\ManageVariantPricing::route('/{record}/pricing'),
+            'media' => Pages\ManageVariantMedia::route('/{record}/media'),
+            'identifiers' => Pages\ManageVariantIdentifiers::route('/{record}/identifiers'),
+            'inventory' => Pages\ManageVariantInventory::route('/{record}/inventory'),
+            'shipping' => Pages\ManageVariantShipping::route('/{record}/shipping'),
+        ];
+    }
+}
diff --git a/packages/admin/src/Filament/Resources/ProductVariantResource/Pages/EditProductVariant.php b/packages/admin/src/Filament/Resources/ProductVariantResource/Pages/EditProductVariant.php
new file mode 100644
index 0000000000..d609ceb7f2
--- /dev/null
+++ b/packages/admin/src/Filament/Resources/ProductVariantResource/Pages/EditProductVariant.php
@@ -0,0 +1,78 @@
+<?php
+
+namespace Lunar\Admin\Filament\Resources\ProductVariantResource\Pages;
+
+use Filament\Actions\Action;
+use Illuminate\Contracts\Support\Htmlable;
+use Illuminate\Database\Eloquent\Model;
+use Lunar\Admin\Filament\Resources\ProductResource;
+use Lunar\Admin\Filament\Resources\ProductVariantResource;
+use Lunar\Admin\Support\Pages\BaseEditRecord;
+
+class EditProductVariant extends BaseEditRecord
+{
+    protected static string $resource = ProductVariantResource::class;
+
+    public function getTitle(): string|Htmlable
+    {
+        return __('lunarpanel::productvariant.pages.edit.title');
+    }
+
+    public static function getNavigationLabel(): string
+    {
+        return __('lunarpanel::productvariant.pages.edit.title');
+    }
+
+    public static bool $formActionsAreSticky = true;
+
+    public function getBreadcrumbs(): array
+    {
+
+        return ProductVariantResource::getBaseBreadcrumbs(
+            $this->getRecord()
+        );
+    }
+
+    protected function getCancelFormAction(): Action
+    {
+        return parent::getCancelFormAction()->url(function (Model $record) {
+            return ProductResource::getUrl('variants', [
+                'record' => $record->product,
+            ]);
+        });
+    }
+
+    public function mount(int|string $record): void
+    {
+        parent::mount($record);
+
+        $variant = $this->getRecord();
+
+        if ($variant->mappedAttributes->isEmpty()) {
+            redirect()->to(
+                ProductVariantResource::getUrl('identifiers', [
+                    'record' => $this->getRecord(),
+                ])
+            );
+        }
+    }
+
+    public static function shouldRegisterNavigation(array $parameters = []): bool
+    {
+        return $parameters['record']->mappedAttributes->isNotEmpty();
+    }
+
+    protected function getDefaultHeaderActions(): array
+    {
+        return [
+            ProductVariantResource::getVariantSwitcherWidget(
+                $this->getRecord()
+            ),
+        ];
+    }
+
+    public function getRelationManagers(): array
+    {
+        return [];
+    }
+}
diff --git a/packages/admin/src/Filament/Resources/ProductVariantResource/Pages/ListProductVariants.php b/packages/admin/src/Filament/Resources/ProductVariantResource/Pages/ListProductVariants.php
new file mode 100644
index 0000000000..85b7384c77
--- /dev/null
+++ b/packages/admin/src/Filament/Resources/ProductVariantResource/Pages/ListProductVariants.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Lunar\Admin\Filament\Resources\ProductVariantResource\Pages;
+
+use Lunar\Admin\Filament\Resources\ProductVariantResource;
+use Lunar\Admin\Support\Pages\BaseListRecords;
+
+class ListProductVariants extends BaseListRecords
+{
+    protected static string $resource = ProductVariantResource::class;
+
+    protected function getDefaultHeaderActions(): array
+    {
+        return [];
+    }
+
+    public static function createActionFormInputs(): array
+    {
+        return [];
+    }
+
+    public function getTabs(): array
+    {
+        return [];
+    }
+}
diff --git a/packages/admin/src/Filament/Resources/ProductVariantResource/Pages/ManageVariantIdentifiers.php b/packages/admin/src/Filament/Resources/ProductVariantResource/Pages/ManageVariantIdentifiers.php
new file mode 100644
index 0000000000..f2a4a3d703
--- /dev/null
+++ b/packages/admin/src/Filament/Resources/ProductVariantResource/Pages/ManageVariantIdentifiers.php
@@ -0,0 +1,85 @@
+<?php
+
+namespace Lunar\Admin\Filament\Resources\ProductVariantResource\Pages;
+
+use Filament\Actions\Action;
+use Filament\Forms\Components\Section;
+use Filament\Forms\Form;
+use Filament\Support\Facades\FilamentIcon;
+use Illuminate\Contracts\Support\Htmlable;
+use Illuminate\Database\Eloquent\Model;
+use Lunar\Admin\Filament\Resources\ProductResource;
+use Lunar\Admin\Filament\Resources\ProductVariantResource;
+use Lunar\Admin\Support\Pages\BaseEditRecord;
+
+class ManageVariantIdentifiers extends BaseEditRecord
+{
+    protected static string $resource = ProductVariantResource::class;
+
+    public function getTitle(): string|Htmlable
+    {
+        return __('lunarpanel::productvariant.pages.identifiers.title');
+    }
+
+    public static function getNavigationLabel(): string
+    {
+        return __('lunarpanel::productvariant.pages.identifiers.title');
+    }
+
+    protected function getCancelFormAction(): Action
+    {
+        return parent::getCancelFormAction()->url(function (Model $record) {
+            return ProductResource::getUrl('variants', [
+                'record' => $record->product,
+            ]);
+        });
+    }
+
+    public function getBreadcrumbs(): array
+    {
+        return [
+            ...ProductVariantResource::getBaseBreadcrumbs(
+                $this->getRecord()
+            ),
+            ProductVariantResource::getUrl('inventory', [
+                'record' => $this->getRecord(),
+            ]) => $this->getTitle(),
+        ];
+    }
+
+    public static function getNavigationIcon(): ?string
+    {
+        return FilamentIcon::resolve('lunar::product-identifiers');
+    }
+
+    public function form(Form $form): Form
+    {
+        return $form->schema([
+            Section::make()->schema([
+                ProductVariantResource::getSkuFormComponent()
+                    ->live()->unique(
+                        table: fn () => $this->getRecord()->getTable(),
+                        ignorable: $this->getRecord(),
+                        ignoreRecord: true,
+                    ),
+                ProductVariantResource::getGtinFormComponent(),
+                ProductVariantResource::getMpnFormComponent(),
+                ProductVariantResource::getEanFormComponent(),
+            ])->columns(1),
+        ]);
+    }
+
+    protected function getDefaultHeaderActions(): array
+    {
+        return [
+            ProductVariantResource::getVariantSwitcherWidget(
+                $this->getRecord()
+            ),
+        ];
+    }
+
+    public function getRelationManagers(): array
+    {
+        return [];
+    }
+}
diff --git a/packages/admin/src/Filament/Resources/ProductVariantResource/Pages/ManageVariantInventory.php b/packages/admin/src/Filament/Resources/ProductVariantResource/Pages/ManageVariantInventory.php
new file mode 100644
index 0000000000..3dec22cadd
--- /dev/null
+++ b/packages/admin/src/Filament/Resources/ProductVariantResource/Pages/ManageVariantInventory.php
@@ -0,0 +1,85 @@
+<?php
+
+namespace Lunar\Admin\Filament\Resources\ProductVariantResource\Pages;
+
+use Filament\Actions\Action;
+use Filament\Forms\Components\Section;
+use Filament\Forms\Form;
+use Filament\Support\Facades\FilamentIcon;
+use Illuminate\Contracts\Support\Htmlable;
+use Illuminate\Database\Eloquent\Model;
+use Lunar\Admin\Filament\Resources\ProductResource;
+use Lunar\Admin\Filament\Resources\ProductVariantResource;
+use Lunar\Admin\Support\Pages\BaseEditRecord;
+
+class ManageVariantInventory extends BaseEditRecord
+{
+    protected static string $resource = ProductVariantResource::class;
+
+    public function getTitle(): string|Htmlable
+    {
+        return __('lunarpanel::productvariant.pages.inventory.title');
+    }
+
+    public static function getNavigationLabel(): string
+    {
+        return __('lunarpanel::productvariant.pages.inventory.title');
+    }
+
+    protected function getCancelFormAction(): Action
+    {
+        return parent::getCancelFormAction()->url(function (Model $record) {
+            return ProductResource::getUrl('variants', [
+                'record' => $record->product,
+            ]);
+        });
+    }
+
+    public function getBreadcrumbs(): array
+    {
+        return [
+            ...ProductVariantResource::getBaseBreadcrumbs(
+                $this->getRecord()
+            ),
+            ProductVariantResource::getUrl('inventory', [
+                'record' => $this->getRecord(),
+            ]) => $this->getTitle(),
+        ];
+    }
+
+    public static function getNavigationIcon(): ?string
+    {
+        return FilamentIcon::resolve('lunar::product-inventory');
+    }
+
+    protected function getDefaultHeaderActions(): array
+    {
+        return [
+            ProductVariantResource::getVariantSwitcherWidget(
+                $this->getRecord()
+            ),
+        ];
+    }
+
+    public function form(Form $form): Form
+    {
+        return $form->schema([
+            Section::make()->schema([
+                ProductVariantResource::getStockFormComponent(),
+                ProductVariantResource::getBackorderFormComponent(),
+                ProductVariantResource::getPurchasableFormComponent(),
+                ProductVariantResource::getUnitQtyFormComponent(),
+                ProductVariantResource::getQuantityIncrementFormComponent(),
+                ProductVariantResource::getMinQuantityFormComponent(),
+            ])->columns([
+                'sm' => 1,
+                'xl' => 3,
+            ]),
+        ]);
+    }
+
+    public function getRelationManagers(): array
+    {
+        return [];
+    }
+}
diff --git a/packages/admin/src/Filament/Resources/ProductVariantResource/Pages/ManageVariantMedia.php b/packages/admin/src/Filament/Resources/ProductVariantResource/Pages/ManageVariantMedia.php
new file mode 100644
index 0000000000..0828ab9d06
--- /dev/null
+++ b/packages/admin/src/Filament/Resources/ProductVariantResource/Pages/ManageVariantMedia.php
@@ -0,0 +1,122 @@
+<?php
+
+namespace Lunar\Admin\Filament\Resources\ProductVariantResource\Pages;
+
+use Awcodes\Shout\Components\Shout;
+use Filament\Actions\Action;
+use Filament\Forms\Components\Section;
+use Filament\Forms\Form;
+use Filament\Forms\Get;
+use Filament\Support\Facades\FilamentIcon;
+use Illuminate\Contracts\Support\Htmlable;
+use Illuminate\Database\Eloquent\Model;
+use Lunar\Admin\Filament\Resources\ProductResource;
+use Lunar\Admin\Filament\Resources\ProductVariantResource;
+use Lunar\Admin\Support\Forms\Components\MediaSelect;
+use Lunar\Admin\Support\Pages\BaseEditRecord;
+use Lunar\Models\ProductVariant;
+
+class ManageVariantMedia extends BaseEditRecord
+{
+    protected static string $resource = ProductVariantResource::class;
+
+    public function getTitle(): string|Htmlable
+    {
+        return __('lunarpanel::productvariant.pages.media.title');
+    }
+
+    public static function getNavigationLabel(): string
+    {
+        return __('lunarpanel::productvariant.pages.media.title');
+    }
+
+    public static function getNavigationIcon(): ?string
+    {
+        return FilamentIcon::resolve('lunar::media');
+    }
+
+    protected function getDefaultHeaderActions(): array
+    {
+        return [
+            ProductVariantResource::getVariantSwitcherWidget(
+                $this->getRecord()
+            ),
+        ];
+    }
+
+    protected function getCancelFormAction(): Action
+    {
+        return parent::getCancelFormAction()->url(function (Model $record) {
+            return ProductResource::getUrl('variants', [
+                'record' => $record->product,
+            ]);
+        });
+    }
+
+    public function getBreadcrumbs(): array
+    {
+        return [
+            ...ProductVariantResource::getBaseBreadcrumbs(
+                $this->getRecord()
+            ),
+            ProductVariantResource::getUrl('media', [
+                'record' => $this->getRecord(),
+            ]) => $this->getTitle(),
+        ];
+    }
+
+    protected function handleRecordUpdate(Model $record, array $data): Model
+    {
+        $record->images()->sync([
+            $data['images'] => ['primary' => true],
+        ]);
+
+        return $record;
+    }
+
+    public function form(Form $form): Form
+    {
+        return $form->schema([
+            Section::make()->schema([
+                Shout::make('no_selection')->content(
+                    __('lunarpanel::productvariant.pages.media.form.no_selection.label')
+                )->visible(
+                    fn (Get $get) => ! $get('images') && $this->getRecord()->product->media()->count()
+                ),
+                Shout::make('no_media_available')->content(
+                    __('lunarpanel::productvariant.pages.media.form.no_media_available.label')
+                )->visible(
+                    fn (Get $get) => ! $this->getRecord()->product->media()->count()
+                ),
+                MediaSelect::make('images')
+                    ->visible(
+                        fn () => $this->getRecord()->product->media()->count()
+                    )
+                    ->label(
+                        __('lunarpanel::productvariant.pages.media.form.images.label')
+                    )
+                    ->helperText(
+                        __('lunarpanel::productvariant.pages.media.form.images.helper_text')
+                    )
+                    ->afterStateHydrated(function (ProductVariant $record, MediaSelect $component) {
+                        $image = $record->images->first(function ($media) {
+                            return (bool) $media->pivot?->primary;
+                        });
+                        $component->state($image?->id);
+                    })
+                    ->options(
+                        $this->getRecord()->product->media->mapWithKeys(
+                            fn ($media) => [
+                                $media->id => $media->getUrl('small'),
+                            ]
+                        )
+                    ),
+            ]),
+        ]);
+    }
+
+    public function getRelationManagers(): array
+    {
+        return [];
+    }
+}
diff --git a/packages/admin/src/Filament/Resources/ProductVariantResource/Pages/ManageVariantPricing.php b/packages/admin/src/Filament/Resources/ProductVariantResource/Pages/ManageVariantPricing.php
new file mode 100644
index 0000000000..bbf6e3c205
--- /dev/null
+++ b/packages/admin/src/Filament/Resources/ProductVariantResource/Pages/ManageVariantPricing.php
@@ -0,0 +1,158 @@
+<?php
+
+namespace Lunar\Admin\Filament\Resources\ProductVariantResource\Pages;
+
+use Filament\Actions\Action;
+use Filament\Forms;
+use Filament\Forms\Form;
+use Filament\Resources\Pages\ManageRelatedRecords;
+use Filament\Support\Facades\FilamentIcon;
+use Filament\Tables;
+use Filament\Tables\Table;
+use Illuminate\Contracts\Support\Htmlable;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Validation\Rules\Unique;
+use Lunar\Admin\Filament\Resources\ProductResource;
+use Lunar\Admin\Filament\Resources\ProductVariantResource;
+use Lunar\Models\Currency;
+use Lunar\Models\Price;
+
+class ManageVariantPricing extends ManageRelatedRecords
+{
+    protected static string $resource = ProductVariantResource::class;
+
+    protected static string $relationship = 'prices';
+
+    public static function getNavigationIcon(): ?string
+    {
+        return FilamentIcon::resolve('lunar::product-pricing');
+    }
+
+    public function getTitle(): string|Htmlable
+    {
+        return __('lunarpanel::relationmanagers.pricing.title');
+    }
+
+    public static function getNavigationLabel(): string
+    {
+        return __('lunarpanel::relationmanagers.pricing.title');
+    }
+
+    protected function getHeaderActions(): array
+    {
+        return [
+            ProductVariantResource::getVariantSwitcherWidget(
+                $this->getRecord()
+            ),
+        ];
+    }
+
+    protected function getCancelFormAction(): Action
+    {
+        return parent::getCancelFormAction()->url(function (Model $record) {
+            return ProductResource::getUrl('variants', [
+                'record' => $record->product,
+            ]);
+        });
+    }
+
+    public function getBreadcrumbs(): array
+    {
+        return [
+            ...ProductVariantResource::getBaseBreadcrumbs(
+                $this->getRecord()
+            ),
+            ProductVariantResource::getUrl('pricing', [
+                'record' => $this->getRecord(),
+            ]) => $this->getTitle(),
+        ];
+    }
+
+    public function form(Form $form): Form
+    {
+        return $form
+            ->schema([
+                Forms\Components\TextInput::make('price')->formatStateUsing(
+                    fn ($state) => $state?->decimal(rounding: false)
+                )->numeric()->unique(
+                    modifyRuleUsing: function (Unique $rule, Forms\Get $get) {
+                        $owner = $this->getOwnerRecord();
+
+                        return $rule->where('customer_group_id', $get('customer_group_id'))
+                            ->where('quantity_break', $get('quantity_break'))
+                            ->where('currency_id', $get('currency_id'))
+                            ->where('priceable_type', get_class($owner))
+                            ->where('priceable_id', $owner->id);
+                    }
+                )->required(),
+                Forms\Components\TextInput::make('quantity_break')
+                    ->label(
+                        __('lunarpanel::relationmanagers.pricing.form.quantity_break.label')
+                    )->numeric()->minValue(1)->required(),
+                Forms\Components\Select::make('currency_id')
+                    ->label(
+                        __('lunarpanel::relationmanagers.pricing.form.currency_id.label')
+                    )->relationship(name: 'currency', titleAttribute: 'name')->required(),
+                Forms\Components\Select::make('customer_group_id')
+                    ->label(
+                        __('lunarpanel::relationmanagers.pricing.form.customer_group_id.label')
+                    )->relationship(name: 'customerGroup', titleAttribute: 'name'),
+            ]);
+    }
+
+    public function table(Table $table): Table
+    {
+        return $table
+            ->recordTitleAttribute('name')
+            ->modifyQueryUsing(
+                fn ($query) => $query->orderBy('quantity_break', 'asc')
+            )
+            ->columns([
+                Tables\Columns\TextColumn::make('price')
+                    ->label(
+                        __('lunarpanel::relationmanagers.pricing.table.price.label')
+                    )->formatStateUsing(
+                        fn ($state) => $state->formatted,
+                    ),
+                Tables\Columns\TextColumn::make('currency.code')->label(
+                    __('lunarpanel::relationmanagers.pricing.table.currency.label')
+                ),
+                Tables\Columns\TextColumn::make('quantity_break')->label(
+                    __('lunarpanel::relationmanagers.pricing.table.quantity_break.label')
+                ),
+                Tables\Columns\TextColumn::make('customerGroup.name')->label(
+                    __('lunarpanel::relationmanagers.pricing.table.customer_group.label')
+                ),
+            ])
+            ->filters([
+                Tables\Filters\SelectFilter::make('currency')
+                    ->relationship(name: 'currency', titleAttribute: 'name')
+                    ->preload(),
+                Tables\Filters\SelectFilter::make('quantity_break')->options(
+                    Price::where('priceable_id', $this->getOwnerRecord()->id)
+                        ->where('priceable_type', get_class($this->getOwnerRecord()))
+                        ->get()
+                        ->pluck('quantity_break', 'quantity_break')
+                ),
+            ])
+            ->headerActions([
+                Tables\Actions\CreateAction::make()->mutateFormDataUsing(function (array $data) {
+                    $currencyModel = Currency::find($data['currency_id']);
+
+                    $data['price'] = (int) ($data['price'] * $currencyModel->factor);
+
+                    return $data;
+                }),
+            ])
+            ->actions([
+                Tables\Actions\EditAction::make()->mutateFormDataUsing(function (array $data): array {
+                    $currencyModel = Currency::find($data['currency_id']);
+
+                    $data['price'] = (int) ($data['price'] * $currencyModel->factor);
+
+                    return $data;
+                }),
+                Tables\Actions\DeleteAction::make(),
+            ]);
+    }
+}
diff --git a/packages/admin/src/Filament/Resources/ProductVariantResource/Pages/ManageVariantShipping.php b/packages/admin/src/Filament/Resources/ProductVariantResource/Pages/ManageVariantShipping.php
new file mode 100644
index 0000000000..b5fa30a954
--- /dev/null
+++ b/packages/admin/src/Filament/Resources/ProductVariantResource/Pages/ManageVariantShipping.php
@@ -0,0 +1,135 @@
+<?php
+
+namespace Lunar\Admin\Filament\Resources\ProductVariantResource\Pages;
+
+use Cartalyst\Converter\Laravel\Facades\Converter;
+use Filament\Actions\Action;
+use Filament\Forms\Components\Section;
+use Filament\Forms\Form;
+use Filament\Support\Facades\FilamentIcon;
+use Illuminate\Contracts\Support\Htmlable;
+use Illuminate\Database\Eloquent\Model;
+use Lunar\Admin\Filament\Resources\ProductResource;
+use Lunar\Admin\Filament\Resources\ProductVariantResource;
+use Lunar\Admin\Support\Pages\BaseEditRecord;
+
+class ManageVariantShipping extends BaseEditRecord
+{
+    protected static string $resource = ProductVariantResource::class;
+
+    public function getTitle(): string|Htmlable
+    {
+        return __('lunarpanel::productvariant.pages.shipping.title');
+    }
+
+    public static function getNavigationLabel(): string
+    {
+        return __('lunarpanel::productvariant.pages.shipping.title');
+    }
+
+    public static function getNavigationIcon(): ?string
+    {
+        return FilamentIcon::resolve('lunar::product-shipping');
+    }
+
+    protected function getDefaultHeaderActions(): array
+    {
+        return [
+            ProductVariantResource::getVariantSwitcherWidget(
+                $this->getRecord()
+            ),
+        ];
+    }
+
+    protected function getCancelFormAction(): Action
+    {
+        return parent::getCancelFormAction()->url(function (Model $record) {
+            return ProductResource::getUrl('variants', [
+                'record' => $record->product,
+            ]);
+        });
+    }
+
+    public function getBreadcrumbs(): array
+    {
+        return [
+            ...ProductVariantResource::getBaseBreadcrumbs(
+                $this->getRecord()
+            ),
+            ProductVariantResource::getUrl('shipping', [
+                'record' => $this->getRecord(),
+            ]) => $this->getTitle(),
+        ];
+    }
+
+    protected function handleRecordUpdate(Model $record, array $data): Model
+    {
+        $volume = static::getVolume(
+            [
+                'value' => $data['width_value'],
+                'unit' => $data['width_unit'] ?? $record->width_unit,
+            ],
+            [
+                'value' => $data['length_value'],
+                'unit' => $data['length_unit'] ?? $record->length_unit,
+            ],
+            [
+                'value' => $data['height_value'],
+                'unit' => $data['height_unit'] ?? $record->height_unit,
+            ]
+        );
+
+        $record->update([
+            ...$data,
+            ...[
+                'volume_unit' => 'l',
+                'volume_value' => $volume,
+            ],
+        ]);
+
+        return $record;
+    }
+
+    public static function getVolume($width = [], $length = [], $height = [])
+    {
+        $width = Converter::value($width['value'])
+            ->from('length.'.$width['unit'])
+            ->to('length.cm')
+            ->convert()
+            ->getValue();
+        $length = Converter::value($length['value'])
+            ->from('length.'.$length['unit'])
+            ->to('length.cm')
+            ->convert()
+            ->getValue();
+
+        $height = Converter::value($height['value'])
+            ->from('length.'.$height['unit'])
+            ->to('length.cm')
+            ->convert()
+            ->getValue();
+
+        return Converter::from('volume.ml')->to('volume.l')->value($length * $width * $height)->convert()->getValue();
+    }
+
+    public function form(Form $form): Form
+    {
+        return $form->schema([
+            Section::make()->schema([
+                ProductVariantResource::getShippableFormComponent(),
+                ProductVariantResource::getLengthFormComponent(),
+                ProductVariantResource::getWidthFormComponent(),
+                ProductVariantResource::getHeightFormComponent(),
+                ProductVariantResource::getWeightFormComponent(),
+            ])->columns([
+                'sm' => 1,
+                'xl' => 2,
+            ]),
+        ]);
+    }
+
+    public function getRelationManagers(): array
+    {
+        return [];
+    }
+}
diff --git a/packages/admin/src/Filament/Resources/StaffResource/Pages/ListStaff.php b/packages/admin/src/Filament/Resources/StaffResource/Pages/ListStaff.php
index 03eff6d798..1870003fa1 100644
--- a/packages/admin/src/Filament/Resources/StaffResource/Pages/ListStaff.php
+++ b/packages/admin/src/Filament/Resources/StaffResource/Pages/ListStaff.php
@@ -11,7 +11,7 @@ class ListStaff extends BaseListRecords
 {
     protected static string $resource = StaffResource::class;
 
-    protected function getHeaderActions(): array
+    protected function getDefaultHeaderActions(): array
     {
         return [
             Actions\Action::make('access-control')
diff --git a/packages/admin/src/Filament/Resources/TaxClassResource/Pages/ListTaxClasses.php b/packages/admin/src/Filament/Resources/TaxClassResource/Pages/ListTaxClasses.php
index 0f2010ed40..4f60c80e35 100644
--- a/packages/admin/src/Filament/Resources/TaxClassResource/Pages/ListTaxClasses.php
+++ b/packages/admin/src/Filament/Resources/TaxClassResource/Pages/ListTaxClasses.php
@@ -10,7 +10,7 @@ class ListTaxClasses extends BaseListRecords
 {
     protected static string $resource = TaxClassResource::class;
 
-    protected function getHeaderActions(): array
+    protected function getDefaultHeaderActions(): array
     {
         return [
             Actions\CreateAction::make(),
diff --git a/packages/admin/src/Filament/Widgets/Dashboard/Orders/AverageOrderValueChart.php b/packages/admin/src/Filament/Widgets/Dashboard/Orders/AverageOrderValueChart.php
index 648bdfc7cd..a663d213ac 100644
--- a/packages/admin/src/Filament/Widgets/Dashboard/Orders/AverageOrderValueChart.php
+++ b/packages/admin/src/Filament/Widgets/Dashboard/Orders/AverageOrderValueChart.php
@@ -115,7 +115,7 @@ protected function getOptions(): array
 
         return [
             'chart' => [
-                'type' => 'bar',
+                'type' => 'area',
                 'toolbar' => [
                     'show' => false,
                 ],
diff --git a/packages/admin/src/Filament/Widgets/Dashboard/Orders/OrderTotalsChart.php b/packages/admin/src/Filament/Widgets/Dashboard/Orders/OrderTotalsChart.php
index fd815809f2..7393bd8978 100644
--- a/packages/admin/src/Filament/Widgets/Dashboard/Orders/OrderTotalsChart.php
+++ b/packages/admin/src/Filament/Widgets/Dashboard/Orders/OrderTotalsChart.php
@@ -47,11 +47,9 @@ protected function getOptions(): array
         $currentPeriod = $this->getTotalsForPeriod($from, $date);
         $previousPeriod = $this->getTotalsForPeriod($from->clone()->subYear(), $date->clone()->subYear());
 
-        //        dd($currentPeriod);
-
         return [
             'chart' => [
-                'type' => 'bar',
+                'type' => 'area',
                 'toolbar' => [
                     'show' => false,
                 ],
diff --git a/packages/admin/src/Filament/Widgets/Dashboard/Orders/OrdersSalesChart.php b/packages/admin/src/Filament/Widgets/Dashboard/Orders/OrdersSalesChart.php
index 0626ed1dd2..ec711e2883 100644
--- a/packages/admin/src/Filament/Widgets/Dashboard/Orders/OrdersSalesChart.php
+++ b/packages/admin/src/Filament/Widgets/Dashboard/Orders/OrdersSalesChart.php
@@ -67,7 +67,7 @@ protected function getOptions(): array
 
         return [
             'chart' => [
-                'type' => 'bar',
+                'type' => 'area',
                 'stacked' => false,
                 'toolbar' => [
                     'show' => false,
diff --git a/packages/admin/src/Filament/Widgets/Products/VariantSwitcherTable.php b/packages/admin/src/Filament/Widgets/Products/VariantSwitcherTable.php
new file mode 100644
index 0000000000..bff6c1caf0
--- /dev/null
+++ b/packages/admin/src/Filament/Widgets/Products/VariantSwitcherTable.php
@@ -0,0 +1,102 @@
+<?php
+
+namespace Lunar\Admin\Filament\Widgets\Products;
+
+use Closure;
+use Filament\Tables;
+use Filament\Widgets\TableWidget;
+use Illuminate\Contracts\Support\Htmlable;
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\Relations\Relation;
+use Lunar\Admin\Filament\Resources\ProductVariantResource;
+use Lunar\Models\ProductOptionValue;
+use Lunar\Models\ProductVariant;
+
+class VariantSwitcherTable extends TableWidget
+{
+    public ?Model $record;
+
+    protected function getTableQuery(): Builder|Relation|null
+    {
+        return ProductVariant::where('product_id', $this->record->id);
+    }
+
+    protected function getTableFilters(): array
+    {
+        $optionValues = ProductOptionValue::whereHas(
+            'variants',
+            fn ($query) => $query->whereIn(
+                'variant_id',
+                $this->getTableQuery()->pluck('id')
+            ))
+            ->with(['option'])
+            ->get()
+            ->groupBy('product_option_id');
+
+        $filters = [];
+
+        foreach ($optionValues as $values) {
+            $option = $values->first()->option;
+
+            $filters[] = Tables\Filters\SelectFilter::make(
+                $option->handle
+            )->label($option->translate('name'))
+                ->options(
+                    $values->mapWithKeys(
+                        fn ($value) => [$value->id => $value->translate('name')]
+                    )
+                )->modifyQueryUsing(function (Builder $query, array $data) {
+                    $value = $data['value'];
+
+                    return $query->when(
+                        $value,
+                        function ($query) use ($value) {
+                            $query->whereHas('values', function ($relation) use ($value) {
+                                $table = $relation->getQuery()->from;
+
+                                $relation->where("{$table}.id", '=', $value);
+                            });
+                        }
+                    );
+                });
+        }
+
+        return $filters;
+    }
+
+    protected function getTableColumns(): array
+    {
+        return [
+            Tables\Columns\TextColumn::make('sku')
+                ->label(
+                    __('lunarpanel::widgets.variant_switcher.table.sku.label')
+                )->searchable(),
+            Tables\Columns\TextColumn::make('values')
+                ->label(
+                    __('lunarpanel::widgets.variant_switcher.table.values.label')
+                )
+                ->formatStateUsing(
+                    function (Model $record) {
+                        return $record->values->map(
+                            fn ($value) => $value->translate('name')
+                        )->join(', ');
+                    }
+                ),
+        ];
+    }
+
+    protected function getTableRecordUrlUsing(): ?Closure
+    {
+        return function (ProductVariant $variant) {
+            return ProductVariantResource::getUrl('edit', [
+                'record' => $variant,
+            ]);
+        };
+    }
+
+    protected function getTableHeading(): string|Htmlable|null
+    {
+        return '';
+    }
+}
diff --git a/packages/admin/src/LunarPanelManager.php b/packages/admin/src/LunarPanelManager.php
index dd4648590e..88957d7505 100644
--- a/packages/admin/src/LunarPanelManager.php
+++ b/packages/admin/src/LunarPanelManager.php
@@ -57,6 +57,7 @@ class LunarPanelManager
         Resources\ProductOptionResource::class,
         Resources\ProductResource::class,
         Resources\ProductTypeResource::class,
+        Resources\ProductVariantResource::class,
         Resources\StaffResource::class,
         Resources\TagResource::class,
         Resources\TaxClassResource::class,
@@ -86,8 +87,6 @@ public function register(): self
             $panel = $fn($panel);
         }
 
-        $panel->id($this->panelId);
-
         Filament::registerPanel($panel);
 
         FilamentIcon::register([
@@ -270,7 +269,9 @@ public function callHook(string $class, string $hookName, ...$args): mixed
     {
         if (isset($this->extensions[$class])) {
             foreach ($this->extensions[$class] as $extension) {
-                $args[0] = $extension->{$hookName}(...$args);
+                if (method_exists($extension, $hookName)) {
+                    $args[0] = $extension->{$hookName}(...$args);
+                }
             }
         }
 
diff --git a/packages/admin/src/Support/Actions/Traits/CreatesChildCollections.php b/packages/admin/src/Support/Actions/Traits/CreatesChildCollections.php
index 70d5faa5ff..9f302a47aa 100644
--- a/packages/admin/src/Support/Actions/Traits/CreatesChildCollections.php
+++ b/packages/admin/src/Support/Actions/Traits/CreatesChildCollections.php
@@ -26,12 +26,12 @@ public function createChildCollection(Collection $parent, string $name)
             ]);
         }
 
-        Collection::create([
+        $parent->appendNode(Collection::create([
             'collection_group_id' => $parent->collection_group_id,
             'attribute_data' => [
                 'name' => new $fieldType($nameValue),
             ],
-        ], $parent);
+        ]));
 
         DB::commit();
     }
diff --git a/packages/admin/src/Support/CustomerStatus.php b/packages/admin/src/Support/CustomerStatus.php
new file mode 100644
index 0000000000..1f8203afac
--- /dev/null
+++ b/packages/admin/src/Support/CustomerStatus.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace Lunar\Admin\Support;
+
+class CustomerStatus
+{
+    protected static array $cachedStatusColor = [];
+
+    protected static array $cachedStatusLabel = [];
+
+    protected static array $cachedStatusIcon = [];
+
+    public static function getLabel($status): string
+    {
+        return static::$cachedStatusLabel[$status] ??= $status ? __('lunarpanel::customer.table.new.label') : __('lunarpanel::customer.table.returning.label');
+    }
+
+    public static function getColor($status): string
+    {
+        return static::$cachedStatusColor[$status] ??= $status ? 'success' : 'gray';
+    }
+
+    public static function getIcon($status): string
+    {
+        return static::$cachedStatusIcon[$status] ??= $status ? 'heroicon-m-sparkles' : 'heroicon-m-arrow-path';
+    }
+}
diff --git a/packages/admin/src/Support/Extending/ViewPageExtension.php b/packages/admin/src/Support/Extending/ViewPageExtension.php
new file mode 100644
index 0000000000..675ecacf54
--- /dev/null
+++ b/packages/admin/src/Support/Extending/ViewPageExtension.php
@@ -0,0 +1,11 @@
+<?php
+
+namespace Lunar\Admin\Support\Extending;
+
+abstract class ViewPageExtension extends BaseExtension
+{
+    public function headerActions(array $actions): array
+    {
+        return $actions;
+    }
+}
diff --git a/packages/admin/src/Support/FieldTypes/Dropdown.php b/packages/admin/src/Support/FieldTypes/Dropdown.php
index 1685d0e15c..cbbb98d601 100644
--- a/packages/admin/src/Support/FieldTypes/Dropdown.php
+++ b/packages/admin/src/Support/FieldTypes/Dropdown.php
@@ -18,7 +18,8 @@ public static function getFilamentComponent(Attribute $attribute): Component
             collect($attribute->configuration->get('lookups'))->mapWithKeys(
                 fn ($lookup) => [$lookup['value'] => $lookup['label'] ?? $lookup['value']]
             )
-        );
+        )
+            ->helperText($attribute->translate('description'));
     }
 
     public static function getConfigurationFields(): array
diff --git a/packages/admin/src/Support/FieldTypes/File.php b/packages/admin/src/Support/FieldTypes/File.php
index aeba801d7c..dda293608b 100644
--- a/packages/admin/src/Support/FieldTypes/File.php
+++ b/packages/admin/src/Support/FieldTypes/File.php
@@ -13,6 +13,7 @@ class File extends BaseFieldType
 
     public static function getFilamentComponent(Attribute $attribute): Component
     {
-        return TextInput::make($attribute->handle);
+        return TextInput::make($attribute->handle)
+            ->helperText($attribute->translate('description'));
     }
 }
diff --git a/packages/admin/src/Support/FieldTypes/ListField.php b/packages/admin/src/Support/FieldTypes/ListField.php
index 8ae0992141..fd6b675981 100644
--- a/packages/admin/src/Support/FieldTypes/ListField.php
+++ b/packages/admin/src/Support/FieldTypes/ListField.php
@@ -15,6 +15,6 @@ public static function getFilamentComponent(Attribute $attribute): Component
     {
         return KeyValue::make($attribute->handle)->dehydrateStateUsing(function ($state) {
             return $state;
-        });
+        })->helperText($attribute->translate('description'));
     }
 }
diff --git a/packages/admin/src/Support/FieldTypes/Number.php b/packages/admin/src/Support/FieldTypes/Number.php
index bac2d70314..fe24ab5e42 100644
--- a/packages/admin/src/Support/FieldTypes/Number.php
+++ b/packages/admin/src/Support/FieldTypes/Number.php
@@ -19,11 +19,11 @@ public static function getFilamentComponent(Attribute $attribute): Component
         $input = TextField::getFilamentComponent($attribute)->numeric();
 
         if ($min) {
-            $input->min($min);
+            $input->minValue($min);
         }
 
         if ($max) {
-            $input->max($max);
+            $input->maxValue($max);
         }
 
         return $input;
diff --git a/packages/admin/src/Support/FieldTypes/TextField.php b/packages/admin/src/Support/FieldTypes/TextField.php
index 4e76dea6ad..ef79a6be58 100644
--- a/packages/admin/src/Support/FieldTypes/TextField.php
+++ b/packages/admin/src/Support/FieldTypes/TextField.php
@@ -24,9 +24,11 @@ public static function getConfigurationFields(): array
     public static function getFilamentComponent(Attribute $attribute): Component
     {
         if ($attribute->configuration->get('richtext')) {
-            return RichEditor::make($attribute->handle);
+            return RichEditor::make($attribute->handle)
+                ->helperText($attribute->translate('description'));
         }
 
-        return TextInput::make($attribute->handle);
+        return TextInput::make($attribute->handle)
+            ->helperText($attribute->translate('description'));
     }
 }
diff --git a/packages/admin/src/Support/FieldTypes/Toggle.php b/packages/admin/src/Support/FieldTypes/Toggle.php
index 401a0b0a48..87316a407b 100644
--- a/packages/admin/src/Support/FieldTypes/Toggle.php
+++ b/packages/admin/src/Support/FieldTypes/Toggle.php
@@ -13,6 +13,10 @@ class Toggle extends BaseFieldType
 
     public static function getFilamentComponent(Attribute $attribute): Component
     {
-        return ToggleInput::make($attribute->handle)->default('true')->live();
+        return ToggleInput::make($attribute->handle)->default('true')
+            ->helperText(
+                $attribute->translate('description')
+            )
+            ->live();
     }
 }
diff --git a/packages/admin/src/Support/FieldTypes/TranslatedText.php b/packages/admin/src/Support/FieldTypes/TranslatedText.php
index 3cb9bd67d3..5dc3465332 100644
--- a/packages/admin/src/Support/FieldTypes/TranslatedText.php
+++ b/packages/admin/src/Support/FieldTypes/TranslatedText.php
@@ -18,6 +18,7 @@ public static function getConfigurationFields(): array
 
     public static function getFilamentComponent(Attribute $attribute): Component
     {
-        return TranslatedTextInput::make($attribute->handle);
+        return TranslatedTextInput::make($attribute->handle)
+            ->helperText($attribute->translate('description'));
     }
 }
diff --git a/packages/admin/src/Support/FieldTypes/YouTube.php b/packages/admin/src/Support/FieldTypes/YouTube.php
index e25994a121..571f4d3789 100644
--- a/packages/admin/src/Support/FieldTypes/YouTube.php
+++ b/packages/admin/src/Support/FieldTypes/YouTube.php
@@ -16,7 +16,7 @@ public static function getFilamentComponent(Attribute $attribute): Component
         return YouTubeInput::make($attribute->handle)
             ->live(debounce: 200)
             ->helperText(
-                __('lunarpanel::components.forms.youtube.helperText')
+                $attribute->translate('description') ?? __('lunarpanel::components.forms.youtube.helperText')
             );
     }
 }
diff --git a/packages/admin/src/Support/Forms/Components/MediaSelect.php b/packages/admin/src/Support/Forms/Components/MediaSelect.php
new file mode 100644
index 0000000000..b62d70982a
--- /dev/null
+++ b/packages/admin/src/Support/Forms/Components/MediaSelect.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Lunar\Admin\Support\Forms\Components;
+
+use Filament\Forms\Components\Select;
+
+class MediaSelect extends Select
+{
+    protected string $view = 'lunarpanel::forms.components.media-select';
+
+    protected function setUp(): void
+    {
+        parent::setUp();
+    }
+}
diff --git a/packages/admin/src/Support/Pages/BaseCreateRecord.php b/packages/admin/src/Support/Pages/BaseCreateRecord.php
index 3d60bebf96..679e2e7849 100644
--- a/packages/admin/src/Support/Pages/BaseCreateRecord.php
+++ b/packages/admin/src/Support/Pages/BaseCreateRecord.php
@@ -7,8 +7,10 @@
 
 abstract class BaseCreateRecord extends CreateRecord
 {
+    use Concerns\ExtendsFooterWidgets;
     use Concerns\ExtendsFormActions;
     use Concerns\ExtendsHeaderActions;
+    use Concerns\ExtendsHeaderWidgets;
     use \Lunar\Admin\Support\Concerns\CallsHooks;
 
     protected function mutateFormDataBeforeCreate(array $data): array
diff --git a/packages/admin/src/Support/Pages/BaseEditRecord.php b/packages/admin/src/Support/Pages/BaseEditRecord.php
index b52991fc07..a51b632e07 100644
--- a/packages/admin/src/Support/Pages/BaseEditRecord.php
+++ b/packages/admin/src/Support/Pages/BaseEditRecord.php
@@ -7,8 +7,10 @@
 
 abstract class BaseEditRecord extends EditRecord
 {
+    use Concerns\ExtendsFooterWidgets;
     use Concerns\ExtendsFormActions;
     use Concerns\ExtendsHeaderActions;
+    use Concerns\ExtendsHeaderWidgets;
     use \Lunar\Admin\Support\Concerns\CallsHooks;
 
     protected function mutateFormDataBeforeFill(array $data): array
diff --git a/packages/admin/src/Support/Pages/BaseListRecords.php b/packages/admin/src/Support/Pages/BaseListRecords.php
index b199b793fa..4d7f21ffab 100644
--- a/packages/admin/src/Support/Pages/BaseListRecords.php
+++ b/packages/admin/src/Support/Pages/BaseListRecords.php
@@ -8,7 +8,9 @@
 
 abstract class BaseListRecords extends ListRecords
 {
+    use Concerns\ExtendsFooterWidgets;
     use Concerns\ExtendsHeaderActions;
+    use Concerns\ExtendsHeaderWidgets;
     use \Lunar\Admin\Support\Concerns\CallsHooks;
 
     protected function applySearchToTableQuery(Builder $query): Builder
diff --git a/packages/admin/src/Support/Pages/BaseViewRecord.php b/packages/admin/src/Support/Pages/BaseViewRecord.php
new file mode 100644
index 0000000000..6cadd9df8e
--- /dev/null
+++ b/packages/admin/src/Support/Pages/BaseViewRecord.php
@@ -0,0 +1,11 @@
+<?php
+
+namespace Lunar\Admin\Support\Pages;
+
+use Filament\Resources\Pages\ViewRecord;
+
+abstract class BaseViewRecord extends ViewRecord
+{
+    use Concerns\ExtendsHeaderActions;
+    use \Lunar\Admin\Support\Concerns\CallsHooks;
+}
diff --git a/packages/admin/src/Support/Pages/Concerns/ExtendsFooterWidgets.php b/packages/admin/src/Support/Pages/Concerns/ExtendsFooterWidgets.php
new file mode 100644
index 0000000000..0c026e854f
--- /dev/null
+++ b/packages/admin/src/Support/Pages/Concerns/ExtendsFooterWidgets.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace Lunar\Admin\Support\Pages\Concerns;
+
+trait ExtendsFooterWidgets
+{
+    protected function getDefaultFooterWidgets(): array
+    {
+        return [];
+    }
+
+    protected function getFooterWidgets(): array
+    {
+        return $this->callLunarHook('footerWidgets', $this->getDefaultFooterWidgets());
+    }
+}
diff --git a/packages/admin/src/Support/Pages/Concerns/ExtendsHeaderWidgets.php b/packages/admin/src/Support/Pages/Concerns/ExtendsHeaderWidgets.php
new file mode 100644
index 0000000000..66f992a213
--- /dev/null
+++ b/packages/admin/src/Support/Pages/Concerns/ExtendsHeaderWidgets.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace Lunar\Admin\Support\Pages\Concerns;
+
+trait ExtendsHeaderWidgets
+{
+    protected function getDefaultHeaderWidgets(): array
+    {
+        return [];
+    }
+
+    protected function getHeaderWidgets(): array
+    {
+        return $this->callLunarHook('headerWidgets', $this->getDefaultHeaderWidgets());
+    }
+}
diff --git a/packages/admin/src/Support/RelationManagers/ChannelRelationManager.php b/packages/admin/src/Support/RelationManagers/ChannelRelationManager.php
index e346dbcaf4..cc0af2095a 100644
--- a/packages/admin/src/Support/RelationManagers/ChannelRelationManager.php
+++ b/packages/admin/src/Support/RelationManagers/ChannelRelationManager.php
@@ -11,6 +11,8 @@
 
 class ChannelRelationManager extends RelationManager
 {
+    protected static bool $isLazy = false;
+
     protected static string $relationship = 'channels';
 
     public function isReadOnly(): bool
diff --git a/packages/admin/src/Support/RelationManagers/MediaRelationManager.php b/packages/admin/src/Support/RelationManagers/MediaRelationManager.php
index ea394cad36..4338e12ff2 100644
--- a/packages/admin/src/Support/RelationManagers/MediaRelationManager.php
+++ b/packages/admin/src/Support/RelationManagers/MediaRelationManager.php
@@ -15,6 +15,8 @@
 
 class MediaRelationManager extends RelationManager
 {
+    protected static bool $isLazy = false;
+
     protected static string $relationship = 'media';
 
     public string $mediaCollection = 'default';
diff --git a/packages/core/database/factories/AttributeFactory.php b/packages/core/database/factories/AttributeFactory.php
index a178a49a24..4c692617ed 100644
--- a/packages/core/database/factories/AttributeFactory.php
+++ b/packages/core/database/factories/AttributeFactory.php
@@ -23,6 +23,9 @@ public function definition(): array
             'name' => [
                 'en' => $this->faker->name(),
             ],
+            'description' => [
+                'en' => Str::limit($this->faker->text(), 100),
+            ],
             'handle' => Str::slug($this->faker->name()),
             'section' => $this->faker->name(),
             'type' => \Lunar\FieldTypes\Text::class,
diff --git a/packages/core/database/factories/ProductOptionFactory.php b/packages/core/database/factories/ProductOptionFactory.php
index 94d782c35a..e0aeca3f48 100644
--- a/packages/core/database/factories/ProductOptionFactory.php
+++ b/packages/core/database/factories/ProductOptionFactory.php
@@ -24,7 +24,7 @@ public function definition(): array
             'label' => [
                 'en' => $name,
             ],
-            'position' => self::$position++,
+            'shared' => true,
         ];
     }
 }
diff --git a/packages/core/database/migrations/2024_01_11_100000_add_description_to_attributes_table.php b/packages/core/database/migrations/2024_01_11_100000_add_description_to_attributes_table.php
new file mode 100644
index 0000000000..be85c88ede
--- /dev/null
+++ b/packages/core/database/migrations/2024_01_11_100000_add_description_to_attributes_table.php
@@ -0,0 +1,22 @@
+<?php
+
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+use Lunar\Base\Migration;
+
+class AddDescriptionToAttributesTable extends Migration
+{
+    public function up()
+    {
+        Schema::table($this->prefix.'attributes', function (Blueprint $table) {
+            $table->json('description')->after('name');
+        });
+    }
+
+    public function down()
+    {
+        Schema::table($this->prefix.'attributes', function ($table) {
+            $table->dropColumn('description');
+        });
+    }
+}
diff --git a/packages/core/database/migrations/2024_01_16_100000_create_product_product_option_table.php b/packages/core/database/migrations/2024_01_16_100000_create_product_product_option_table.php
new file mode 100644
index 0000000000..f8c0734ba8
--- /dev/null
+++ b/packages/core/database/migrations/2024_01_16_100000_create_product_product_option_table.php
@@ -0,0 +1,22 @@
+<?php
+
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+use Lunar\Base\Migration;
+
+class CreateProductProductOptionTable extends Migration
+{
+    public function up()
+    {
+        Schema::create($this->prefix.'product_product_option', function (Blueprint $table) {
+            $table->foreignId('product_id')->constrained($this->prefix.'products');
+            $table->foreignId('product_option_id')->constrained($this->prefix.'product_options');
+            $table->smallInteger('position')->index();
+        });
+    }
+
+    public function down()
+    {
+        Schema::dropIfExists($this->prefix.'product_product_option');
+    }
+}
diff --git a/packages/core/database/migrations/2024_01_16_100010_update_product_option_relations.php b/packages/core/database/migrations/2024_01_16_100010_update_product_option_relations.php
new file mode 100644
index 0000000000..502fb2760e
--- /dev/null
+++ b/packages/core/database/migrations/2024_01_16_100010_update_product_option_relations.php
@@ -0,0 +1,64 @@
+<?php
+
+use Illuminate\Support\Facades\Schema;
+use Lunar\Base\Migration;
+use Lunar\Facades\DB;
+
+class UpdateProductOptionRelations extends Migration
+{
+    public $withinTransaction = true;
+
+    public function up()
+    {
+        $variantsTable = $this->prefix.'product_variants';
+        $productsTable = $this->prefix.'products';
+        $optionsTable = $this->prefix.'product_options';
+        $optionValueTable = $this->prefix.'product_option_values';
+        $variantOptionsValueTable = $this->prefix.'product_option_value_product_variant';
+
+        DB::table($variantOptionsValueTable)->join(
+            $variantsTable,
+            "{$variantOptionsValueTable}.variant_id",
+            '=',
+            "{$variantsTable}.id"
+        )->join(
+            $optionValueTable,
+            "{$variantOptionsValueTable}.value_id",
+            '=',
+            "{$optionValueTable}.id"
+        )->join(
+            $optionsTable,
+            "{$optionValueTable}.product_option_id",
+            '=',
+            "{$optionsTable}.id"
+        )->join(
+            $productsTable,
+            "{$variantsTable}.product_id",
+            '=',
+            "{$productsTable}.id"
+        )->select([
+            "{$productsTable}.id as product_id",
+            "{$optionsTable}.id as product_option_id",
+            "{$optionsTable}.position",
+        ])->groupBy([
+            "{$productsTable}.id",
+            "{$optionsTable}.id",
+            "{$optionsTable}.position",
+        ])
+            ->orderBy("{$productsTable}.id")
+            ->chunk(200, function ($rows) {
+                DB::table(
+                    $this->prefix.'product_product_option'
+                )->insert(
+                    $rows->map(
+                        fn ($row) => (array) $row
+                    )->toArray()
+                );
+            });
+    }
+
+    public function down()
+    {
+        Schema::dropIfExists($this->prefix.'product_product_option');
+    }
+}
diff --git a/packages/core/database/migrations/2024_01_16_100020_remove_position_from_product_options_table.php b/packages/core/database/migrations/2024_01_16_100020_remove_position_from_product_options_table.php
new file mode 100644
index 0000000000..5a7a008b1c
--- /dev/null
+++ b/packages/core/database/migrations/2024_01_16_100020_remove_position_from_product_options_table.php
@@ -0,0 +1,22 @@
+<?php
+
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+use Lunar\Base\Migration;
+
+class RemovePositionFromProductOptionsTable extends Migration
+{
+    public function up()
+    {
+        Schema::table($this->prefix.'product_options', function (Blueprint $table) {
+            $table->dropColumn('position');
+        });
+    }
+
+    public function down()
+    {
+        Schema::table($this->prefix.'product_options', function (Blueprint $table) {
+            $table->smallInteger('position')->after('label');
+        });
+    }
+}
diff --git a/packages/core/database/migrations/2024_01_16_100030_add_and_set_shared_to_product_options_table.php b/packages/core/database/migrations/2024_01_16_100030_add_and_set_shared_to_product_options_table.php
new file mode 100644
index 0000000000..3f70da28b5
--- /dev/null
+++ b/packages/core/database/migrations/2024_01_16_100030_add_and_set_shared_to_product_options_table.php
@@ -0,0 +1,28 @@
+<?php
+
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+use Lunar\Base\Migration;
+
+class AddAndSetSharedToProductOptionsTable extends Migration
+{
+    public $withinTransaction = true;
+
+    public function up()
+    {
+        Schema::table($this->prefix.'product_options', function (Blueprint $table) {
+            $table->boolean('shared')->after('handle')->default(false)->index();
+        });
+
+        \Lunar\Facades\DB::table($this->prefix.'product_options')->update([
+            'shared' => true,
+        ]);
+    }
+
+    public function down()
+    {
+        Schema::table($this->prefix.'product_options', function (Blueprint $table) {
+            $table->dropColumn('shared');
+        });
+    }
+}
diff --git a/packages/core/database/migrations/2024_01_24_100000_update_product_option_handle_fk.php b/packages/core/database/migrations/2024_01_24_100000_update_product_option_handle_fk.php
new file mode 100644
index 0000000000..5d47915358
--- /dev/null
+++ b/packages/core/database/migrations/2024_01_24_100000_update_product_option_handle_fk.php
@@ -0,0 +1,36 @@
+<?php
+
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+use Lunar\Base\Migration;
+
+class UpdateProductOptionHandleFk extends Migration
+{
+    public $withinTransaction = true;
+
+    public function up()
+    {
+        Schema::table($this->prefix.'product_options', function (Blueprint $table) {
+            $table->dropUnique(
+                $this->prefix.'product_options_handle_unique'
+            );
+        });
+
+        Schema::table($this->prefix.'product_options', function (Blueprint $table) {
+            $table->index('handle');
+        });
+    }
+
+    public function down()
+    {
+        Schema::table($this->prefix.'product_options', function (Blueprint $table) {
+            $table->dropIndex(
+                $this->prefix.'product_options_handle_index'
+            );
+        });
+
+        Schema::table($this->prefix.'product_options', function (Blueprint $table) {
+            $table->unique('handle');
+        });
+    }
+}
diff --git a/packages/core/database/migrations/2024_01_29_100000_update_nullable_currency_on_prices_table.php b/packages/core/database/migrations/2024_01_29_100000_update_nullable_currency_on_prices_table.php
new file mode 100644
index 0000000000..472f19f1fd
--- /dev/null
+++ b/packages/core/database/migrations/2024_01_29_100000_update_nullable_currency_on_prices_table.php
@@ -0,0 +1,22 @@
+-<?php
+
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+use Lunar\Base\Migration;
+
+class UpdateNullableCurrencyOnPricesTable extends Migration
+{
+    public function up()
+    {
+        Schema::table($this->prefix.'prices', function (Blueprint $table) {
+            $table->foreignId('currency_id')->nullable(false)->change();
+        });
+    }
+
+    public function down()
+    {
+        Schema::table($this->prefix.'prices', function (Blueprint $table) {
+            $table->foreignId('currency_id')->nullable(true)->change();
+        });
+    }
+}
diff --git a/packages/core/database/migrations/2024_01_31_100000_update_tier_to_quantity_break_on_prices_table.php b/packages/core/database/migrations/2024_01_31_100000_update_tier_to_quantity_break_on_prices_table.php
new file mode 100644
index 0000000000..43582f3963
--- /dev/null
+++ b/packages/core/database/migrations/2024_01_31_100000_update_tier_to_quantity_break_on_prices_table.php
@@ -0,0 +1,22 @@
+-<?php
+
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+use Lunar\Base\Migration;
+
+class UpdateTierToQuantityBreakOnPricesTable extends Migration
+{
+    public function up()
+    {
+        Schema::table($this->prefix.'prices', function (Blueprint $table) {
+            $table->renameColumn('tier', 'quantity_break');
+        });
+    }
+
+    public function down()
+    {
+        Schema::table($this->prefix.'prices', function (Blueprint $table) {
+            $table->renameColumn('quantity_break', 'tier');
+        });
+    }
+}
diff --git a/packages/core/src/Base/DataTransferObjects/PricingResponse.php b/packages/core/src/Base/DataTransferObjects/PricingResponse.php
index 894fba842f..18e84a39e6 100644
--- a/packages/core/src/Base/DataTransferObjects/PricingResponse.php
+++ b/packages/core/src/Base/DataTransferObjects/PricingResponse.php
@@ -10,7 +10,7 @@ class PricingResponse
     public function __construct(
         public Price $matched,
         public Price $base,
-        public Collection $tiered,
+        public Collection $quantityBreaks,
         public Collection $customerGroupPrices,
     ) {
         //
diff --git a/packages/core/src/Base/ShippingModifiers.php b/packages/core/src/Base/ShippingModifiers.php
index 3f1b06e3db..60ab1da31a 100644
--- a/packages/core/src/Base/ShippingModifiers.php
+++ b/packages/core/src/Base/ShippingModifiers.php
@@ -32,7 +32,7 @@ public function getModifiers()
     /**
      * Add a shipping modifier.
      *
-     * @param $modifier Class reference to the modifier.
+     * @param  string  $modifier Class reference to the modifier.
      * @return void
      */
     public function add($modifier)
diff --git a/packages/core/src/Base/Traits/HasPrices.php b/packages/core/src/Base/Traits/HasPrices.php
index b72065b51c..a3f20963ce 100644
--- a/packages/core/src/Base/Traits/HasPrices.php
+++ b/packages/core/src/Base/Traits/HasPrices.php
@@ -25,7 +25,12 @@ public function prices(): MorphMany
      */
     public function basePrices(): MorphMany
     {
-        return $this->prices()->whereTier(1)->whereNull('customer_group_id');
+        return $this->prices()->whereQuantityBreak(1)->whereNull('customer_group_id');
+    }
+
+    public function quantityBreaks(): MorphMany
+    {
+        return $this->prices()->where('quantity_break', '>', 1);
     }
 
     /**
diff --git a/packages/core/src/Base/Traits/HasTranslations.php b/packages/core/src/Base/Traits/HasTranslations.php
index e9bbb5f922..c09592ae2e 100644
--- a/packages/core/src/Base/Traits/HasTranslations.php
+++ b/packages/core/src/Base/Traits/HasTranslations.php
@@ -12,7 +12,7 @@ trait HasTranslations
      *
      * @param  string  $attribute
      * @param  string  $locale
-     * @return string
+     * @return string|null
      */
     public function translate($attribute, $locale = null)
     {
@@ -43,7 +43,7 @@ public function translate($attribute, $locale = null)
      *
      * @param  string  $attribute
      * @param  string  $locale
-     * @return string
+     * @return string|null
      */
     public function translateAttribute($attribute, $locale = null)
     {
@@ -61,9 +61,9 @@ public function translateAttribute($attribute, $locale = null)
 
         $value = Arr::get($translations, $locale ?: app()->getLocale(), Arr::first($translations));
 
-        // We we don't have a value, we just return null as it may not have a value.
+        // When we don't have a value, we just return null as it may not have a value.
         if (! $value) {
-            return;
+            return null;
         }
 
         /**
@@ -80,7 +80,7 @@ public function translateAttribute($attribute, $locale = null)
     /**
      * Shorthand to translate an attribute.
      *
-     * @return void
+     * @return string|null
      */
     public function attr(...$params)
     {
diff --git a/packages/core/src/LunarServiceProvider.php b/packages/core/src/LunarServiceProvider.php
index e74400fca4..77329f07ac 100644
--- a/packages/core/src/LunarServiceProvider.php
+++ b/packages/core/src/LunarServiceProvider.php
@@ -64,6 +64,9 @@
 use Lunar\Models\Language;
 use Lunar\Models\Order;
 use Lunar\Models\OrderLine;
+use Lunar\Models\ProductOption;
+use Lunar\Models\ProductOptionValue;
+use Lunar\Models\ProductVariant;
 use Lunar\Models\Transaction;
 use Lunar\Models\Url;
 use Lunar\Observers\AddressObserver;
@@ -75,6 +78,9 @@
 use Lunar\Observers\LanguageObserver;
 use Lunar\Observers\OrderLineObserver;
 use Lunar\Observers\OrderObserver;
+use Lunar\Observers\ProductOptionObserver;
+use Lunar\Observers\ProductOptionValueObserver;
+use Lunar\Observers\ProductVariantObserver;
 use Lunar\Observers\TransactionObserver;
 use Lunar\Observers\UrlObserver;
 
@@ -277,6 +283,9 @@ protected function registerObservers(): void
         Url::observe(UrlObserver::class);
         Collection::observe(CollectionObserver::class);
         CartLine::observe(CartLineObserver::class);
+        ProductOption::observe(ProductOptionObserver::class);
+        ProductOptionValue::observe(ProductOptionValueObserver::class);
+        ProductVariant::observe(ProductVariantObserver::class);
         Order::observe(OrderObserver::class);
         OrderLine::observe(OrderLineObserver::class);
         Address::observe(AddressObserver::class);
diff --git a/packages/core/src/Managers/PricingManager.php b/packages/core/src/Managers/PricingManager.php
index 9bd1c2065f..a63e7d7282 100644
--- a/packages/core/src/Managers/PricingManager.php
+++ b/packages/core/src/Managers/PricingManager.php
@@ -195,25 +195,25 @@ public function get()
         })->sortBy('price');
 
         // Get our base price
-        $basePrice = $prices->first(fn ($price) => $price->tier == 1 && ! $price->customer_group_id);
+        $basePrice = $prices->first(fn ($price) => $price->quantity_break == 1 && ! $price->customer_group_id);
 
         // To start, we'll set the matched price to the base price.
         $matched = $basePrice;
 
         // If we have customer group prices, we should find the cheapest one and send that back.
         $potentialGroupPrice = $prices->filter(function ($price) {
-            return (bool) $price->customer_group_id && $price->tier == 1;
+            return (bool) $price->customer_group_id && ($price->quantity_break == 1);
         })->sortBy('price');
 
         $matched = $potentialGroupPrice->first() ?: $matched;
 
-        // Get all tiers that match for the given quantity. These take priority over the other steps
+        // Get all quantity breaks that match for the given quantity. These take priority over the other steps
         // as we could be bulk purchasing.
-        $tieredPricing = $prices->filter(function ($price) {
-            return $price->tier > 1 && $this->qty >= $price->tier;
+        $quantityBreaks = $prices->filter(function ($price) {
+            return $price->quantity_break > 1 && $this->qty >= $price->quantity_break;
         })->sortBy('price');
 
-        $matched = $tieredPricing->first() ?: $matched;
+        $matched = $quantityBreaks->first() ?: $matched;
 
         if (! $matched) {
             throw new \ErrorException('No price set.');
@@ -221,8 +221,8 @@ public function get()
 
         $this->pricing = new PricingResponse(
             matched: $matched,
-            base: $prices->first(fn ($price) => $price->tier == 1),
-            tiered: $prices->filter(fn ($price) => $price->tier > 1),
+            base: $prices->first(fn ($price) => $price->quantity_break == 1),
+            quantityBreaks: $prices->filter(fn ($price) => $price->quantity_break > 1),
             customerGroupPrices: $prices->filter(fn ($price) => (bool) $price->customer_group_id)
         );
 
diff --git a/packages/core/src/Models/Attribute.php b/packages/core/src/Models/Attribute.php
index 1b0420eedf..4f322e2a1c 100644
--- a/packages/core/src/Models/Attribute.php
+++ b/packages/core/src/Models/Attribute.php
@@ -72,6 +72,7 @@ protected static function newFactory(): AttributeFactory
      */
     protected $casts = [
         'name' => AsCollection::class,
+        'description' => AsCollection::class,
         'configuration' => AsCollection::class,
     ];
 
diff --git a/packages/core/src/Models/Price.php b/packages/core/src/Models/Price.php
index 5554ea0e26..3807b77e3b 100644
--- a/packages/core/src/Models/Price.php
+++ b/packages/core/src/Models/Price.php
@@ -19,7 +19,7 @@
  * @property int $priceable_id
  * @property int $price
  * @property ?int $compare_price
- * @property int $tier
+ * @property int $quantity_break
  * @property ?\Illuminate\Support\Carbon $created_at
  * @property ?\Illuminate\Support\Carbon $updated_at
  */
diff --git a/packages/core/src/Models/Product.php b/packages/core/src/Models/Product.php
index ae61229621..0133f37a9b 100644
--- a/packages/core/src/Models/Product.php
+++ b/packages/core/src/Models/Product.php
@@ -213,4 +213,14 @@ public function prices(): HasManyThrough
             'priceable_id'
         )->wherePriceableType(ProductVariant::class);
     }
+
+    public function productOptions(): BelongsToMany
+    {
+        $prefix = config('lunar.database.table_prefix');
+
+        return $this->belongsToMany(
+            ProductOption::class,
+            "{$prefix}product_product_option"
+        )->withPivot(['position'])->orderByPivot('position');
+    }
 }
diff --git a/packages/core/src/Models/ProductOption.php b/packages/core/src/Models/ProductOption.php
index 8bb7402a66..b10d97c13e 100644
--- a/packages/core/src/Models/ProductOption.php
+++ b/packages/core/src/Models/ProductOption.php
@@ -2,9 +2,11 @@
 
 namespace Lunar\Models;
 
+use Illuminate\Database\Eloquent\Builder;
 use Illuminate\Database\Eloquent\Casts\AsCollection;
 use Illuminate\Database\Eloquent\Casts\Attribute;
 use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Relations\BelongsToMany;
 use Illuminate\Database\Eloquent\Relations\HasMany;
 use Lunar\Base\BaseModel;
 use Lunar\Base\Traits\HasMacros;
@@ -39,6 +41,7 @@ class ProductOption extends BaseModel implements SpatieHasMedia
     protected $casts = [
         'name' => AsCollection::class,
         'label' => AsCollection::class,
+        'shared' => 'boolean',
     ];
 
     /**
@@ -75,6 +78,16 @@ protected function label(): Attribute
      */
     protected $guarded = [];
 
+    public function scopeShared(Builder $builder)
+    {
+        return $builder->where('shared', '=', true);
+    }
+
+    public function scopeExclusive(Builder $builder)
+    {
+        return $builder->where('shared', '=', false);
+    }
+
     /**
      * Get the values.
      *
@@ -84,4 +97,14 @@ public function values(): HasMany
     {
         return $this->hasMany(ProductOptionValue::class)->orderBy('position');
     }
+
+    public function products(): BelongsToMany
+    {
+        $prefix = config('lunar.database.table_prefix');
+
+        return $this->belongsToMany(
+            Product::class,
+            "{$prefix}product_product_option"
+        )->withPivot(['position'])->orderByPivot('position');
+    }
 }
diff --git a/packages/core/src/Models/ProductVariant.php b/packages/core/src/Models/ProductVariant.php
index 6759c1573e..74b4287e53 100644
--- a/packages/core/src/Models/ProductVariant.php
+++ b/packages/core/src/Models/ProductVariant.php
@@ -9,6 +9,7 @@
 use Lunar\Base\BaseModel;
 use Lunar\Base\Casts\AsAttributeData;
 use Lunar\Base\Purchasable;
+use Lunar\Base\Traits\HasAttributes;
 use Lunar\Base\Traits\HasDimensions;
 use Lunar\Base\Traits\HasMacros;
 use Lunar\Base\Traits\HasPrices;
@@ -51,6 +52,7 @@
  */
 class ProductVariant extends BaseModel implements Purchasable
 {
+    use HasAttributes;
     use HasDimensions;
     use HasFactory;
     use HasMacros;
diff --git a/packages/core/src/Observers/ProductOptionObserver.php b/packages/core/src/Observers/ProductOptionObserver.php
new file mode 100644
index 0000000000..4826b99f5d
--- /dev/null
+++ b/packages/core/src/Observers/ProductOptionObserver.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace Lunar\Observers;
+
+use Lunar\Models\ProductOption;
+use Lunar\Models\ProductOptionValue;
+
+class ProductOptionObserver
+{
+    /**
+     * Handle the ProductOption "deleting" event.
+     *
+     * @return void
+     */
+    public function deleting(ProductOption $productOption)
+    {
+        $productOption->products()->detach();
+        $productOption->values()->each(
+            fn (ProductOptionValue $optionValue) => $optionValue->delete()
+        );
+    }
+}
diff --git a/packages/core/src/Observers/ProductOptionValueObserver.php b/packages/core/src/Observers/ProductOptionValueObserver.php
new file mode 100644
index 0000000000..b372caab40
--- /dev/null
+++ b/packages/core/src/Observers/ProductOptionValueObserver.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Lunar\Observers;
+
+use Lunar\Models\ProductOptionValue;
+
+class ProductOptionValueObserver
+{
+    /**
+     * Handle the ProductOptionValue "deleting" event.
+     *
+     * @return void
+     */
+    public function deleting(ProductOptionValue $productOptionValue)
+    {
+        $productOptionValue->variants()->detach();
+    }
+}
diff --git a/packages/core/src/Observers/ProductVariantObserver.php b/packages/core/src/Observers/ProductVariantObserver.php
new file mode 100644
index 0000000000..5d58b8e347
--- /dev/null
+++ b/packages/core/src/Observers/ProductVariantObserver.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace Lunar\Observers;
+
+use Lunar\Models\ProductVariant;
+
+class ProductVariantObserver
+{
+    /**
+     * Handle the ProductVariant "deleted" event.
+     *
+     * @return void
+     */
+    public function deleting(ProductVariant $productVariant)
+    {
+        $productVariant->prices()->delete();
+        $productVariant->values()->detach();
+        $productVariant->images()->detach();
+    }
+}
diff --git a/packages/table-rate-shipping/README.md b/packages/table-rate-shipping/README.md
index 899886b091..4b90525994 100644
--- a/packages/table-rate-shipping/README.md
+++ b/packages/table-rate-shipping/README.md
@@ -1,3 +1,116 @@
 # Lunar Table Rate Shipping
 
-WIP
+
+# Requirements
+
+- LunarPHP Admin `>` `1.x`
+
+# Installation
+
+Install via Composer
+
+```
+composer require lunarphp/table-rate-shipping
+```
+
+Then register the plugin in your service provider
+
+```php
+use Lunar\Admin\Support\Facades\LunarPanel;
+use Lunar\Shipping\ShippingPlugin;
+// ...
+
+public function register(): void
+{
+    LunarPanel::panel(function (Panel $panel) {
+        return $panel->plugin(new ShippingPlugin());
+    })->register();
+    
+    // ...
+}
+```
+# Getting Started
+This addon provides an easy way for you to add different shipping to your storefront and allow your customers to choose from the different shipping rates you have set up, based on various factors such as zones, minimum spend etc
+
+## Shipping Methods
+
+Shipping Methods are the different ways in which your storefront can send orders to customers, you could also allow customers to collect their order from your store which this addon supports.
+
+## Shipping Zones
+
+Shipping Zones allow you to section of area's of the countries you ship to, providing you with an easy way to offer distinct shipping methods and pricing to each zone, each zone can be restricted by the following:
+
+- Postal codes
+- Country
+- State/Province (based on country)
+
+## Shipping Rates
+
+Shipping Rates are the prices you offer for each of your shipping zones, they are linked to a shipping method. So for example you might have a Courier Area Shipping Zone and an Everywhere Else Shipping Zone, you can offer different pricing restrictions using the same shipping methods.
+
+## Shipping Exclusion Lists
+
+Sometimes, you might not want to ship certain items to particular Shipping Zone, this is where exclusion lists come in. You can associate purchasables to a list which you can then associate to a shipping zone, if a cart contains any of them then they won't be able to select a shipping rate.
+
+# Storefront usage
+
+This addon uses the shipping modifiers provided by the Lunar core, so you shouldn't need to change your existing implementation.
+
+```php
+$options = \Lunar\Base\ShippingManifest::getOptions(
+    $cart
+);
+```
+
+# Advanced usage
+
+## Return available drivers
+
+```php
+\Lunar\Shipping\Facades\Shipping::getSupportedDrivers();
+```
+
+## Using the driver directly
+
+```php
+\Lunar\Shipping\Facades\Shipping::with('ship-by')->resolve(
+    new \Lunar\Shipping\DataTransferObjects\ShippingOptionRequest(
+        shippingRate: \Lunar\Shipping\Models\ShippingRate $shippingRate,
+        cart: \Lunar\Models\Cart $cart
+    )
+);
+```
+
+## Shipping Zones
+
+Each method is optional, the more you add the more strict it becomes.
+
+```php
+$shippingZones = Lunar\Shipping\Facades\Shipping::zones()
+    ->country(\Lunar\Models\Country $country)
+    ->state(\Lunar\Models\State $state)
+    ->postcode(
+        new \Lunar\Shipping\DataTransferObjects\PostcodeLookup(
+            country: \Lunar\Models\Country $country,
+            postcode: 'NW1'
+        )
+    )->get()
+    
+$shippingZones->map(/* .. */);
+```
+
+## Shipping Rates
+
+```php
+$shippingRates = \Lunar\Shipping\Facades\Shipping::shippingRates(
+    \Lunar\Models\Cart $cart
+);
+```
+
+## Shipping Options
+
+```php
+$shippingOptions = \Lunar\Shipping\Facades\Shipping::shippingOptions(
+    \Lunar\Models\Cart $cart
+);
+```
\ No newline at end of file
diff --git a/packages/table-rate-shipping/database/factories/ShippingRateFactory.php b/packages/table-rate-shipping/database/factories/ShippingRateFactory.php
new file mode 100644
index 0000000000..28c27ca3de
--- /dev/null
+++ b/packages/table-rate-shipping/database/factories/ShippingRateFactory.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace Lunar\Shipping\Database\Factories;
+
+use Illuminate\Database\Eloquent\Factories\Factory;
+use Lunar\Shipping\Models\ShippingRate;
+
+class ShippingRateFactory extends Factory
+{
+    protected $model = ShippingRate::class;
+
+    public function definition(): array
+    {
+        return [];
+    }
+}
diff --git a/packages/table-rate-shipping/database/migrations/2022_04_28_110000_create_shipping_methods_table.php b/packages/table-rate-shipping/database/migrations/2022_04_28_110000_create_shipping_methods_table.php
index 5ad782d218..8c21a80c60 100644
--- a/packages/table-rate-shipping/database/migrations/2022_04_28_110000_create_shipping_methods_table.php
+++ b/packages/table-rate-shipping/database/migrations/2022_04_28_110000_create_shipping_methods_table.php
@@ -10,13 +10,12 @@ public function up()
     {
         Schema::create($this->prefix.'shipping_methods', function (Blueprint $table) {
             $table->bigIncrements('id');
-            $table->foreignId('shipping_zone_id')->constrained(
-                $this->prefix.'shipping_zones'
-            );
             $table->string('name');
             $table->text('description')->nullable();
             $table->string('code')->index()->nullable();
             $table->boolean('enabled')->default(true);
+            $table->boolean('stock_available')->default(false);
+            $table->time('cutoff')->nullable();
             $table->json('data')->nullable();
             $table->string('driver');
             $table->timestamps();
diff --git a/packages/table-rate-shipping/database/migrations/2022_04_28_111000_create_shipping_rates_table.php b/packages/table-rate-shipping/database/migrations/2022_04_28_111000_create_shipping_rates_table.php
new file mode 100644
index 0000000000..9f49cbd487
--- /dev/null
+++ b/packages/table-rate-shipping/database/migrations/2022_04_28_111000_create_shipping_rates_table.php
@@ -0,0 +1,28 @@
+<?php
+
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+use Lunar\Base\Migration;
+
+class CreateShippingRatesTable extends Migration
+{
+    public function up()
+    {
+        Schema::create($this->prefix.'shipping_rates', function (Blueprint $table) {
+            $table->bigIncrements('id');
+            $table->foreignId('shipping_method_id')->constrained(
+                $this->prefix.'shipping_methods'
+            );
+            $table->foreignId('shipping_zone_id')->constrained(
+                $this->prefix.'shipping_zones'
+            );
+            $table->boolean('enabled')->default(true);
+            $table->timestamps();
+        });
+    }
+
+    public function down()
+    {
+        Schema::dropIfExists($this->prefix.'shipping_rates');
+    }
+}
diff --git a/packages/table-rate-shipping/database/migrations/2022_04_28_120000_create_shipping_exclusion_lists_table.php b/packages/table-rate-shipping/database/migrations/2022_04_28_120000_create_shipping_exclusion_lists_table.php
index bc68089668..5d79885974 100644
--- a/packages/table-rate-shipping/database/migrations/2022_04_28_120000_create_shipping_exclusion_lists_table.php
+++ b/packages/table-rate-shipping/database/migrations/2022_04_28_120000_create_shipping_exclusion_lists_table.php
@@ -10,10 +10,6 @@ public function up()
     {
         Schema::create($this->prefix.'shipping_exclusion_lists', function (Blueprint $table) {
             $table->bigIncrements('id');
-            // $table->foreignId('shipping_exclusion_list_id')->constrained(
-            //     $this->prefix.'shipping_exclusion_lists'
-            // );
-            // $table->morphs('purchasable', 'shipping_exclusions_purchasable_type_purchasable_id_index');
             $table->string('name')->unique();
             $table->timestamps();
         });
diff --git a/packages/table-rate-shipping/database/migrations/2022_04_28_140000_create_shipping_exclusion_list_shipping_method_table.php b/packages/table-rate-shipping/database/migrations/2022_04_28_140000_create_shipping_exclusion_list_shipping_zone_table.php
similarity index 54%
rename from packages/table-rate-shipping/database/migrations/2022_04_28_140000_create_shipping_exclusion_list_shipping_method_table.php
rename to packages/table-rate-shipping/database/migrations/2022_04_28_140000_create_shipping_exclusion_list_shipping_zone_table.php
index eda8dfd212..c1e8372e4e 100644
--- a/packages/table-rate-shipping/database/migrations/2022_04_28_140000_create_shipping_exclusion_list_shipping_method_table.php
+++ b/packages/table-rate-shipping/database/migrations/2022_04_28_140000_create_shipping_exclusion_list_shipping_zone_table.php
@@ -4,17 +4,17 @@
 use Illuminate\Support\Facades\Schema;
 use Lunar\Base\Migration;
 
-class CreateShippingExclusionListShippingMethodTable extends Migration
+class CreateShippingExclusionListShippingZoneTable extends Migration
 {
     public function up()
     {
-        Schema::create($this->prefix.'exclusion_list_shipping_method', function (Blueprint $table) {
+        Schema::create($this->prefix.'exclusion_list_shipping_zone', function (Blueprint $table) {
             $table->bigIncrements('id');
             $table->foreignId('exclusion_id')->constrained(
                 $this->prefix.'shipping_exclusion_lists'
             );
-            $table->foreignId('method_id')->constrained(
-                $this->prefix.'shipping_methods'
+            $table->foreignId('shipping_zone_id')->constrained(
+                $this->prefix.'shipping_zones'
             );
             $table->timestamps();
         });
@@ -22,6 +22,6 @@ public function up()
 
     public function down()
     {
-        Schema::dropIfExists($this->prefix.'shipping_exclusion_list_shipping_method');
+        Schema::dropIfExists($this->prefix.'exclusion_list_shipping_zone');
     }
 }
diff --git a/packages/table-rate-shipping/database/migrations/2022_08_09_100000_add_cutoff_to_shipping_methods_table.php b/packages/table-rate-shipping/database/migrations/2022_08_09_100000_add_cutoff_to_shipping_methods_table.php
deleted file mode 100644
index abcf8719fd..0000000000
--- a/packages/table-rate-shipping/database/migrations/2022_08_09_100000_add_cutoff_to_shipping_methods_table.php
+++ /dev/null
@@ -1,22 +0,0 @@
-<?php
-
-use Illuminate\Database\Schema\Blueprint;
-use Illuminate\Support\Facades\Schema;
-use Lunar\Base\Migration;
-
-class AddCutoffToShippingMethodsTable extends Migration
-{
-    public function up()
-    {
-        Schema::table($this->prefix.'shipping_methods', function (Blueprint $table) {
-            $table->time('cutoff')->nullable()->after('enabled');
-        });
-    }
-
-    public function down()
-    {
-        Schema::table($this->prefix.'shipping_methods', function (Blueprint $table) {
-            $table->dropColumn('cutoff');
-        });
-    }
-}
diff --git a/packages/table-rate-shipping/database/migrations/2023_02_28_100000_add_stock_available_to_shipping_methods_table.php b/packages/table-rate-shipping/database/migrations/2023_02_28_100000_add_stock_available_to_shipping_methods_table.php
deleted file mode 100644
index b07f1f46a4..0000000000
--- a/packages/table-rate-shipping/database/migrations/2023_02_28_100000_add_stock_available_to_shipping_methods_table.php
+++ /dev/null
@@ -1,22 +0,0 @@
-<?php
-
-use Illuminate\Database\Schema\Blueprint;
-use Illuminate\Support\Facades\Schema;
-use Lunar\Base\Migration;
-
-class AddStockAvailableToShippingMethodsTable extends Migration
-{
-    public function up()
-    {
-        Schema::table($this->prefix.'shipping_methods', function (Blueprint $table) {
-            $table->boolean('stock_available')->default(false)->after('cutoff');
-        });
-    }
-
-    public function down()
-    {
-        Schema::table($this->prefix.'shipping_methods', function (Blueprint $table) {
-            $table->dropColumn('stock_available');
-        });
-    }
-}
diff --git a/packages/table-rate-shipping/lang/en/create.php b/packages/table-rate-shipping/lang/en/create.php
deleted file mode 100644
index 609739b221..0000000000
--- a/packages/table-rate-shipping/lang/en/create.php
+++ /dev/null
@@ -1,5 +0,0 @@
-<?php
-
-return [
-    'title' => 'Create Shipping Zone',
-];
diff --git a/packages/table-rate-shipping/lang/en/index.php b/packages/table-rate-shipping/lang/en/index.php
deleted file mode 100644
index a0e8be75e9..0000000000
--- a/packages/table-rate-shipping/lang/en/index.php
+++ /dev/null
@@ -1,8 +0,0 @@
-<?php
-
-return [
-    'menu_item' => 'Shipping',
-    'title' => 'Shipping Zones',
-    'no_results' => 'There are no Shipping Zones in the system',
-    'add_zone_btn' => 'Add shipping zone',
-];
diff --git a/packages/table-rate-shipping/lang/en/shipping-zones.php b/packages/table-rate-shipping/lang/en/shipping-zones.php
deleted file mode 100644
index d02178f500..0000000000
--- a/packages/table-rate-shipping/lang/en/shipping-zones.php
+++ /dev/null
@@ -1,10 +0,0 @@
-<?php
-
-return [
-    'show.shipping_method.delete' => 'Delete Shipping Method',
-    'show.shipping_method.delete_confirm' => 'Are you sure? This action cannot be undone, consider disabling the Shipping Method instead to preserve the data.',
-    'show.shipping_zone.delete' => 'Delete Shipping Zones',
-    'show.shipping_zone.delete_message' => 'This action cannot be undone',
-    'show.shipping_methods.title' => 'Shipping Methods',
-    'show.shipping_methods.add_btn' => 'Add shipping method',
-];
diff --git a/packages/table-rate-shipping/resources/lang/en/plugin.php b/packages/table-rate-shipping/resources/lang/en/plugin.php
new file mode 100644
index 0000000000..3b131322d4
--- /dev/null
+++ b/packages/table-rate-shipping/resources/lang/en/plugin.php
@@ -0,0 +1,7 @@
+<?php
+
+return [
+    'navigation' => [
+        'group' => 'Shipping',
+    ],
+];
diff --git a/packages/table-rate-shipping/resources/lang/en/relationmanagers.php b/packages/table-rate-shipping/resources/lang/en/relationmanagers.php
new file mode 100644
index 0000000000..f24eb5b3b3
--- /dev/null
+++ b/packages/table-rate-shipping/resources/lang/en/relationmanagers.php
@@ -0,0 +1,72 @@
+<?php
+
+return [
+    'shipping_rates' => [
+        'title_plural' => 'Shipping Rates',
+        'actions' => [
+            'create' => [
+                'label' => 'Create Shipping Rate',
+            ],
+        ],
+        'notices' => [
+            'prices_incl_tax' => 'All prices include tax, which will be considered when calculating minimum spend.',
+            'prices_excl_tax' => 'All prices exclude tax, the minimum spend will be based on the cart sub total.',
+        ],
+        'form' => [
+            'shipping_method_id' => [
+                'label' => 'Shipping Method',
+            ],
+            'price' => [
+                'label' => 'Price',
+            ],
+            'prices' => [
+                'label' => 'Quantity Breaks',
+                'repeater' => [
+                    'customer_group_id' => [
+                        'label' => 'Customer Group',
+                        'placeholder' => 'Any',
+                    ],
+                    'currency_id' => [
+                        'label' => 'Currency',
+                    ],
+                    'quantity_break' => [
+                        'label' => 'Min. Spend',
+                    ],
+                    'price' => [
+                        'label' => 'Price',
+                    ],
+                ],
+            ],
+        ],
+        'table' => [
+            'shipping_method' => [
+                'label' => 'Shipping Method',
+            ],
+            'price' => [
+                'label' => 'Price',
+            ],
+            'quantity_breaks_count' => [
+                'label' => 'Quantity Breaks',
+            ],
+        ],
+    ],
+    'exclusions' => [
+        'title_plural' => 'Shipping Exclusions',
+        'form' => [
+            'purchasable' => [
+                'label' => 'Product',
+            ],
+        ],
+        'actions' => [
+            'create' => [
+                'label' => 'Add shipping exclusion list',
+            ],
+            'attach' => [
+                'label' => 'Add exclusion list',
+            ],
+            'detach' => [
+                'label' => 'Remove',
+            ],
+        ],
+    ],
+];
diff --git a/packages/table-rate-shipping/resources/lang/en/shippingexclusionlist.php b/packages/table-rate-shipping/resources/lang/en/shippingexclusionlist.php
new file mode 100644
index 0000000000..cddaf6bd95
--- /dev/null
+++ b/packages/table-rate-shipping/resources/lang/en/shippingexclusionlist.php
@@ -0,0 +1,19 @@
+<?php
+
+return [
+    'label' => 'Shipping Exclusion List',
+    'label_plural' => 'Shipping Exclusion Lists',
+    'form' => [
+        'name' => [
+            'label' => 'Name',
+        ],
+    ],
+    'table' => [
+        'name' => [
+            'label' => 'Name',
+        ],
+        'exclusions_count' => [
+            'label' => 'No. Products',
+        ],
+    ],
+];
diff --git a/packages/table-rate-shipping/resources/lang/en/shippingmethod.php b/packages/table-rate-shipping/resources/lang/en/shippingmethod.php
new file mode 100644
index 0000000000..ec2b560317
--- /dev/null
+++ b/packages/table-rate-shipping/resources/lang/en/shippingmethod.php
@@ -0,0 +1,52 @@
+<?php
+
+return [
+    'label_plural' => 'Shipping Methods',
+    'label' => 'Shipping Method',
+    'form' => [
+        'name' => [
+            'label' => 'Name',
+        ],
+        'description' => [
+            'label' => 'Description',
+        ],
+        'code' => [
+            'label' => 'Code',
+        ],
+        'cutoff' => [
+            'label' => 'Cutoff',
+        ],
+        'charge_by' => [
+            'label' => 'Charge By',
+            'options' => [
+                'cart_total' => 'Cart Total',
+                'weight' => 'Weight',
+            ],
+        ],
+        'driver' => [
+            'label' => 'Type',
+            'options' => [
+                'ship-by' => 'Standard',
+                'collection' => 'Collection',
+            ],
+        ],
+        'stock_available' => [
+            'label' => 'Stock of all basket items must be available',
+        ],
+    ],
+    'table' => [
+        'name' => [
+            'label' => 'Name',
+        ],
+        'code' => [
+            'label' => 'Code',
+        ],
+        'driver' => [
+            'label' => 'Type',
+            'options' => [
+                'ship-by' => 'Standard',
+                'collection' => 'Collection',
+            ],
+        ],
+    ],
+];
diff --git a/packages/table-rate-shipping/resources/lang/en/shippingzone.php b/packages/table-rate-shipping/resources/lang/en/shippingzone.php
new file mode 100644
index 0000000000..f291f27826
--- /dev/null
+++ b/packages/table-rate-shipping/resources/lang/en/shippingzone.php
@@ -0,0 +1,50 @@
+<?php
+
+return [
+    'label' => 'Shipping Zone',
+    'label_plural' => 'Shipping Zones',
+    'form' => [
+        'unrestricted' => [
+            'content' => 'This shipping zone has no restrictions in place and will be available to all customers at checkout.',
+        ],
+        'name' => [
+            'label' => 'Name',
+        ],
+        'type' => [
+            'label' => 'Type',
+            'options' => [
+                'unrestricted' => 'Unrestricted',
+                'countries' => 'Limit to Countries',
+                'states' => 'Limit to States / Provinces',
+                'postcodes' => 'Limit to Postcodes',
+            ],
+        ],
+        'country' => [
+            'label' => 'Country',
+        ],
+        'states' => [
+            'label' => 'States',
+        ],
+        'countries' => [
+            'label' => 'States',
+        ],
+        'postcodes' => [
+            'label' => 'Postcodes',
+            'helper' => 'List each postcode on a new line. Supports wildcards such as NW*',
+        ],
+    ],
+    'table' => [
+        'name' => [
+            'label' => 'Name',
+        ],
+        'type' => [
+            'label' => 'Type',
+            'options' => [
+                'unrestricted' => 'Unrestricted',
+                'countries' => 'Limit to Countries',
+                'states' => 'Limit to States / Provinces',
+                'postcodes' => 'Limit to Postcodes',
+            ],
+        ],
+    ],
+];
diff --git a/packages/table-rate-shipping/resources/views/partials/ship-by-total.blade.php b/packages/table-rate-shipping/resources/views/partials/ship-by-total.blade.php
index 989b2e1c32..ec590d0a45 100644
--- a/packages/table-rate-shipping/resources/views/partials/ship-by-total.blade.php
+++ b/packages/table-rate-shipping/resources/views/partials/ship-by-total.blade.php
@@ -18,7 +18,7 @@
       </x-hub::input.group>
 
       <div class="mt-4">
-        <strong>Tiers</strong><br>
+        <strong>Quantity Breaks</strong><br>
 
         <div class="space-y-4">
           <div class="grid items-center grid-cols-4 gap-2">
diff --git a/packages/table-rate-shipping/resources/views/shipping-methods/ship-by.blade.php b/packages/table-rate-shipping/resources/views/shipping-methods/ship-by.blade.php
index 3ca4d033cc..84d46ef218 100644
--- a/packages/table-rate-shipping/resources/views/shipping-methods/ship-by.blade.php
+++ b/packages/table-rate-shipping/resources/views/shipping-methods/ship-by.blade.php
@@ -50,7 +50,7 @@
     </div>
 
     <div class="space-y-4">
-      @if(count($tieredPrices))
+      @if(count($quantityBreaks))
         <div>
         @if(!$this->currency->default)
           <x-hub::alert>
@@ -74,11 +74,11 @@
           </div>
 
           <div class="space-y-2">
-            @foreach($tieredPrices as $index => $tier)
-              <div wire:key="tier_{{ $index }}">
+            @foreach($quantityBreaks as $index => $quantityBreak)
+              <div wire:key="qb_{{ $index }}">
                 <div class="flex items-center">
                   <div class="grid grid-cols-3 gap-4">
-                      <x-hub::input.select wire:model='tieredPrices.{{ $index }}.customer_group_id' :disabled="!$this->currency->default">
+                      <x-hub::input.select wire:model='quantityBreaks.{{ $index }}.customer_group_id' :disabled="!$this->currency->default">
                         <option value="*">{{ __('adminhub::global.any') }}</option>
                         @foreach($this->customerGroups as $group)
                           <option value="{{ $group->id }}">{{ $group->name }}</option>
@@ -86,24 +86,24 @@
                       </x-hub::input.select>
 
                       <x-hub::input.text
-                        id="tier_field_{{ $index }}"
-                        wire:model='tieredPrices.{{ $index }}.tier'
+                        id="quantity_break_field_{{ $index }}"
+                        wire:model='quantityBreaks.{{ $index }}.quantity_break'
                         :symbol="$this->currency->format"
                         :currencyCode="$this->currency->code"
                         type="number"
                         step="any"
                         required
                         :disabled="!$this->currency->default"
-                        :error="$errors->first('tieredPrices.'.$index.'.tier')"
+                        :error="$errors->first('quantityBreaks.'.$index.'.quantity_break')"
                       />
 
-                    <x-hub::input.price wire:model="tieredPrices.{{ $index }}.prices.{{ $currency->code }}.price" :symbol="$this->currency->format" :currencyCode="$this->currency->code" />
+                    <x-hub::input.price wire:model="quantityBreaks.{{ $index }}.prices.{{ $currency->code }}.price" :symbol="$this->currency->format" :currencyCode="$this->currency->code" />
                   </div>
                   <div class="ml-4">
                     <button class="text-gray-500 hover:text-red-500" wire:click.prevent="removeTier('{{ $index }}')"><x-hub::icon ref="trash" class="w-4" /></button>
                   </div>
                 </div>
-                @foreach($errors->get('tieredPrices.'.$index.'*') as $error)
+                @foreach($errors->get('quantityBreaks.'.$index.'*') as $error)
                   @foreach($error as $text)
                     <p class="mt-2 text-sm text-red-600">{{ $text }}</p>
                   @endforeach
diff --git a/packages/table-rate-shipping/src/DataTransferObjects/ShippingOptionLookup.php b/packages/table-rate-shipping/src/DataTransferObjects/ShippingOptionLookup.php
index 8b5805d35e..edaba3b848 100644
--- a/packages/table-rate-shipping/src/DataTransferObjects/ShippingOptionLookup.php
+++ b/packages/table-rate-shipping/src/DataTransferObjects/ShippingOptionLookup.php
@@ -4,23 +4,19 @@
 
 use Doctrine\Common\Cache\Psr6\InvalidArgument;
 use Illuminate\Support\Collection;
-use Lunar\Models\Country;
-use Lunar\Shipping\Models\ShippingMethod;
+use Lunar\Shipping\Models\ShippingRate;
 
 class ShippingOptionLookup
 {
     /**
-     * Initialise the postcode lookup class.
-     *
-     * @param Country Country description
-     * @param public string description
+     * Initialise the shipping option lookup class.
      */
     public function __construct(
-        public Collection $shippingMethods
+        public Collection $shippingRates
     ) {
         throw_if(
-            $shippingMethods->filter(
-                fn ($method) => get_class($method) != ShippingMethod::class
+            $this->shippingRates->filter(
+                fn ($method) => get_class($method) != ShippingRate::class
             )->count(),
             new InvalidArgument()
         );
diff --git a/packages/table-rate-shipping/src/DataTransferObjects/ShippingOptionRequest.php b/packages/table-rate-shipping/src/DataTransferObjects/ShippingOptionRequest.php
index 201af2fd29..eb61f39e4f 100644
--- a/packages/table-rate-shipping/src/DataTransferObjects/ShippingOptionRequest.php
+++ b/packages/table-rate-shipping/src/DataTransferObjects/ShippingOptionRequest.php
@@ -3,7 +3,7 @@
 namespace Lunar\Shipping\DataTransferObjects;
 
 use Lunar\Models\Cart;
-use Lunar\Shipping\Models\ShippingMethod;
+use Lunar\Shipping\Models\ShippingRate;
 
 class ShippingOptionRequest
 {
@@ -11,7 +11,7 @@ class ShippingOptionRequest
      * Initialise the shipping option request class.
      */
     public function __construct(
-        public ShippingMethod $shippingMethod,
+        public ShippingRate $shippingRate,
         public Cart $cart
     ) {
         //
diff --git a/packages/table-rate-shipping/src/Drivers/ShippingMethods/Collection.php b/packages/table-rate-shipping/src/Drivers/ShippingMethods/Collection.php
index 0ff5149233..95989904fb 100644
--- a/packages/table-rate-shipping/src/Drivers/ShippingMethods/Collection.php
+++ b/packages/table-rate-shipping/src/Drivers/ShippingMethods/Collection.php
@@ -6,15 +6,15 @@
 use Lunar\DataTypes\ShippingOption;
 use Lunar\Models\Product;
 use Lunar\Shipping\DataTransferObjects\ShippingOptionRequest;
-use Lunar\Shipping\Interfaces\ShippingMethodInterface;
-use Lunar\Shipping\Models\ShippingMethod;
+use Lunar\Shipping\Interfaces\ShippingRateInterface;
+use Lunar\Shipping\Models\ShippingRate;
 
-class Collection implements ShippingMethodInterface
+class Collection implements ShippingRateInterface
 {
     /**
-     * The shipping method for context.
+     * The shipping rate for context.
      */
-    public ShippingMethod $shippingMethod;
+    public ShippingRate $shippingRate;
 
     /**
      * {@inheritdoc}
@@ -34,14 +34,16 @@ public function description(): string
 
     public function resolve(ShippingOptionRequest $shippingOptionRequest): ?ShippingOption
     {
-        $shippingMethod = $shippingOptionRequest->shippingMethod;
+        $shippingRate = $shippingOptionRequest->shippingRate;
+        $shippingMethod = $shippingRate->shippingMethod;
+        $shippingZone = $shippingRate->shippingZone;
         $cart = $shippingOptionRequest->cart;
 
         // Do we have any products in our exclusions list?
         // If so, we do not want to return this option regardless.
         $productIds = $cart->lines->load('purchasable')->pluck('purchasable.product_id');
 
-        $hasExclusions = $shippingMethod->shippingExclusions()
+        $hasExclusions = $shippingZone->shippingExclusions()
             ->whereHas('exclusions', function ($query) use ($productIds) {
                 $query->wherePurchasableType(Product::class)->whereIn('purchasable_id', $productIds);
             })->exists();
@@ -53,26 +55,26 @@ public function resolve(ShippingOptionRequest $shippingOptionRequest): ?Shipping
         return new ShippingOption(
             name: $shippingMethod->name,
             description: $shippingMethod->description,
-            identifier: $shippingMethod->getIdentifier(),
+            identifier: $shippingRate->getIdentifier(),
             price: new Price(
                 value: 0,
                 currency: $cart->currency,
                 unitQty: 1
             ),
-            taxClass: $shippingMethod->getTaxClass(),
-            taxReference: $shippingMethod->getTaxReference(),
-            option: $shippingMethod->shippingZone->name,
+            taxClass: $shippingRate->getTaxClass(),
+            taxReference: $shippingRate->getTaxReference(),
+            option: $shippingZone->name,
             collect: true,
-            meta: ['shipping_zone' => $shippingMethod->shippingZone->name]
+            meta: ['shipping_zone' => $shippingZone->name]
         );
     }
 
     /**
      * {@inheritDoc}
      */
-    public function on(ShippingMethod $shippingMethod): self
+    public function on(ShippingRate $shippingRate): self
     {
-        $this->shippingMethod = $shippingMethod;
+        $this->shippingRate = $shippingRate;
 
         return $this;
     }
diff --git a/packages/table-rate-shipping/src/Drivers/ShippingMethods/FlatRate.php b/packages/table-rate-shipping/src/Drivers/ShippingMethods/FlatRate.php
index 6b4836023f..711cd6cdcc 100644
--- a/packages/table-rate-shipping/src/Drivers/ShippingMethods/FlatRate.php
+++ b/packages/table-rate-shipping/src/Drivers/ShippingMethods/FlatRate.php
@@ -6,15 +6,15 @@
 use Lunar\Facades\Pricing;
 use Lunar\Models\Product;
 use Lunar\Shipping\DataTransferObjects\ShippingOptionRequest;
-use Lunar\Shipping\Interfaces\ShippingMethodInterface;
-use Lunar\Shipping\Models\ShippingMethod;
+use Lunar\Shipping\Interfaces\ShippingRateInterface;
+use Lunar\Shipping\Models\ShippingRate;
 
-class FlatRate implements ShippingMethodInterface
+class FlatRate implements ShippingRateInterface
 {
     /**
      * The shipping method for context.
      */
-    public ShippingMethod $shippingMethod;
+    public ShippingRate $shippingRate;
 
     /**
      * {@inheritdoc}
@@ -34,15 +34,16 @@ public function description(): string
 
     public function resolve(ShippingOptionRequest $shippingOptionRequest): ?ShippingOption
     {
-        $data = $shippingOptionRequest->shippingMethod->data;
+        $shippingRate = $shippingOptionRequest->shippingRate;
+        $shippingMethod = $shippingRate->shippingMethod;
+        $shippingZone = $shippingRate->shippingZone;
         $cart = $shippingOptionRequest->cart;
-        $shippingMethod = $shippingOptionRequest->shippingMethod;
 
         // Do we have any products in our exclusions list?
         // If so, we do not want to return this option regardless.
         $productIds = $cart->lines->load('purchasable')->pluck('purchasable.product_id');
 
-        $hasExclusions = $shippingMethod->shippingExclusions()
+        $hasExclusions = $shippingZone->shippingExclusions()
             ->whereHas('exclusions', function ($query) use ($productIds) {
                 $query->wherePurchasableType(Product::class)->whereIn('purchasable_id', $productIds);
             })->exists();
@@ -53,7 +54,7 @@ public function resolve(ShippingOptionRequest $shippingOptionRequest): ?Shipping
 
         $subTotal = $cart->lines->sum('subTotal.value');
 
-        $pricing = Pricing::for($shippingMethod)->qty($subTotal)->get();
+        $pricing = Pricing::for($shippingRate)->qty($subTotal)->get();
 
         if (! $pricing->matched) {
             return null;
@@ -62,21 +63,21 @@ public function resolve(ShippingOptionRequest $shippingOptionRequest): ?Shipping
         return new ShippingOption(
             name: $shippingMethod->name ?: $this->name(),
             description: $shippingMethod->description ?: $this->description(),
-            identifier: $shippingMethod->getIdentifier(),
+            identifier: $shippingRate->getIdentifier(),
             price: $pricing->matched->price,
-            taxClass: $shippingMethod->getTaxClass(),
-            taxReference: $shippingMethod->getTaxReference(),
-            option: $shippingMethod->shippingZone->name,
-            meta: ['shipping_zone' => $shippingMethod->shippingZone->name]
+            taxClass: $shippingRate->getTaxClass(),
+            taxReference: $shippingRate->getTaxReference(),
+            option: $shippingZone->name,
+            meta: ['shipping_zone' => $shippingZone->name]
         );
     }
 
     /**
      * {@inheritDoc}
      */
-    public function on(ShippingMethod $shippingMethod): self
+    public function on(ShippingRate $shippingRate): self
     {
-        $this->shippingMethod = $shippingMethod;
+        $this->shippingRate = $shippingRate;
 
         return $this;
     }
diff --git a/packages/table-rate-shipping/src/Drivers/ShippingMethods/FreeShipping.php b/packages/table-rate-shipping/src/Drivers/ShippingMethods/FreeShipping.php
index 355447492f..1b8a8be6db 100644
--- a/packages/table-rate-shipping/src/Drivers/ShippingMethods/FreeShipping.php
+++ b/packages/table-rate-shipping/src/Drivers/ShippingMethods/FreeShipping.php
@@ -5,17 +5,16 @@
 use Lunar\DataTypes\Price;
 use Lunar\DataTypes\ShippingOption;
 use Lunar\Models\Product;
-use Lunar\Models\TaxClass;
 use Lunar\Shipping\DataTransferObjects\ShippingOptionRequest;
-use Lunar\Shipping\Interfaces\ShippingMethodInterface;
-use Lunar\Shipping\Models\ShippingMethod;
+use Lunar\Shipping\Interfaces\ShippingRateInterface;
+use Lunar\Shipping\Models\ShippingRate;
 
-class FreeShipping implements ShippingMethodInterface
+class FreeShipping implements ShippingRateInterface
 {
     /**
      * The shipping method for context.
      */
-    public ShippingMethod $shippingMethod;
+    public ShippingRate $shippingRate;
 
     /**
      * {@inheritDoc}
@@ -35,7 +34,9 @@ public function description(): string
 
     public function resolve(ShippingOptionRequest $shippingOptionRequest): ?ShippingOption
     {
-        $shippingMethod = $shippingOptionRequest->shippingMethod;
+        $shippingRate = $shippingOptionRequest->shippingRate;
+        $shippingMethod = $shippingRate->shippingMethod;
+        $shippingZone = $shippingRate->shippingZone;
         $data = $shippingMethod->data;
         $cart = $shippingOptionRequest->cart;
 
@@ -43,7 +44,7 @@ public function resolve(ShippingOptionRequest $shippingOptionRequest): ?Shipping
         // If so, we do not want to return this option regardless.
         $productIds = $cart->lines->load('purchasable')->pluck('purchasable.product_id');
 
-        $hasExclusions = $shippingMethod->shippingExclusions()
+        $hasExclusions = $shippingZone->shippingExclusions()
             ->whereHas('exclusions', function ($query) use ($productIds) {
                 $query->wherePurchasableType(Product::class)->whereIn('purchasable_id', $productIds);
             })->exists();
@@ -54,41 +55,37 @@ public function resolve(ShippingOptionRequest $shippingOptionRequest): ?Shipping
 
         $subTotal = $cart->lines->sum('subTotal.value');
 
-        if ($data->use_discount_amount ?? false) {
+        if ($data['use_discount_amount'] ?? false) {
             $subTotal -= $cart->discountTotal->value;
         }
 
-        if (empty($data)) {
-            $minSpend = 0;
-        } else {
-            if (is_array($data->minimum_spend)) {
-                $minSpend = ($data->minimum_spend[$cart->currency->code] ?? null);
-            } else {
-                $minSpend = ($data->minimum_spend->{$cart->currency->code} ?? null);
-            }
+        $minSpend = $data['minimum_spend'] ?? null;
+
+        if (is_array($minSpend)) {
+            $minSpend = $minSpend[$cart->currency->code] ?? null;
         }
 
-        if (is_null($minSpend) || ($minSpend) > $subTotal) {
+        if (is_null($minSpend) || $minSpend > $subTotal) {
             return null;
         }
 
         return new ShippingOption(
             name: $shippingMethod->name,
             description: $shippingMethod->description,
-            identifier: $shippingMethod->code,
+            identifier: $shippingRate->getIdentifier(),
             price: new Price(0, $cart->currency, 1),
-            taxClass: TaxClass::getDefault(),
-            option: $shippingMethod->shippingZone->name,
-            meta: ['shipping_zone' => $shippingMethod->shippingZone->name]
+            taxClass: $shippingRate->getTaxClass(),
+            option: $shippingZone->name,
+            meta: ['shipping_zone' => $shippingZone->name]
         );
     }
 
     /**
      * {@inheritDoc}
      */
-    public function on(ShippingMethod $shippingMethod): self
+    public function on(ShippingRate $shippingRate): self
     {
-        $this->shippingMethod = $shippingMethod;
+        $this->shippingRate = $shippingRate;
 
         return $this;
     }
diff --git a/packages/table-rate-shipping/src/Drivers/ShippingMethods/ShipBy.php b/packages/table-rate-shipping/src/Drivers/ShippingMethods/ShipBy.php
index 266480cc64..db076d9af8 100644
--- a/packages/table-rate-shipping/src/Drivers/ShippingMethods/ShipBy.php
+++ b/packages/table-rate-shipping/src/Drivers/ShippingMethods/ShipBy.php
@@ -6,15 +6,15 @@
 use Lunar\Facades\Pricing;
 use Lunar\Models\Product;
 use Lunar\Shipping\DataTransferObjects\ShippingOptionRequest;
-use Lunar\Shipping\Interfaces\ShippingMethodInterface;
-use Lunar\Shipping\Models\ShippingMethod;
+use Lunar\Shipping\Interfaces\ShippingRateInterface;
+use Lunar\Shipping\Models\ShippingRate;
 
-class ShipBy implements ShippingMethodInterface
+class ShipBy implements ShippingRateInterface
 {
     /**
-     * The shipping method for context.
+     * The shipping rate for context.
      */
-    public ShippingMethod $shippingMethod;
+    public ShippingRate $shippingRate;
 
     /**
      * {@inheritdoc}
@@ -34,9 +34,11 @@ public function description(): string
 
     public function resolve(ShippingOptionRequest $shippingOptionRequest): ?ShippingOption
     {
-        $data = $shippingOptionRequest->shippingMethod->data;
+        $shippingRate = $shippingOptionRequest->shippingRate;
+        $shippingMethod = $shippingRate->shippingMethod;
+        $shippingZone = $shippingRate->shippingZone;
+        $data = $shippingMethod->data;
         $cart = $shippingOptionRequest->cart;
-        $shippingMethod = $shippingOptionRequest->shippingMethod;
         $customerGroups = collect([]);
 
         if ($user = $cart->user) {
@@ -49,7 +51,7 @@ public function resolve(ShippingOptionRequest $shippingOptionRequest): ?Shipping
         // If so, we do not want to return this option regardless.
         $productIds = $cart->lines->load('purchasable')->pluck('purchasable.product_id');
 
-        $hasExclusions = $shippingMethod->shippingExclusions()
+        $hasExclusions = $shippingZone->shippingExclusions()
             ->whereHas('exclusions', function ($query) use ($productIds) {
                 $query->wherePurchasableType(Product::class)->whereIn('purchasable_id', $productIds);
             })->exists();
@@ -58,7 +60,7 @@ public function resolve(ShippingOptionRequest $shippingOptionRequest): ?Shipping
             return null;
         }
 
-        $chargeBy = $data->charge_by ?? null;
+        $chargeBy = $data['charge_by'] ?? null;
 
         if (! $chargeBy) {
             $chargeBy = 'cart_total';
@@ -73,9 +75,9 @@ public function resolve(ShippingOptionRequest $shippingOptionRequest): ?Shipping
         }
 
         // Do we have a suitable tier price?
-        $pricing = Pricing::for($shippingMethod)->customerGroups($customerGroups)->qty($tier)->get();
+        $pricing = Pricing::for($shippingRate)->customerGroups($customerGroups)->qty($tier)->get();
 
-        $prices = $pricing->tiered;
+        $prices = $pricing->quantityBreaks;
 
         // If there are customer group prices, they need to take priority.
         if (! $pricing->customerGroupPrices->isEmpty()) {
@@ -83,8 +85,8 @@ public function resolve(ShippingOptionRequest $shippingOptionRequest): ?Shipping
         }
 
         $matched = $prices->filter(function ($price) use ($tier) {
-            return $tier >= $price->tier;
-        })->sortByDesc('tier')->first() ?: $pricing->base;
+            return $tier >= $price->quantity_break;
+        })->sortByDesc('quantity_break')->first() ?: $pricing->base;
 
         if (! $matched) {
             return null;
@@ -95,21 +97,21 @@ public function resolve(ShippingOptionRequest $shippingOptionRequest): ?Shipping
         return new ShippingOption(
             name: $shippingMethod->name,
             description: $shippingMethod->description,
-            identifier: $shippingMethod->getIdentifier(),
+            identifier: $shippingRate->getIdentifier(),
             price: $price,
-            taxClass: $shippingMethod->getTaxClass(),
-            taxReference: $shippingMethod->getTaxReference(),
-            option: $shippingMethod->shippingZone->name,
-            meta: ['shipping_zone' => $shippingMethod->shippingZone->name]
+            taxClass: $shippingRate->getTaxClass(),
+            taxReference: $shippingRate->getTaxReference(),
+            option: $shippingZone->name,
+            meta: ['shipping_zone' => $shippingZone->name]
         );
     }
 
     /**
      * {@inheritDoc}
      */
-    public function on(ShippingMethod $shippingMethod): self
+    public function on(ShippingRate $shippingRate): self
     {
-        $this->shippingMethod = $shippingMethod;
+        $this->shippingRate = $shippingRate;
 
         return $this;
     }
diff --git a/packages/table-rate-shipping/src/Events/ShippingOptionResolvedEvent.php b/packages/table-rate-shipping/src/Events/ShippingOptionResolvedEvent.php
index 0abbb07107..962e337f47 100644
--- a/packages/table-rate-shipping/src/Events/ShippingOptionResolvedEvent.php
+++ b/packages/table-rate-shipping/src/Events/ShippingOptionResolvedEvent.php
@@ -7,7 +7,7 @@
 use Illuminate\Queue\SerializesModels;
 use Lunar\DataTypes\ShippingOption;
 use Lunar\Models\Cart;
-use Lunar\Shipping\Models\ShippingMethod;
+use Lunar\Shipping\Models\ShippingRate;
 
 class ShippingOptionResolvedEvent
 {
@@ -21,17 +21,17 @@ class ShippingOptionResolvedEvent
     /**
      * The instance of the shipping method.
      */
-    public ShippingMethod $shippingMethod;
+    public ShippingRate $shippingRate;
 
     /**
      * The instance of the cart.
      */
     public Cart $cart;
 
-    public function __construct(Cart $cart, ShippingMethod $shippingMethod, ShippingOption $shippingOption)
+    public function __construct(Cart $cart, ShippingRate $shippingRate, ShippingOption $shippingOption)
     {
         $this->cart = $cart;
-        $this->shippingMethod = $shippingMethod;
+        $this->shippingRate = $shippingRate;
         $this->shippingOption = $shippingOption;
     }
 }
diff --git a/packages/table-rate-shipping/src/Filament/Resources/ShippingExclusionListResource.php b/packages/table-rate-shipping/src/Filament/Resources/ShippingExclusionListResource.php
new file mode 100644
index 0000000000..283395f6f2
--- /dev/null
+++ b/packages/table-rate-shipping/src/Filament/Resources/ShippingExclusionListResource.php
@@ -0,0 +1,116 @@
+<?php
+
+namespace Lunar\Shipping\Filament\Resources;
+
+use Filament\Forms;
+use Filament\Forms\Components\Component;
+use Filament\Forms\Form;
+use Filament\Pages\SubNavigationPosition;
+use Filament\Support\Facades\FilamentIcon;
+use Filament\Tables;
+use Filament\Tables\Table;
+use Lunar\Admin\Support\Resources\BaseResource;
+use Lunar\Shipping\Filament\Resources\ShippingExclusionListResource\Pages;
+use Lunar\Shipping\Filament\Resources\ShippingExclusionListResource\RelationManagers\ShippingExclusionRelationManager;
+use Lunar\Shipping\Models\ShippingExclusionList;
+
+class ShippingExclusionListResource extends BaseResource
+{
+    protected static ?string $model = ShippingExclusionList::class;
+
+    protected static ?int $navigationSort = 1;
+
+    protected static SubNavigationPosition $subNavigationPosition = SubNavigationPosition::End;
+
+    public static function getLabel(): string
+    {
+        return __('lunarpanel.shipping::shippingexclusionlist.label');
+    }
+
+    public static function getPluralLabel(): string
+    {
+        return __('lunarpanel.shipping::shippingexclusionlist.label_plural');
+    }
+
+    public static function getNavigationIcon(): ?string
+    {
+        return FilamentIcon::resolve('lunar::shipping-exclusion-lists');
+    }
+
+    public static function getNavigationGroup(): ?string
+    {
+        return __('lunarpanel.shipping::plugin.navigation.group');
+    }
+
+    public static function getDefaultForm(Form $form): Form
+    {
+        return $form->schema([
+            Forms\Components\Section::make()->schema(
+                static::getMainFormComponents(),
+            ),
+        ]);
+    }
+
+    protected static function getDefaultRelations(): array
+    {
+        return [
+            ShippingExclusionRelationManager::class,
+        ];
+    }
+
+    protected static function getMainFormComponents(): array
+    {
+        return [
+            static::getNameFormComponent(),
+        ];
+    }
+
+    public static function getNameFormComponent(): Component
+    {
+        return Forms\Components\TextInput::make('name')
+            ->label(__('lunarpanel.shipping::shippingexclusionlist.form.name.label'))
+            ->required()
+            ->maxLength(255)
+            ->autofocus();
+    }
+
+    public static function getDefaultTable(Table $table): Table
+    {
+        return $table
+            ->columns(static::getTableColumns())
+            ->filters([
+                //
+            ])
+            ->actions([
+                Tables\Actions\EditAction::make(),
+            ])
+            ->bulkActions([
+                Tables\Actions\BulkActionGroup::make([
+                    Tables\Actions\DeleteBulkAction::make(),
+                ]),
+            ]);
+    }
+
+    public static function getTableColumns(): array
+    {
+        return [
+            Tables\Columns\TextColumn::make('name')
+                ->label(
+                    __('lunarpanel.shipping::shippingexclusionlist.table.name.label')
+                ),
+            Tables\Columns\TextColumn::make('exclusions_count')
+                ->label(
+                    __('lunarpanel.shipping::shippingexclusionlist.table.exclusions_count.label')
+                )
+                ->counts('exclusions'),
+        ];
+    }
+
+    public static function getPages(): array
+    {
+        return [
+            'index' => Pages\ListShippingExclusionLists::route('/'),
+            'edit' => Pages\EditShippingExclusionList::route('/{record}/edit'),
+        ];
+    }
+}
diff --git a/packages/table-rate-shipping/src/Filament/Resources/ShippingExclusionListResource/Pages/EditShippingExclusionList.php b/packages/table-rate-shipping/src/Filament/Resources/ShippingExclusionListResource/Pages/EditShippingExclusionList.php
new file mode 100644
index 0000000000..321757e87a
--- /dev/null
+++ b/packages/table-rate-shipping/src/Filament/Resources/ShippingExclusionListResource/Pages/EditShippingExclusionList.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Lunar\Shipping\Filament\Resources\ShippingExclusionListResource\Pages;
+
+use Filament\Actions;
+use Lunar\Admin\Support\Pages\BaseEditRecord;
+use Lunar\Shipping\Filament\Resources\ShippingExclusionListResource;
+
+class EditShippingExclusionList extends BaseEditRecord
+{
+    protected static string $resource = ShippingExclusionListResource::class;
+
+    protected function getDefaultHeaderActions(): array
+    {
+        return [
+            Actions\DeleteAction::make(),
+        ];
+    }
+
+    protected function getRedirectUrl(): string
+    {
+        return $this->getResource()::getUrl('index');
+    }
+}
diff --git a/packages/table-rate-shipping/src/Filament/Resources/ShippingExclusionListResource/Pages/ListShippingExclusionLists.php b/packages/table-rate-shipping/src/Filament/Resources/ShippingExclusionListResource/Pages/ListShippingExclusionLists.php
new file mode 100644
index 0000000000..0508c247cb
--- /dev/null
+++ b/packages/table-rate-shipping/src/Filament/Resources/ShippingExclusionListResource/Pages/ListShippingExclusionLists.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Lunar\Shipping\Filament\Resources\ShippingExclusionListResource\Pages;
+
+use Filament\Actions;
+use Lunar\Admin\Support\Pages\BaseListRecords;
+use Lunar\Shipping\Filament\Resources\ShippingExclusionListResource;
+
+class ListShippingExclusionLists extends BaseListRecords
+{
+    protected static string $resource = ShippingExclusionListResource::class;
+
+    protected function getDefaultHeaderActions(): array
+    {
+        return [
+            Actions\CreateAction::make()->form([
+                ShippingExclusionListResource::getNameFormComponent(),
+            ]),
+        ];
+    }
+}
diff --git a/packages/table-rate-shipping/src/Filament/Resources/ShippingExclusionListResource/RelationManagers/ShippingExclusionRelationManager.php b/packages/table-rate-shipping/src/Filament/Resources/ShippingExclusionListResource/RelationManagers/ShippingExclusionRelationManager.php
new file mode 100644
index 0000000000..dc305c41fb
--- /dev/null
+++ b/packages/table-rate-shipping/src/Filament/Resources/ShippingExclusionListResource/RelationManagers/ShippingExclusionRelationManager.php
@@ -0,0 +1,93 @@
+<?php
+
+namespace Lunar\Shipping\Filament\Resources\ShippingExclusionListResource\RelationManagers;
+
+use Filament\Forms;
+use Filament\Forms\Form;
+use Filament\Resources\RelationManagers\RelationManager;
+use Filament\Tables;
+use Filament\Tables\Table;
+use Illuminate\Database\Eloquent\Model;
+use Lunar\Models\Product;
+
+class ShippingExclusionRelationManager extends RelationManager
+{
+    protected static string $relationship = 'exclusions';
+
+    public static function getTitle(Model $ownerRecord, string $pageClass): string
+    {
+        return __('lunarpanel.shipping::relationmanagers.exclusions.title_plural');
+    }
+
+    protected static ?string $recordTitleAttribute = 'name';
+
+    public function form(Form $form): Form
+    {
+        return $form
+            ->schema([
+                Forms\Components\MorphToSelect::make('purchasable')
+                    ->types([
+                        Forms\Components\MorphToSelect\Type::make(Product::class)
+                            ->titleAttribute('name.en')
+                            ->getSearchResultsUsing(static function (Forms\Components\Select $component, string $search): array {
+                                return Product::search($search)
+                                    ->get()
+                                    ->mapWithKeys(fn (Product $record): array => [$record->getKey() => $record->translateAttribute('name')])
+                                    ->all();
+                            }),
+                    ])
+                    ->label(
+                        __('lunarpanel.shipping::relationmanagers.exclusions.form.purchasable.label')
+                    )
+                    ->required()
+                    ->searchable(true),
+            ]);
+    }
+
+    public function table(Table $table): Table
+    {
+        return $table
+            ->columns([
+                Tables\Columns\SpatieMediaLibraryImageColumn::make('purchasable.thumbnail')
+                    ->collection('images')
+                    ->conversion('small')
+                    ->limit(1)
+                    ->square()
+                    ->label(''),
+                Tables\Columns\TextColumn::make('purchasable.attribute_data.name')
+                    ->formatStateUsing(fn (Model $record): string => $record->purchasable->translateAttribute('name'))
+                    ->limit(50)
+                    ->tooltip(function (Tables\Columns\TextColumn $column, Model $record): ?string {
+                        $state = $column->getState();
+                        $record = $record->purchasable;
+
+                        if (strlen($record->translateAttribute('name')) <= $column->getCharacterLimit()) {
+                            return null;
+                        }
+
+                        return $record->translateAttribute('name');
+                    })
+                    ->label(__('lunarpanel::product.table.name.label')),
+                Tables\Columns\TextColumn::make('purchasable.variants.sku')
+                    ->label(__('lunarpanel::product.table.sku.label'))
+                    ->toggleable(),
+            ])
+            ->filters([
+                //
+            ])
+            ->headerActions([
+                Tables\Actions\CreateAction::make()->mutateFormDataUsing(function (array $data, RelationManager $livewire) {
+                    return $data;
+                }),
+            ])
+            ->actions([
+                Tables\Actions\EditAction::make(),
+                Tables\Actions\DeleteAction::make(),
+            ])
+            ->bulkActions([
+                Tables\Actions\BulkActionGroup::make([
+                    Tables\Actions\DeleteBulkAction::make(),
+                ]),
+            ]);
+    }
+}
diff --git a/packages/table-rate-shipping/src/Filament/Resources/ShippingMethodResource.php b/packages/table-rate-shipping/src/Filament/Resources/ShippingMethodResource.php
new file mode 100644
index 0000000000..b7c34f678e
--- /dev/null
+++ b/packages/table-rate-shipping/src/Filament/Resources/ShippingMethodResource.php
@@ -0,0 +1,180 @@
+<?php
+
+namespace Lunar\Shipping\Filament\Resources;
+
+use Filament\Forms;
+use Filament\Forms\Components\Component;
+use Filament\Forms\Form;
+use Filament\Support\Facades\FilamentIcon;
+use Filament\Tables;
+use Filament\Tables\Table;
+use Lunar\Admin\Support\Resources\BaseResource;
+use Lunar\Shipping\Filament\Resources\ShippingMethodResource\Pages;
+use Lunar\Shipping\Models\ShippingMethod;
+
+class ShippingMethodResource extends BaseResource
+{
+    protected static ?string $model = ShippingMethod::class;
+
+    protected static ?int $navigationSort = 1;
+
+    public static function getLabel(): string
+    {
+        return __('lunarpanel.shipping::shippingmethod.label');
+    }
+
+    public static function getPluralLabel(): string
+    {
+        return __('lunarpanel.shipping::shippingmethod.label_plural');
+    }
+
+    public static function getNavigationIcon(): ?string
+    {
+        return FilamentIcon::resolve('lunar::shipping-methods');
+    }
+
+    public static function getNavigationGroup(): ?string
+    {
+        return __('lunarpanel.shipping::plugin.navigation.group');
+    }
+
+    public static function getDefaultForm(Form $form): Form
+    {
+        return $form->schema([
+            Forms\Components\Section::make()->schema(
+                static::getMainFormComponents(),
+            ),
+        ]);
+    }
+
+    protected static function getMainFormComponents(): array
+    {
+        return [
+            static::getNameFormComponent(),
+            Forms\Components\Group::make([
+                static::getCodeFormComponent(),
+                static::getDriverFormComponent(),
+            ])->columns(2),
+            Forms\Components\Group::make([
+                static::getCutoffFormComponent(),
+                static::getChargeByFormComponent(),
+            ])->columns(2),
+            static::getStockAvailableFormComponent(),
+            static::getDescriptionFormComponent(),
+
+        ];
+    }
+
+    public static function getNameFormComponent(): Component
+    {
+        return Forms\Components\TextInput::make('name')
+            ->label(__('lunarpanel.shipping::shippingmethod.form.name.label'))
+            ->required()
+            ->maxLength(255)
+            ->autofocus();
+    }
+
+    public static function getDescriptionFormComponent(): Component
+    {
+        return Forms\Components\RichEditor::make('description')
+            ->label(__('lunarpanel.shipping::shippingmethod.form.description.label'));
+    }
+
+    public static function getCodeFormComponent(): Component
+    {
+        return Forms\Components\TextInput::make('code')
+            ->label(__('lunarpanel.shipping::shippingmethod.form.code.label'))
+            ->required()
+            ->unique(ignoreRecord: true);
+    }
+
+    public static function getCutoffFormComponent(): Component
+    {
+        return Forms\Components\TimePicker::make('cutoff')
+            ->label(__('lunarpanel.shipping::shippingmethod.form.cutoff.label'));
+    }
+
+    public static function getStockAvailableFormComponent(): Component
+    {
+        return Forms\Components\Toggle::make('stock_available')
+            ->label(__('lunarpanel.shipping::shippingmethod.form.stock_available.label'));
+    }
+
+    public static function getChargeByFormComponent(): Component
+    {
+        return Forms\Components\Group::make([
+            Forms\Components\Select::make('charge_by')
+                ->label(
+                    __('lunarpanel.shipping::shippingmethod.form.charge_by.label')
+                )
+                ->options([
+                    'cart_total' => __('lunarpanel.shipping::shippingmethod.form.charge_by.options.cart_total'),
+                    'weight' => __('lunarpanel.shipping::shippingmethod.form.charge_by.options.weight'),
+                ]),
+
+        ])->columns(1)->statePath('data');
+    }
+
+    public static function getDriverFormComponent(): Component
+    {
+        return Forms\Components\Select::make('driver')
+            ->label(__('lunarpanel.shipping::shippingmethod.form.driver.label'))
+            ->options([
+                'ship-by' => __('lunarpanel.shipping::shippingmethod.form.driver.options.ship-by'),
+                'collection' => __('lunarpanel.shipping::shippingmethod.form.driver.options.collection'),
+            ])->label('Type')
+            ->default('ship-by');
+    }
+
+    public static function getDefaultTable(Table $table): Table
+    {
+        return $table
+            ->columns(static::getTableColumns())
+            ->filters([
+                //
+            ])
+            ->actions([
+                Tables\Actions\EditAction::make(),
+            ])
+            ->bulkActions([
+                Tables\Actions\BulkActionGroup::make([
+                    Tables\Actions\DeleteBulkAction::make(),
+                ]),
+            ]);
+    }
+
+    protected static function getTableColumns(): array
+    {
+        return [
+            Tables\Columns\TextColumn::make('name')
+                ->label(
+                    __('lunarpanel.shipping::shippingmethod.table.name.label')
+                ),
+            Tables\Columns\TextColumn::make('code')
+                ->label(
+                    __('lunarpanel.shipping::shippingmethod.table.code.label')
+                ),
+            Tables\Columns\TextColumn::make('driver')
+                ->label(
+                    __('lunarpanel.shipping::shippingmethod.table.driver.label')
+                )->formatStateUsing(
+                    fn ($state) => __("lunarpanel.shipping::shippingmethod.table.driver.options.{$state}")
+                ),
+        ];
+    }
+
+    public static function getRelations(): array
+    {
+        return [
+            //
+        ];
+    }
+
+    public static function getPages(): array
+    {
+        return [
+            'index' => Pages\ListShippingMethod::route('/'),
+            'edit' => Pages\EditShippingMethod::route('/{record}/edit'),
+        ];
+    }
+}
diff --git a/packages/table-rate-shipping/src/Filament/Resources/ShippingMethodResource/Pages/EditShippingMethod.php b/packages/table-rate-shipping/src/Filament/Resources/ShippingMethodResource/Pages/EditShippingMethod.php
new file mode 100644
index 0000000000..7279be485a
--- /dev/null
+++ b/packages/table-rate-shipping/src/Filament/Resources/ShippingMethodResource/Pages/EditShippingMethod.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Lunar\Shipping\Filament\Resources\ShippingMethodResource\Pages;
+
+use Filament\Actions;
+use Lunar\Admin\Support\Pages\BaseEditRecord;
+use Lunar\Shipping\Filament\Resources\ShippingMethodResource;
+
+class EditShippingMethod extends BaseEditRecord
+{
+    protected static string $resource = ShippingMethodResource::class;
+
+    protected function getDefaultHeaderActions(): array
+    {
+        return [
+            Actions\DeleteAction::make(),
+        ];
+    }
+
+    protected function getRedirectUrl(): string
+    {
+        return $this->getResource()::getUrl('index');
+    }
+}
diff --git a/packages/table-rate-shipping/src/Filament/Resources/ShippingMethodResource/Pages/ListShippingMethod.php b/packages/table-rate-shipping/src/Filament/Resources/ShippingMethodResource/Pages/ListShippingMethod.php
new file mode 100644
index 0000000000..3808546f1e
--- /dev/null
+++ b/packages/table-rate-shipping/src/Filament/Resources/ShippingMethodResource/Pages/ListShippingMethod.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace Lunar\Shipping\Filament\Resources\ShippingMethodResource\Pages;
+
+use Filament\Actions;
+use Filament\Forms\Components\Group;
+use Lunar\Admin\Support\Pages\BaseListRecords;
+use Lunar\Shipping\Filament\Resources\ShippingMethodResource;
+
+class ListShippingMethod extends BaseListRecords
+{
+    protected static string $resource = ShippingMethodResource::class;
+
+    protected function getDefaultHeaderActions(): array
+    {
+        return [
+            Actions\CreateAction::make()->form([
+                ShippingMethodResource::getNameFormComponent(),
+                Group::make([
+                    ShippingMethodResource::getCodeFormComponent(),
+                    ShippingMethodResource::getDriverFormComponent(),
+                ])->columns(2),
+                ShippingMethodResource::getDescriptionFormComponent(),
+            ]),
+        ];
+    }
+}
diff --git a/packages/table-rate-shipping/src/Filament/Resources/ShippingZoneResource.php b/packages/table-rate-shipping/src/Filament/Resources/ShippingZoneResource.php
new file mode 100644
index 0000000000..877e4ff38f
--- /dev/null
+++ b/packages/table-rate-shipping/src/Filament/Resources/ShippingZoneResource.php
@@ -0,0 +1,303 @@
+<?php
+
+namespace Lunar\Shipping\Filament\Resources;
+
+use Awcodes\Shout\Components\Shout;
+use Filament\Forms;
+use Filament\Forms\Components\Component;
+use Filament\Forms\Form;
+use Filament\Pages\Page;
+use Filament\Pages\SubNavigationPosition;
+use Filament\Support\Facades\FilamentIcon;
+use Filament\Tables;
+use Filament\Tables\Table;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Support\Collection;
+use Lunar\Admin\Support\Resources\BaseResource;
+use Lunar\Models\Country;
+use Lunar\Models\State;
+use Lunar\Shipping\Filament\Resources\ShippingZoneResource\Pages;
+use Lunar\Shipping\Models\ShippingZone;
+
+class ShippingZoneResource extends BaseResource
+{
+    protected static ?string $model = ShippingZone::class;
+
+    protected static ?int $navigationSort = 1;
+
+    protected static SubNavigationPosition $subNavigationPosition = SubNavigationPosition::End;
+
+    public static function getLabel(): string
+    {
+        return __('lunarpanel.shipping::shippingzone.label');
+    }
+
+    public static function getPluralLabel(): string
+    {
+        return __('lunarpanel.shipping::shippingzone.label_plural');
+    }
+
+    public static function getNavigationIcon(): ?string
+    {
+        return FilamentIcon::resolve('lunar::shipping-zones');
+    }
+
+    public static function getNavigationGroup(): ?string
+    {
+        return __('lunarpanel.shipping::plugin.navigation.group');
+    }
+
+    public static function getRecordSubNavigation(Page $page): array
+    {
+        return $page->generateNavigationItems([
+            Pages\EditShippingZone::class,
+            Pages\ManageShippingRates::class,
+            Pages\ManageShippingExclusions::class,
+        ]);
+    }
+
+    public static function getDefaultForm(Form $form): Form
+    {
+        return $form->schema([
+            Forms\Components\Section::make()->schema(
+                static::getMainFormComponents(),
+            ),
+        ]);
+    }
+
+    protected static function getMainFormComponents(): array
+    {
+        return [
+            static::getNameFormComponent(),
+            static::getTypeFormComponent(),
+            static::getCountryFormComponent(),
+            static::getPostcodesFormComponent(),
+            static::getStatesFormComponent(),
+            static::getCountriesFormComponent(),
+            Shout::make('unrestricted')->content(
+                __('lunarpanel.shipping::shippingzone.form.unrestricted.content')
+            )->hidden(
+                fn (Forms\Get $get) => $get('type') != 'unrestricted'
+            ),
+        ];
+    }
+
+    public static function getNameFormComponent(): Component
+    {
+        return Forms\Components\TextInput::make('name')
+            ->label(__('lunarpanel.shipping::shippingzone.form.name.label'))
+            ->required()
+            ->maxLength(255)
+            ->autofocus();
+    }
+
+    public static function getTypeFormComponent(): Component
+    {
+        return Forms\Components\Select::make('type')
+            ->label(__('lunarpanel.shipping::shippingzone.form.type.label'))
+            ->required()
+            ->options([
+                'unrestricted' => __('lunarpanel.shipping::shippingzone.form.type.options.unrestricted'),
+                'countries' => __('lunarpanel.shipping::shippingzone.form.type.options.countries'),
+                'states' => __('lunarpanel.shipping::shippingzone.form.type.options.states'),
+                'postcodes' => __('lunarpanel.shipping::shippingzone.form.type.options.postcodes'),
+            ])->live();
+    }
+
+    protected static function getCountryFormComponent(): Component
+    {
+        return Forms\Components\Select::make('country')
+            ->label(__('lunarpanel.shipping::shippingzone.form.country.label'))
+            ->dehydrated(false)
+            ->visible(
+                fn (Forms\Get $get) => ! in_array($get('type'), ['countries', 'unrestricted'])
+            )
+            ->options(Country::get()->pluck('name', 'id'))
+
+            ->required()
+            ->searchable()
+            ->loadStateFromRelationshipsUsing(static function (Forms\Components\Select $component, Model $record): void {
+                $record->loadMissing('countries');
+
+                /** @var Collection $relatedModels */
+                $country = $record->countries->first();
+
+                $component->state(
+                    $country?->id
+                );
+            })->getOptionLabelsUsing(static function (Model $record): array {
+                $record->loadMissing('countries.country');
+
+                return $record->countries
+                    ->pluck('country.name', 'country.id')
+                    ->toArray();
+            })
+            ->saveRelationshipsUsing(static function (Model $record, $state) {
+                $selectedCountry = Country::where('id', $state)->first();
+
+                $record->countries()->sync($selectedCountry->id);
+            });
+    }
+
+    protected static function getCountriesFormComponent(): Component
+    {
+        return Forms\Components\Select::make('countries')
+            ->label(__('lunarpanel.shipping::shippingzone.form.countries.label'))
+            ->visible(fn ($get) => $get('type') == 'countries')
+            ->dehydrated(false)
+            ->options(Country::get()->pluck('name', 'id'))
+            ->multiple()
+            ->required()
+            ->loadStateFromRelationshipsUsing(static function (Forms\Components\Select $component, Model $record): void {
+                $record->loadMissing('countries');
+                /** @var Collection $relatedModels */
+                $relatedModels = $record->countries;
+
+                $component->state(
+                    $relatedModels
+                        ->pluck('id')
+                        ->map(static fn ($key): string => strval($key))
+                        ->toArray(),
+                );
+            })->getOptionLabelsUsing(static function (Model $record): array {
+                $record->loadMissing('countries');
+
+                return $record->countries
+                    ->pluck('name', 'id')
+                    ->toArray();
+            })
+            ->saveRelationshipsUsing(static function (Model $record, $state) {
+                $record->countries()->sync($state);
+            });
+    }
+
+    protected static function getStatesFormComponent(): Component
+    {
+        return Forms\Components\Select::make('states')
+            ->label(__('lunarpanel.shipping::shippingzone.form.states.label'))
+            ->visible(fn ($get) => $get('type') == 'states')
+            ->dehydrated(false)
+            ->options(fn ($get) => State::where('country_id', $get('country'))->get()->pluck('name', 'id'))
+            ->multiple()
+            ->required()
+            ->loadStateFromRelationshipsUsing(static function (Forms\Components\Select $component, Model $record): void {
+                $record->loadMissing('states');
+
+                /** @var Collection $relatedModels */
+                $relatedModels = $record->states;
+
+                $component->state(
+                    $relatedModels
+                        ->pluck('id')
+                        ->map(static fn ($key): string => strval($key))
+                        ->toArray(),
+                );
+            })->getOptionLabelsUsing(static function (Model $record): array {
+                $record->loadMissing('states');
+
+                return $record->states
+                    ->pluck('name', 'id')
+                    ->toArray();
+            })
+            ->saveRelationshipsUsing(static function (Model $record, $state, $get) {
+                $record->states()->sync($state);
+            });
+    }
+
+    protected static function getPostcodesFormComponent(): Component
+    {
+        return Forms\Components\Textarea::make('postcodes')
+            ->label(__('lunarpanel.shipping::shippingzone.form.postcodes.label'))
+            ->visible(fn ($get) => $get('type') == 'postcodes')
+            ->dehydrated(false)
+            ->rows(10)
+            ->helperText(__('lunarpanel.shipping::shippingzone.form.postcodes.helper'))
+            ->required()
+            ->afterStateHydrated(static function (Forms\Components\Textarea $component, Model $record): void {
+                /** @var Collection $relatedModels */
+                $relatedModels = $record->postcodes;
+
+                $component->state(
+                    $relatedModels
+                        ->pluck('postcode')
+                        ->join("\n"),
+                );
+            })
+            ->saveRelationshipsUsing(static function (Model $record, $state, $get) {
+                static::syncPostcodes($record, $get('zone_country'), $state);
+
+                $record->states()->detach();
+            });
+    }
+
+    private static function syncPostcodes(ShippingZone $shippingZone, $countryId, $postcodes)
+    {
+        $postcodes = collect(
+            explode(
+                "\n",
+                str_replace(' ', '', $postcodes)
+            )
+        )->unique()->filter();
+
+        $shippingZone->postcodes()->delete();
+
+        $shippingZone->postcodes()->createMany(
+            $postcodes->map(function ($postcode) {
+                return [
+                    'postcode' => $postcode,
+                ];
+            })
+        );
+    }
+
+    public static function getDefaultTable(Table $table): Table
+    {
+        return $table
+            ->columns(static::getTableColumns())
+            ->filters([
+                //
+            ])
+            ->actions([
+                Tables\Actions\EditAction::make(),
+            ])
+            ->bulkActions([
+                Tables\Actions\BulkActionGroup::make([
+                    Tables\Actions\DeleteBulkAction::make(),
+                ]),
+            ]);
+    }
+
+    protected static function getTableColumns(): array
+    {
+        return [
+            Tables\Columns\TextColumn::make('name')
+                ->label(
+                    __('lunarpanel.shipping::shippingzone.table.name.label')
+                ),
+            Tables\Columns\TextColumn::make('type')
+                ->label(
+                    __('lunarpanel.shipping::shippingzone.table.type.label')
+                )
+                ->formatStateUsing(
+                    fn ($state) => __("lunarpanel.shipping::shippingzone.table.type.options.{$state}")
+                ),
+        ];
+    }
+
+    public static function getRelations(): array
+    {
+        return [
+            //
+        ];
+    }
+
+    public static function getPages(): array
+    {
+        return [
+            'index' => Pages\ListShippingZones::route('/'),
+            'edit' => Pages\EditShippingZone::route('/{record}/edit'),
+            'rates' => Pages\ManageShippingRates::route('/{record}/rates'),
+            'exclusions' => Pages\ManageShippingExclusions::route('/{record}/exclusions'),
+        ];
+    }
+}
diff --git a/packages/table-rate-shipping/src/Filament/Resources/ShippingZoneResource/Pages/EditShippingZone.php b/packages/table-rate-shipping/src/Filament/Resources/ShippingZoneResource/Pages/EditShippingZone.php
new file mode 100644
index 0000000000..754106f684
--- /dev/null
+++ b/packages/table-rate-shipping/src/Filament/Resources/ShippingZoneResource/Pages/EditShippingZone.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Lunar\Shipping\Filament\Resources\ShippingZoneResource\Pages;
+
+use Filament\Actions;
+use Lunar\Admin\Support\Pages\BaseEditRecord;
+use Lunar\Shipping\Filament\Resources\ShippingZoneResource;
+
+class EditShippingZone extends BaseEditRecord
+{
+    protected static string $resource = ShippingZoneResource::class;
+
+    protected function getDefaultHeaderActions(): array
+    {
+        return [
+            Actions\DeleteAction::make(),
+        ];
+    }
+
+    protected function getRedirectUrl(): string
+    {
+        return $this->getResource()::getUrl('index');
+    }
+}
diff --git a/packages/table-rate-shipping/src/Filament/Resources/ShippingZoneResource/Pages/ListShippingZones.php b/packages/table-rate-shipping/src/Filament/Resources/ShippingZoneResource/Pages/ListShippingZones.php
new file mode 100644
index 0000000000..6df578862d
--- /dev/null
+++ b/packages/table-rate-shipping/src/Filament/Resources/ShippingZoneResource/Pages/ListShippingZones.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace Lunar\Shipping\Filament\Resources\ShippingZoneResource\Pages;
+
+use Filament\Actions;
+use Lunar\Admin\Support\Pages\BaseListRecords;
+use Lunar\Shipping\Filament\Resources\ShippingZoneResource;
+
+class ListShippingZones extends BaseListRecords
+{
+    protected static string $resource = ShippingZoneResource::class;
+
+    protected function getDefaultHeaderActions(): array
+    {
+        return [
+            Actions\CreateAction::make()->form([
+                ShippingZoneResource::getNameFormComponent(),
+                ShippingZoneResource::getTypeFormComponent(),
+            ]),
+        ];
+    }
+}
diff --git a/packages/table-rate-shipping/src/Filament/Resources/ShippingZoneResource/Pages/ManageShippingExclusions.php b/packages/table-rate-shipping/src/Filament/Resources/ShippingZoneResource/Pages/ManageShippingExclusions.php
new file mode 100644
index 0000000000..6dc14185e7
--- /dev/null
+++ b/packages/table-rate-shipping/src/Filament/Resources/ShippingZoneResource/Pages/ManageShippingExclusions.php
@@ -0,0 +1,62 @@
+<?php
+
+namespace Lunar\Shipping\Filament\Resources\ShippingZoneResource\Pages;
+
+use Filament\Forms\Form;
+use Filament\Resources\Pages\ManageRelatedRecords;
+use Filament\Support\Facades\FilamentIcon;
+use Filament\Tables;
+use Filament\Tables\Table;
+use Illuminate\Contracts\Support\Htmlable;
+use Lunar\Shipping\Filament\Resources\ShippingExclusionListResource;
+use Lunar\Shipping\Filament\Resources\ShippingZoneResource;
+
+class ManageShippingExclusions extends ManageRelatedRecords
+{
+    protected static string $resource = ShippingZoneResource::class;
+
+    protected static string $relationship = 'shippingExclusions';
+
+    protected static ?string $recordTitle = 'name';
+
+    public function getTitle(): string|Htmlable
+    {
+        return __('lunarpanel.shipping::relationmanagers.exclusions.title_plural');
+    }
+
+    public static function getNavigationIcon(): ?string
+    {
+        return FilamentIcon::resolve('lunar::shipping-exclusion-lists');
+    }
+
+    public static function getNavigationLabel(): string
+    {
+        return __('lunarpanel.shipping::relationmanagers.exclusions.title_plural');
+    }
+
+    public function form(Form $form): Form
+    {
+        return $form->schema([]);
+    }
+
+    public function table(Table $table): Table
+    {
+        return $table->columns(
+            ShippingExclusionListResource::getTableColumns(),
+        )->headerActions([
+            Tables\Actions\AttachAction::make()
+                ->color('primary')
+                ->label(
+                    __('lunarpanel.shipping::relationmanagers.exclusions.actions.attach.label')
+                )
+                ->preloadRecordSelect()
+                ->recordTitleAttribute('name'),
+        ])->actions([
+            Tables\Actions\DetachAction::make('detach')
+                ->label(
+                    __('lunarpanel.shipping::relationmanagers.exclusions.actions.detach.label')
+                ),
+
+        ]);
+    }
+}
diff --git a/packages/table-rate-shipping/src/Filament/Resources/ShippingZoneResource/Pages/ManageShippingRates.php b/packages/table-rate-shipping/src/Filament/Resources/ShippingZoneResource/Pages/ManageShippingRates.php
new file mode 100644
index 0000000000..8269a33380
--- /dev/null
+++ b/packages/table-rate-shipping/src/Filament/Resources/ShippingZoneResource/Pages/ManageShippingRates.php
@@ -0,0 +1,192 @@
+<?php
+
+namespace Lunar\Shipping\Filament\Resources\ShippingZoneResource\Pages;
+
+use Awcodes\Shout\Components\Shout;
+use Filament\Forms;
+use Filament\Forms\Form;
+use Filament\Resources\Pages\ManageRelatedRecords;
+use Filament\Support\Facades\FilamentIcon;
+use Filament\Tables;
+use Filament\Tables\Columns\TextColumn;
+use Filament\Tables\Table;
+use Illuminate\Contracts\Support\Htmlable;
+use Illuminate\Database\Eloquent\Model;
+use Lunar\Models\Currency;
+use Lunar\Models\CustomerGroup;
+use Lunar\Models\Price;
+use Lunar\Shipping\Filament\Resources\ShippingZoneResource;
+use Lunar\Shipping\Models\ShippingRate;
+
+class ManageShippingRates extends ManageRelatedRecords
+{
+    protected static string $resource = ShippingZoneResource::class;
+
+    protected static string $relationship = 'rates';
+
+    public function getTitle(): string|Htmlable
+    {
+        return __('lunarpanel.shipping::relationmanagers.shipping_rates.title_plural');
+    }
+
+    public static function getNavigationIcon(): ?string
+    {
+        return FilamentIcon::resolve('lunar::shipping-rates');
+    }
+
+    public static function getNavigationLabel(): string
+    {
+        return __('lunarpanel.shipping::relationmanagers.shipping_rates.title_plural');
+    }
+
+    public function form(Form $form): Form
+    {
+        return $form->schema([
+            Shout::make('')->content(
+                function () {
+                    $pricesIncTax = config('lunar.pricing.stored_inclusive_of_tax', false);
+
+                    if ($pricesIncTax) {
+                        return __('lunarpanel.shipping::relationmanagers.shipping_rates.notices.prices_inc_tax');
+                    }
+
+                    return __('lunarpanel.shipping::relationmanagers.shipping_rates.notices.prices_excl_tax');
+                }
+            ),
+            Forms\Components\Select::make('shipping_method_id')
+                ->label(
+                    __('lunarpanel.shipping::relationmanagers.shipping_rates.form.shipping_method_id.label')
+                )
+                ->relationship(name: 'shippingMethod', titleAttribute: 'name')
+                ->columnSpan(2),
+            Forms\Components\TextInput::make('price')
+                ->label(
+                    __('lunarpanel.shipping::relationmanagers.shipping_rates.form.price.label')
+                )
+                ->numeric()
+                ->required()
+                ->columnSpan(2)
+                ->afterStateHydrated(static function (Forms\Components\TextInput $component, Model $record = null): void {
+                    if ($record) {
+                        $basePrice = $record->basePrices->first();
+
+                        $component->state(
+                            $basePrice->price->decimal
+                        );
+                    }
+                }),
+            Forms\Components\Repeater::make('prices')
+                ->label(
+                    __('lunarpanel.shipping::relationmanagers.shipping_rates.form.prices.label')
+                )->schema([
+                    Forms\Components\Select::make('customer_group_id')
+                        ->label(
+                            __('lunarpanel.shipping::relationmanagers.shipping_rates.form.prices.repeater.customer_group_id.label')
+                        )
+                        ->options(
+                            fn () => CustomerGroup::all()->pluck('name', 'id')
+                        )->placeholder(
+                            __('lunarpanel.shipping::relationmanagers.shipping_rates.form.prices.repeater.customer_group_id.placeholder')
+                        )->preload(),
+                    Forms\Components\Select::make('currency_id')
+                        ->label(
+                            __('lunarpanel.shipping::relationmanagers.shipping_rates.form.prices.repeater.currency_id.label')
+                        )
+                        ->options(
+                            fn () => Currency::all()->pluck('name', 'id')
+                        )->default(
+                            Currency::getDefault()->id
+                        )->required()->preload(),
+                    Forms\Components\TextInput::make('quantity_break')
+                        ->label(
+                            __('lunarpanel.shipping::relationmanagers.shipping_rates.form.prices.repeater.quantity_break.label')
+                        )
+                        ->numeric()
+                        ->required(),
+                    Forms\Components\TextInput::make('price')
+                        ->label(
+                            __('lunarpanel.shipping::relationmanagers.shipping_rates.form.prices.repeater.price.label')
+                        )
+                        ->numeric()
+                        ->required(),
+                ])->afterStateHydrated(
+                    static function (Forms\Components\Repeater $component, Model $record = null): void {
+                        if ($record) {
+                            $component->state(
+                                $record->quantityBreaks->map(function ($price) {
+                                    return [
+                                        'customer_group_id' => $price->customer_group_id,
+                                        'price' => $price->price->decimal,
+                                        'currency_id' => $price->currency_id,
+                                        'quantity_break' => $price->quantity_break / 100,
+                                    ];
+                                })->toArray()
+                            );
+                        }
+                    }
+                )->columns(4),
+        ])->columns(1);
+    }
+
+    public function table(Table $table): Table
+    {
+        return $table->columns([
+            TextColumn::make('shippingMethod.name')
+                ->label(
+                    __('lunarpanel.shipping::relationmanagers.shipping_rates.table.shipping_method.label')
+                ),
+            TextColumn::make('basePrices.0')->formatStateUsing(
+                fn ($state = null) => $state->price->formatted
+            )->label(
+                __('lunarpanel.shipping::relationmanagers.shipping_rates.table.price.label')
+            ),
+            TextColumn::make('quantity_breaks_count')
+                ->label(
+                    __('lunarpanel.shipping::relationmanagers.shipping_rates.table.quantity_breaks_count.label')
+                )->counts('quantityBreaks'),
+        ])->headerActions([
+            Tables\Actions\CreateAction::make()->label(
+                __('lunarpanel.shipping::relationmanagers.shipping_rates.actions.create.label')
+            )->action(function (Table $table, ShippingRate $shippingRate = null, array $data = []) {
+                $relationship = $table->getRelationship();
+
+                $record = new ShippingRate();
+                $record->shipping_method_id = $data['shipping_method_id'];
+                $relationship->save($record);
+
+                static::saveShippingRate($record, $data);
+            })->slideOver(),
+        ])->actions([
+            Tables\Actions\EditAction::make()->slideOver()->action(function (ShippingRate $shippingRate, array $data) {
+                static::saveShippingRate($shippingRate, $data);
+            }),
+
+        ]);
+    }
+
+    protected static function saveShippingRate(ShippingRate $shippingRate = null, array $data = []): void
+    {
+        $currency = Currency::getDefault();
+
+        $basePrice = $shippingRate->basePrices->first() ?: new Price;
+
+        $basePrice->price = (int) ($data['price'] * $currency->factor);
+        $basePrice->priceable_type = get_class($shippingRate);
+        $basePrice->currency_id = $currency->id;
+        $basePrice->priceable_id = $shippingRate->id;
+        $basePrice->customer_group_id = null;
+        $basePrice->save();
+
+        $shippingRate->quantityBreaks()->delete();
+
+        $tiers = collect($data['prices'] ?? [])->map(
+            function ($price) {
+                $price['quantity_break'] = $price['quantity_break'] * 100;
+
+                return $price;
+            }
+        );
+
+        $shippingRate->prices()->createMany($tiers->toArray());
+    }
+}
diff --git a/packages/table-rate-shipping/src/Interfaces/ShippingMethodInterface.php b/packages/table-rate-shipping/src/Interfaces/ShippingRateInterface.php
similarity index 76%
rename from packages/table-rate-shipping/src/Interfaces/ShippingMethodInterface.php
rename to packages/table-rate-shipping/src/Interfaces/ShippingRateInterface.php
index 6c0d45d467..a78aba5bb3 100644
--- a/packages/table-rate-shipping/src/Interfaces/ShippingMethodInterface.php
+++ b/packages/table-rate-shipping/src/Interfaces/ShippingRateInterface.php
@@ -4,9 +4,9 @@
 
 use Lunar\DataTypes\ShippingOption;
 use Lunar\Shipping\DataTransferObjects\ShippingOptionRequest;
-use Lunar\Shipping\Models\ShippingMethod;
+use Lunar\Shipping\Models\ShippingRate;
 
-interface ShippingMethodInterface
+interface ShippingRateInterface
 {
     /**
      * Return the name of the shipping method.
@@ -21,12 +21,10 @@ public function description(): string;
     /**
      * Set the context for the driver.
      */
-    public function on(ShippingMethod $shippingMethod): self;
+    public function on(ShippingRate $shippingRate): self;
 
     /**
      * Return the shipping option price.
-     *
-     * @return ShippingOption
      */
     public function resolve(ShippingOptionRequest $shippingOptionRequest): ?ShippingOption;
 }
diff --git a/packages/table-rate-shipping/src/Managers/ShippingManager.php b/packages/table-rate-shipping/src/Managers/ShippingManager.php
index 9ba48b39bd..ae7444726f 100644
--- a/packages/table-rate-shipping/src/Managers/ShippingManager.php
+++ b/packages/table-rate-shipping/src/Managers/ShippingManager.php
@@ -9,8 +9,8 @@
 use Lunar\Shipping\Drivers\ShippingMethods\FreeShipping;
 use Lunar\Shipping\Drivers\ShippingMethods\ShipBy;
 use Lunar\Shipping\Interfaces\ShippingMethodManagerInterface;
-use Lunar\Shipping\Resolvers\ShippingMethodResolver;
 use Lunar\Shipping\Resolvers\ShippingOptionResolver;
+use Lunar\Shipping\Resolvers\ShippingRateResolver;
 use Lunar\Shipping\Resolvers\ShippingZoneResolver;
 
 class ShippingManager extends Manager implements ShippingMethodManagerInterface
@@ -35,7 +35,7 @@ public function createCollectionDriver()
         return $this->buildProvider(Collection::class);
     }
 
-    public function getSupportedDrivers()
+    public function getSupportedDrivers(): \Illuminate\Support\Collection
     {
         return collect([
             'free-shipping' => $this->createDriver('free-shipping'),
@@ -53,27 +53,24 @@ public function getSupportedDrivers()
 
     /**
      * Find the zone for a given address.
-     *
-     * @param  Cart  $cart
-     * @return Collection
      */
-    public function zones()
+    public function zones(): ShippingZoneResolver
     {
         return new ShippingZoneResolver();
     }
 
-    public function shippingMethods(Cart $cart = null)
+    public function shippingRates(Cart $cart = null): ShippingRateResolver
     {
-        return new ShippingMethodResolver($cart);
+        return new ShippingRateResolver($cart);
     }
 
-    public function shippingOptions(Cart $cart = null)
+    public function shippingOptions(Cart $cart = null): ShippingOptionResolver
     {
         return new ShippingOptionResolver($cart);
     }
 
     /**
-     * Build a tax provider instance.
+     * Build a shipping provider instance
      *
      * @param  string  $provider
      * @return mixed
diff --git a/packages/table-rate-shipping/src/Models/ShippingExclusionList.php b/packages/table-rate-shipping/src/Models/ShippingExclusionList.php
index 664d784258..e367d20105 100644
--- a/packages/table-rate-shipping/src/Models/ShippingExclusionList.php
+++ b/packages/table-rate-shipping/src/Models/ShippingExclusionList.php
@@ -3,6 +3,8 @@
 namespace Lunar\Shipping\Models;
 
 use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Relations\BelongsToMany;
+use Illuminate\Database\Eloquent\Relations\HasMany;
 use Lunar\Base\BaseModel;
 use Lunar\Shipping\Factories\ShippingExclusionListFactory;
 
@@ -28,28 +30,32 @@ protected static function newFactory(): ShippingExclusionListFactory
         return ShippingExclusionListFactory::new();
     }
 
+    protected static function booted()
+    {
+        static::deleting(function (ShippingExclusionList $list) {
+            $list->exclusions()->delete();
+            $list->shippingZones()->detach();
+        });
+    }
+
     /**
      * Return the shipping zone relationship.
-     *
-     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
      */
-    public function exclusions()
+    public function exclusions(): HasMany
     {
         return $this->hasMany(ShippingExclusion::class);
     }
 
     /**
      * Return the shipping methods relationship.
-     *
-     * @return \Illuminate\Database\Eloquent\Relations\HasMany
      */
-    public function shippingMethods()
+    public function shippingZones(): BelongsToMany
     {
         return $this->belongsToMany(
-            ShippingMethod::class,
-            config('lunar.database.table_prefix').'exclusion_list_shipping_method',
+            ShippingZone::class,
+            config('lunar.database.table_prefix').'exclusion_list_shipping_zone',
             'exclusion_id',
-            'method_id',
+            'shipping_zone_id',
         );
     }
 }
diff --git a/packages/table-rate-shipping/src/Models/ShippingMethod.php b/packages/table-rate-shipping/src/Models/ShippingMethod.php
index 18b3d03c77..bb7be31610 100644
--- a/packages/table-rate-shipping/src/Models/ShippingMethod.php
+++ b/packages/table-rate-shipping/src/Models/ShippingMethod.php
@@ -2,21 +2,16 @@
 
 namespace Lunar\Shipping\Models;
 
+use Illuminate\Database\Eloquent\Casts\AsArrayObject;
 use Illuminate\Database\Eloquent\Factories\HasFactory;
-use Illuminate\Support\Collection;
+use Illuminate\Database\Eloquent\Relations\HasMany;
 use Lunar\Base\BaseModel;
-use Lunar\Base\Purchasable;
-use Lunar\Base\Traits\HasPrices;
-use Lunar\DataTypes\ShippingOption;
-use Lunar\Models\Cart;
-use Lunar\Models\TaxClass;
 use Lunar\Shipping\Database\Factories\ShippingMethodFactory;
-use Lunar\Shipping\DataTransferObjects\ShippingOptionRequest;
 use Lunar\Shipping\Facades\Shipping;
 
-class ShippingMethod extends BaseModel implements Purchasable
+class ShippingMethod extends BaseModel
 {
-    use HasFactory, HasPrices;
+    use HasFactory;
 
     /**
      * Define which attributes should be
@@ -27,139 +22,24 @@ class ShippingMethod extends BaseModel implements Purchasable
     protected $guarded = [];
 
     protected $casts = [
-        'data' => 'object',
+        'data' => AsArrayObject::class,
     ];
 
     /**
      * Return a new factory instance for the model.
-     *
-     * @return \Lunar\Shipping\Factories\ShippingMethodFactory
      */
     protected static function newFactory(): ShippingMethodFactory
     {
         return ShippingMethodFactory::new();
     }
 
-    /**
-     * Return the shipping zone relationship.
-     *
-     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
-     */
-    public function shippingZone()
-    {
-        return $this->belongsTo(ShippingZone::class);
-    }
-
-    /**
-     * Return the shipping method driver.
-     */
-    public function getShippingOption(Cart $cart): ?ShippingOption
+    public function shippingRates(): HasMany
     {
-        return $this->driver()->resolve(
-            new ShippingOptionRequest(
-                cart: $cart,
-                shippingMethod: $this
-            )
-        );
+        return $this->hasMany(ShippingRate::class);
     }
 
     public function driver()
     {
         return Shipping::driver($this->driver);
     }
-
-    /**
-     * Return the shipping exclusions property.
-     *
-     * @return \Illuminate\Database\Eloquent\Relations\HasMany
-     */
-    public function shippingExclusions()
-    {
-        return $this->belongsToMany(
-            ShippingExclusionList::class,
-            config('lunar.database.table_prefix').'exclusion_list_shipping_method',
-            'method_id',
-            'exclusion_id',
-            // 'method_id',
-        )->withTimestamps();
-    }
-
-    public function getPrices(): Collection
-    {
-        return $this->prices;
-    }
-
-    /**
-     * Return the unit quantity for the variant.
-     */
-    public function getUnitQuantity(): int
-    {
-        return 1;
-    }
-
-    /**
-     * Return the tax class.
-     */
-    public function getTaxClass(): TaxClass
-    {
-        return TaxClass::getDefault();
-    }
-
-    public function getTaxReference()
-    {
-        return $this->code;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public function getType()
-    {
-        return 'shipping';
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public function isShippable()
-    {
-        return false;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public function getDescription()
-    {
-        return $this->name ?: $this->driver()->name();
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public function getOption()
-    {
-        return $this->code;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public function getOptions()
-    {
-        return collect();
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public function getIdentifier()
-    {
-        return $this->code;
-    }
-
-    public function getThumbnail()
-    {
-        return null;
-    }
 }
diff --git a/packages/table-rate-shipping/src/Models/ShippingRate.php b/packages/table-rate-shipping/src/Models/ShippingRate.php
new file mode 100644
index 0000000000..ce4c4bda7e
--- /dev/null
+++ b/packages/table-rate-shipping/src/Models/ShippingRate.php
@@ -0,0 +1,138 @@
+<?php
+
+namespace Lunar\Shipping\Models;
+
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Support\Collection;
+use Lunar\Base\BaseModel;
+use Lunar\Base\Purchasable;
+use Lunar\Base\Traits\HasPrices;
+use Lunar\DataTypes\ShippingOption;
+use Lunar\Models\Cart;
+use Lunar\Models\TaxClass;
+use Lunar\Shipping\Database\Factories\ShippingRateFactory;
+use Lunar\Shipping\DataTransferObjects\ShippingOptionRequest;
+
+class ShippingRate extends BaseModel implements Purchasable
+{
+    use HasFactory;
+    use HasPrices;
+
+    /**
+     * Define which attributes should be
+     * protected from mass assignment.
+     *
+     * @var array
+     */
+    protected $guarded = [];
+
+    /**
+     * Return a new factory instance for the model.
+     */
+    protected static function newFactory(): ShippingRateFactory
+    {
+        return ShippingRateFactory::new();
+    }
+
+    public function shippingZone()
+    {
+        return $this->belongsTo(ShippingZone::class);
+    }
+
+    public function shippingMethod()
+    {
+        return $this->belongsTo(ShippingMethod::class);
+    }
+
+    public function getPrices(): Collection
+    {
+        return $this->prices;
+    }
+
+    /**
+     * Return the unit quantity for the variant.
+     */
+    public function getUnitQuantity(): int
+    {
+        return 1;
+    }
+
+    /**
+     * Return the tax class.
+     */
+    public function getTaxClass(): TaxClass
+    {
+        return TaxClass::getDefault();
+    }
+
+    public function getTaxReference()
+    {
+        return $this->shippingMethod->code;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getType()
+    {
+        return 'shipping';
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function isShippable()
+    {
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getDescription()
+    {
+        return $this->shippingMethod->name ?: $this->driver()->name();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getOption()
+    {
+        return $this->shippingMethod->code;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getOptions()
+    {
+        return collect();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getIdentifier()
+    {
+        return $this->shippingMethod->code;
+    }
+
+    public function getThumbnail()
+    {
+        return null;
+    }
+
+    /**
+     * Return the shipping method driver.
+     */
+    public function getShippingOption(Cart $cart): ?ShippingOption
+    {
+        return $this->shippingMethod->driver()->resolve(
+            new ShippingOptionRequest(
+                shippingRate: $this,
+                cart: $cart,
+            )
+        );
+    }
+}
diff --git a/packages/table-rate-shipping/src/Models/ShippingZone.php b/packages/table-rate-shipping/src/Models/ShippingZone.php
index 7dc102f380..cee495a6b9 100644
--- a/packages/table-rate-shipping/src/Models/ShippingZone.php
+++ b/packages/table-rate-shipping/src/Models/ShippingZone.php
@@ -3,6 +3,7 @@
 namespace Lunar\Shipping\Models;
 
 use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Relations\BelongsToMany;
 use Illuminate\Database\Eloquent\Relations\HasMany;
 use Lunar\Base\BaseModel;
 use Lunar\Models\Country;
@@ -76,4 +77,22 @@ public function postcodes()
     {
         return $this->hasMany(ShippingZonePostcode::class);
     }
+
+    public function rates()
+    {
+        return $this->hasMany(ShippingRate::class);
+    }
+
+    /**
+     * Return the shipping exclusions property.
+     */
+    public function shippingExclusions(): BelongsToMany
+    {
+        return $this->belongsToMany(
+            ShippingExclusionList::class,
+            config('lunar.database.table_prefix').'exclusion_list_shipping_zone',
+            'shipping_zone_id',
+            'exclusion_id',
+        )->withTimestamps();
+    }
 }
diff --git a/packages/table-rate-shipping/src/Resolvers/ShippingOptionResolver.php b/packages/table-rate-shipping/src/Resolvers/ShippingOptionResolver.php
index a0c9f50a24..03986491bd 100644
--- a/packages/table-rate-shipping/src/Resolvers/ShippingOptionResolver.php
+++ b/packages/table-rate-shipping/src/Resolvers/ShippingOptionResolver.php
@@ -47,10 +47,10 @@ public function get(ShippingOptionLookup $shippingOptionLookup): Collection
             return collect();
         }
 
-        foreach ($shippingOptionLookup->shippingMethods as $shippingMethod) {
+        foreach ($shippingOptionLookup->shippingRates as $shippingRate) {
             $shippingOptions->push((object) [
-                'shippingMethod' => $shippingMethod,
-                'option' => $shippingMethod->getShippingOption($this->cart),
+                'shippingRate' => $shippingRate,
+                'option' => $shippingRate->getShippingOption($this->cart),
             ]);
         }
 
@@ -61,7 +61,7 @@ public function get(ShippingOptionLookup $shippingOptionLookup): Collection
         })->each(function ($option) {
             ShippingOptionResolvedEvent::dispatch(
                 $this->cart,
-                $option->shippingMethod,
+                $option->shippingRate,
                 $option->option
             );
         });
diff --git a/packages/table-rate-shipping/src/Resolvers/ShippingMethodResolver.php b/packages/table-rate-shipping/src/Resolvers/ShippingRateResolver.php
similarity index 85%
rename from packages/table-rate-shipping/src/Resolvers/ShippingMethodResolver.php
rename to packages/table-rate-shipping/src/Resolvers/ShippingRateResolver.php
index 1b6f42d0a3..8acbca8758 100644
--- a/packages/table-rate-shipping/src/Resolvers/ShippingMethodResolver.php
+++ b/packages/table-rate-shipping/src/Resolvers/ShippingRateResolver.php
@@ -9,7 +9,7 @@
 use Lunar\Shipping\DataTransferObjects\PostcodeLookup;
 use Lunar\Shipping\Facades\Shipping;
 
-class ShippingMethodResolver
+class ShippingRateResolver
 {
     /**
      * The cart to use when resolving.
@@ -143,11 +143,13 @@ public function get(): Collection
             )
         )->get();
 
-        $shippingMethods = collect();
+        $shippingRates = collect();
 
         foreach ($zones as $zone) {
-            $shippingMethods = $zone->shippingMethods
-                ->reject(function ($method) {
+            $shippingRates = $zone->rates
+                ->reject(function ($rate) {
+                    $method = $rate->shippingMethod;
+
                     if (! $method->cutoff) {
                         return false;
                     }
@@ -159,23 +161,23 @@ public function get(): Collection
                         ->set('second', $s)
                         ->isPast();
                 })
-                ->reject(function ($method) {
-                    if ($this->allCartItemsAreInStock || ! ($method->stock_available ?? false)) {
+                ->reject(function ($rate) {
+                    if ($this->allCartItemsAreInStock || ! ($rate->shippingMethod->stock_available ?? false)) {
                         return false;
                     }
 
                     return true;
                 });
 
-            foreach ($shippingMethods as $shippingMethod) {
-                $shippingMethods->push(
-                    $shippingMethod
+            foreach ($shippingRates as $shippingRate) {
+                $shippingRates->push(
+                    $shippingRate
                 );
             }
         }
 
-        return $shippingMethods->filter()->unique(function ($method) {
-            return $method->code;
+        return $shippingRates->filter()->unique(function ($rate) {
+            return $rate->shippingMethod->code;
         });
     }
 }
diff --git a/packages/table-rate-shipping/src/ShippingModifier.php b/packages/table-rate-shipping/src/ShippingModifier.php
index c1d984f2c2..7283fac565 100644
--- a/packages/table-rate-shipping/src/ShippingModifier.php
+++ b/packages/table-rate-shipping/src/ShippingModifier.php
@@ -11,11 +11,11 @@ class ShippingModifier
 {
     public function handle(Cart $cart)
     {
-        $shippingMethods = Shipping::shippingMethods($cart)->get();
+        $shippingRates = Shipping::shippingRates($cart)->get();
 
         $options = Shipping::shippingOptions($cart)->get(
             new ShippingOptionLookup(
-                shippingMethods: $shippingMethods
+                shippingRates: $shippingRates
             )
         );
 
diff --git a/packages/table-rate-shipping/src/ShippingPlugin.php b/packages/table-rate-shipping/src/ShippingPlugin.php
new file mode 100644
index 0000000000..6ffd06719a
--- /dev/null
+++ b/packages/table-rate-shipping/src/ShippingPlugin.php
@@ -0,0 +1,57 @@
+<?php
+
+namespace Lunar\Shipping;
+
+use Filament\Contracts\Plugin;
+use Filament\Navigation\NavigationGroup;
+use Filament\Panel;
+use Filament\Support\Facades\FilamentIcon;
+use Lunar\Shipping\Filament\Resources\ShippingExclusionListResource;
+use Lunar\Shipping\Filament\Resources\ShippingMethodResource;
+use Lunar\Shipping\Filament\Resources\ShippingZoneResource;
+
+class ShippingPlugin implements Plugin
+{
+    public function getId(): string
+    {
+        return 'shipping';
+    }
+
+    public function boot(Panel $panel): void
+    {
+        // TODO: Implement boot() method.
+    }
+
+    public function register(Panel $panel): void
+    {
+        $panel->navigationGroups([
+            NavigationGroup::make('shipping')
+                ->label(
+                    fn () => __('lunarpanel.shipping::plugin.navigation.group')
+                ),
+        ])->resources([
+            ShippingMethodResource::class,
+            ShippingZoneResource::class,
+            ShippingExclusionListResource::class,
+        ]);
+
+        FilamentIcon::register([
+            'lunar::shipping-rates' => 'lucide-coins',
+            'lunar::shipping-zones' => 'lucide-globe-2',
+            'lunar::shipping-methods' => 'lucide-truck',
+            'lunar::shipping-exclusion-lists' => 'lucide-package-minus',
+        ]);
+    }
+
+    public static function make(): static
+    {
+        return app(static::class);
+    }
+
+    public function panel(Panel $panel): Panel
+    {
+        return $panel;
+    }
+
+    // ...
+}
diff --git a/packages/table-rate-shipping/src/ShippingServiceProvider.php b/packages/table-rate-shipping/src/ShippingServiceProvider.php
index 8bddb50e25..0b2227df05 100644
--- a/packages/table-rate-shipping/src/ShippingServiceProvider.php
+++ b/packages/table-rate-shipping/src/ShippingServiceProvider.php
@@ -22,14 +22,14 @@ public function boot(ShippingModifiers $shippingModifiers)
             return;
         }
 
-        $this->loadTranslationsFrom(__DIR__.'/../lang', 'shipping');
+        $this->loadTranslationsFrom(__DIR__.'/../resources/lang', 'lunarpanel.shipping');
 
         $this->loadMigrationsFrom(__DIR__.'/../database/migrations');
 
         $this->loadViewsFrom(__DIR__.'/../resources/views', 'shipping');
 
         $shippingModifiers->add(
-            ShippingModifier::class, // TODO: param typed incorrectly
+            ShippingModifier::class,
         );
 
         Order::observe(OrderObserver::class);
diff --git a/tests/admin/Feature/Filament/Resources/CollectionResources/Pages/ManageCollectionChildrenTest.php b/tests/admin/Feature/Filament/Resources/CollectionResources/Pages/ManageCollectionChildrenTest.php
index 404a4444f8..20b5162deb 100644
--- a/tests/admin/Feature/Filament/Resources/CollectionResources/Pages/ManageCollectionChildrenTest.php
+++ b/tests/admin/Feature/Filament/Resources/CollectionResources/Pages/ManageCollectionChildrenTest.php
@@ -30,6 +30,9 @@
         'name' => [
             'en' => 'Name',
         ],
+        'description' => [
+            'en' => 'Description',
+        ],
         'handle' => 'name',
         'type' => \Lunar\FieldTypes\TranslatedText::class,
         'attribute_type' => \Lunar\Models\Collection::class,
diff --git a/tests/admin/Feature/Filament/Resources/ProductResource/ListProductsTest.php b/tests/admin/Feature/Filament/Resources/ProductResource/ListProductsTest.php
index c160ff7cfd..bc0ee2fc21 100644
--- a/tests/admin/Feature/Filament/Resources/ProductResource/ListProductsTest.php
+++ b/tests/admin/Feature/Filament/Resources/ProductResource/ListProductsTest.php
@@ -13,6 +13,9 @@
         'name' => [
             'en' => 'Name',
         ],
+        'description' => [
+            'en' => 'Description',
+        ],
     ]);
     \Lunar\Models\TaxClass::factory()->create([
         'default' => true,
diff --git a/tests/admin/Feature/Filament/Resources/ProductResource/Pages/ManageProductPricingTest.php b/tests/admin/Feature/Filament/Resources/ProductResource/Pages/ManageProductPricingTest.php
index 5128174540..f087c53e0c 100644
--- a/tests/admin/Feature/Filament/Resources/ProductResource/Pages/ManageProductPricingTest.php
+++ b/tests/admin/Feature/Filament/Resources/ProductResource/Pages/ManageProductPricingTest.php
@@ -96,17 +96,15 @@
     \Livewire\Livewire::test(
         \Lunar\Admin\Filament\Resources\ProductResource\Pages\ManageProductPricing::class, [
             'record' => $record->getRouteKey(),
-        ])->assertFormSet([
-            'price' => null,
         ])->callTableAction('create', data: [
             'price' => 10.99,
             'currency_id' => $currency->id,
-            'tier' => 1,
+            'quantity_break' => 1,
         ])->assertHasNoErrors();
 
     $this->assertDatabaseHas((new \Lunar\Models\Price())->getTable(), [
         'price' => 1099,
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'customer_group_id' => null,
     ]);
diff --git a/tests/admin/Unit/Actions/Products/MapVariantsToProductOptionsTest.php b/tests/admin/Unit/Actions/Products/MapVariantsToProductOptionsTest.php
new file mode 100644
index 0000000000..66cda680e0
--- /dev/null
+++ b/tests/admin/Unit/Actions/Products/MapVariantsToProductOptionsTest.php
@@ -0,0 +1,79 @@
+<?php
+
+use Lunar\Admin\Actions\Products\MapVariantsToProductOptions;
+
+uses(\Lunar\Tests\Admin\Feature\Filament\TestCase::class)
+    ->group('support.actions');
+
+it('can map variants given one set of option values', function () {
+
+    $optionValues = [
+        'Shoe Size' => [
+            'UK-5',
+            'UK-10',
+            'UK-15',
+        ],
+    ];
+
+    $variants = [
+        [
+            'id' => 1,
+            'sku' => 'ABC',
+            'values' => [
+                'Shoe Size' => 'UK-5',
+            ],
+        ],
+        [
+            'id' => 2,
+            'sku' => 'DEF',
+            'values' => [
+                'Shoe Size' => 'UK-10',
+            ],
+        ],
+        [
+            'id' => 3,
+            'sku' => 'GHI',
+            'values' => [
+                'Shoe Size' => 'UK-15',
+            ],
+        ],
+    ];
+
+    $result = MapVariantsToProductOptions::map($optionValues, $variants);
+
+    expect($result[0]['sku'])->toBe('ABC');
+    expect($result[1]['sku'])->toBe('DEF');
+    expect($result[2]['sku'])->toBe('GHI');
+});
+
+it('can map variants given three sets of option values', function () {
+
+    $optionValues = [
+        'Size' => [
+            'Small',
+            'Medium',
+        ],
+        'Colour' => [
+            'Blue',
+            'Black',
+        ],
+        'Material' => [
+            'Black',
+        ],
+    ];
+
+    $variants = [
+        [
+            'id' => 1,
+            'sku' => 'SMBLK',
+            'values' => [
+                'Size' => 'Small',
+                'Colour' => 'Black',
+            ],
+        ],
+    ];
+
+    $result = MapVariantsToProductOptions::map($optionValues, $variants);
+
+    expect($result)->toHaveCount(4);
+})->group('momo');
diff --git a/tests/core/Unit/Actions/Carts/AddOrUpdatePurchasableTest.php b/tests/core/Unit/Actions/Carts/AddOrUpdatePurchasableTest.php
index ac6807c01d..dad27ad3f2 100644
--- a/tests/core/Unit/Actions/Carts/AddOrUpdatePurchasableTest.php
+++ b/tests/core/Unit/Actions/Carts/AddOrUpdatePurchasableTest.php
@@ -1,6 +1,7 @@
 <?php
 
 uses(\Lunar\Tests\Core\TestCase::class);
+
 use Lunar\Actions\Carts\AddOrUpdatePurchasable;
 use Lunar\Exceptions\InvalidCartLineQuantityException;
 use Lunar\Models\Cart;
@@ -22,7 +23,7 @@
 
     Price::factory()->create([
         'price' => 100,
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasable),
         'priceable_id' => $purchasable->id,
@@ -48,7 +49,7 @@
 
     Price::factory()->create([
         'price' => 100,
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasable),
         'priceable_id' => $purchasable->id,
@@ -74,7 +75,7 @@
 
     Price::factory()->create([
         'price' => 100,
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasable),
         'priceable_id' => $purchasable->id,
diff --git a/tests/core/Unit/Actions/Carts/CalculateLineTest.php b/tests/core/Unit/Actions/Carts/CalculateLineTest.php
index a0c14d5f85..2850b3d6ea 100644
--- a/tests/core/Unit/Actions/Carts/CalculateLineTest.php
+++ b/tests/core/Unit/Actions/Carts/CalculateLineTest.php
@@ -1,6 +1,7 @@
 <?php
 
 uses(\Lunar\Tests\Core\TestCase::class);
+
 use Lunar\Actions\Carts\CalculateLine;
 use Lunar\Base\ValueObjects\Cart\TaxBreakdown;
 use Lunar\DataTypes\Price as DataTypesPrice;
@@ -43,7 +44,7 @@
     Price::factory()->create([
         'price' => 100,
         'currency_id' => $currency->id,
-        'tier' => 1,
+        'quantity_break' => 1,
         'priceable_type' => get_class($purchasable),
         'priceable_id' => $purchasable->id,
     ]);
@@ -107,7 +108,7 @@
 
     Price::factory()->create([
         'price' => 100,
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasable),
         'priceable_id' => $purchasable->id,
@@ -172,7 +173,7 @@
 
     Price::factory()->create([
         'price' => 1000,
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasable),
         'priceable_id' => $purchasable->id,
@@ -237,7 +238,7 @@
 
     Price::factory()->create([
         'price' => 100,
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasable),
         'priceable_id' => $purchasable->id,
@@ -304,7 +305,7 @@ function check_for_know_rounding_error_on_unit_price_with_unit_quantity_of_one()
     Price::factory()->create([
         'price' => 912, //Known failing value
         'currency_id' => $currency->id,
-        'tier' => 1,
+        'quantity_break' => 1,
         'priceable_type' => get_class($purchasable),
         'priceable_id' => $purchasable->id,
     ]);
diff --git a/tests/core/Unit/Actions/Carts/CreateOrderTest.php b/tests/core/Unit/Actions/Carts/CreateOrderTest.php
index eae4cde2a1..9c5b02dad3 100644
--- a/tests/core/Unit/Actions/Carts/CreateOrderTest.php
+++ b/tests/core/Unit/Actions/Carts/CreateOrderTest.php
@@ -1,6 +1,7 @@
 <?php
 
 uses(\Lunar\Tests\Core\TestCase::class);
+
 use Lunar\Actions\Carts\CreateOrder;
 use Lunar\DataTypes\Price as PriceDataType;
 use Lunar\DataTypes\ShippingOption;
@@ -158,7 +159,7 @@ function can_update_draft_order()
 
     Price::factory()->create([
         'price' => 100,
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasable),
         'priceable_id' => $purchasable->id,
@@ -290,7 +291,7 @@ function can_update_draft_order()
 
     Price::factory()->create([
         'price' => 100,
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasable),
         'priceable_id' => $purchasable->id,
diff --git a/tests/core/Unit/Actions/Carts/GenerateFingerprintTest.php b/tests/core/Unit/Actions/Carts/GenerateFingerprintTest.php
index 69f033a6bc..620acf03a5 100644
--- a/tests/core/Unit/Actions/Carts/GenerateFingerprintTest.php
+++ b/tests/core/Unit/Actions/Carts/GenerateFingerprintTest.php
@@ -1,6 +1,7 @@
 <?php
 
 uses(\Lunar\Tests\Core\TestCase::class);
+
 use Lunar\Actions\Carts\GenerateFingerprint;
 use Lunar\Models\Cart;
 use Lunar\Models\Channel;
@@ -27,7 +28,7 @@
 
     Price::factory()->create([
         'price' => 100,
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($variant),
         'priceable_id' => $variant->id,
diff --git a/tests/core/Unit/Actions/Carts/GetExistingCartLineTest.php b/tests/core/Unit/Actions/Carts/GetExistingCartLineTest.php
index dc24e98c06..07c86bb23d 100644
--- a/tests/core/Unit/Actions/Carts/GetExistingCartLineTest.php
+++ b/tests/core/Unit/Actions/Carts/GetExistingCartLineTest.php
@@ -1,6 +1,7 @@
 <?php
 
 uses(\Lunar\Tests\Core\TestCase::class);
+
 use Lunar\Actions\Carts\GetExistingCartLine;
 use Lunar\Models\Cart;
 use Lunar\Models\CartLine;
@@ -21,7 +22,7 @@
 
     Price::factory()->create([
         'price' => 100,
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasable),
         'priceable_id' => $purchasable->id,
@@ -53,7 +54,7 @@
 
     Price::factory()->create([
         'price' => 100,
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasable),
         'priceable_id' => $purchasable->id,
diff --git a/tests/core/Unit/Actions/Carts/MergeCartTest.php b/tests/core/Unit/Actions/Carts/MergeCartTest.php
index 82d12d10a0..8e33a24ddc 100644
--- a/tests/core/Unit/Actions/Carts/MergeCartTest.php
+++ b/tests/core/Unit/Actions/Carts/MergeCartTest.php
@@ -1,6 +1,7 @@
 <?php
 
 uses(\Lunar\Tests\Core\TestCase::class);
+
 use Lunar\Actions\Carts\MergeCart;
 use Lunar\Models\Cart;
 use Lunar\Models\Currency;
@@ -50,7 +51,7 @@
 
     Price::factory()->create([
         'price' => 100,
-        'tier' => 1,
+        'quantity_break' => 1,
         'priceable_type' => get_class($purchasableA),
         'priceable_id' => $purchasableA->id,
     ]);
@@ -121,7 +122,7 @@
 
     Price::factory()->create([
         'price' => 100,
-        'tier' => 1,
+        'quantity_break' => 1,
         'priceable_type' => get_class($purchasableA),
         'priceable_id' => $purchasableA->id,
     ]);
diff --git a/tests/core/Unit/Actions/Carts/RemovePurchasableTest.php b/tests/core/Unit/Actions/Carts/RemovePurchasableTest.php
index e8a88c8051..53361a4140 100644
--- a/tests/core/Unit/Actions/Carts/RemovePurchasableTest.php
+++ b/tests/core/Unit/Actions/Carts/RemovePurchasableTest.php
@@ -1,6 +1,7 @@
 <?php
 
 uses(\Lunar\Tests\Core\TestCase::class);
+
 use Lunar\Actions\Carts\RemovePurchasable;
 use Lunar\Models\Cart;
 use Lunar\Models\Currency;
@@ -20,7 +21,7 @@
 
     Price::factory()->create([
         'price' => 100,
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasable),
         'priceable_id' => $purchasable->id,
diff --git a/tests/core/Unit/Actions/Carts/UpdateCartLineTest.php b/tests/core/Unit/Actions/Carts/UpdateCartLineTest.php
index e414c1efca..b94237ba4a 100644
--- a/tests/core/Unit/Actions/Carts/UpdateCartLineTest.php
+++ b/tests/core/Unit/Actions/Carts/UpdateCartLineTest.php
@@ -1,6 +1,7 @@
 <?php
 
 uses(\Lunar\Tests\Core\TestCase::class);
+
 use Lunar\Actions\Carts\UpdateCartLine;
 use Lunar\Models\Cart;
 use Lunar\Models\CartLine;
@@ -21,7 +22,7 @@
 
     Price::factory()->create([
         'price' => 100,
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasable),
         'priceable_id' => $purchasable->id,
diff --git a/tests/core/Unit/Actions/Collections/SortProductsByPriceTest.php b/tests/core/Unit/Actions/Collections/SortProductsByPriceTest.php
index 01feed396a..666312157c 100644
--- a/tests/core/Unit/Actions/Collections/SortProductsByPriceTest.php
+++ b/tests/core/Unit/Actions/Collections/SortProductsByPriceTest.php
@@ -1,6 +1,7 @@
 <?php
 
 uses(\Lunar\Tests\Core\TestCase::class);
+
 use Lunar\Actions\Collections\SortProductsByPrice;
 use Lunar\Models\Collection;
 use Lunar\Models\Currency;
@@ -32,7 +33,7 @@
                 'priceable_type' => ProductVariant::class,
                 'priceable_id' => $variant->id,
                 'currency_id' => $currency->id,
-                'tier' => 1,
+                'quantity_break' => 1,
                 'price' => $prices[$index],
             ]);
         }
diff --git a/tests/core/Unit/Base/ShippingManifestTest.php b/tests/core/Unit/Base/ShippingManifestTest.php
index bafdc4a5b2..cc6001343e 100644
--- a/tests/core/Unit/Base/ShippingManifestTest.php
+++ b/tests/core/Unit/Base/ShippingManifestTest.php
@@ -1,6 +1,7 @@
 <?php
 
 uses(\Lunar\Tests\Core\TestCase::class);
+
 use Lunar\DataTypes\Price;
 use Lunar\DataTypes\ShippingOption;
 use Lunar\Facades\ShippingManifest;
@@ -42,7 +43,7 @@
 
     PriceModel::factory()->create([
         'price' => 100,
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasable),
         'priceable_id' => $purchasable->id,
diff --git a/tests/core/Unit/Base/Traits/CachesPropertiesTest.php b/tests/core/Unit/Base/Traits/CachesPropertiesTest.php
index 79ec4de087..77fb306e88 100644
--- a/tests/core/Unit/Base/Traits/CachesPropertiesTest.php
+++ b/tests/core/Unit/Base/Traits/CachesPropertiesTest.php
@@ -1,6 +1,7 @@
 <?php
 
 uses(\Lunar\Tests\Core\TestCase::class);
+
 use Lunar\DataTypes\Price as DataTypesPrice;
 use Lunar\Models\Cart;
 use Lunar\Models\Currency;
@@ -20,7 +21,7 @@
 
     Price::factory()->create([
         'price' => 100,
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasable),
         'priceable_id' => $purchasable->id,
diff --git a/tests/core/Unit/DiscountTypes/AmountOffTest.php b/tests/core/Unit/DiscountTypes/AmountOffTest.php
index 9186eebd6e..565a686a81 100644
--- a/tests/core/Unit/DiscountTypes/AmountOffTest.php
+++ b/tests/core/Unit/DiscountTypes/AmountOffTest.php
@@ -71,7 +71,7 @@
 
     Price::factory()->create([
         'price' => 1000, // £10
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasableA),
         'priceable_id' => $purchasableA->id,
@@ -85,7 +85,7 @@
 
     Price::factory()->create([
         'price' => 1000, // £10
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasableB),
         'priceable_id' => $purchasableB->id,
@@ -173,7 +173,7 @@
 
     Price::factory()->create([
         'price' => 1000, // £10
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasableA),
         'priceable_id' => $purchasableA->id,
@@ -187,7 +187,7 @@
 
     Price::factory()->create([
         'price' => 2000, // £20
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasableB),
         'priceable_id' => $purchasableB->id,
@@ -271,7 +271,7 @@
 
     Price::factory()->create([
         'price' => 1000, // £10
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasableA),
         'priceable_id' => $purchasableA->id,
@@ -285,7 +285,7 @@
 
     Price::factory()->create([
         'price' => 1000, // £10
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasableB),
         'priceable_id' => $purchasableB->id,
@@ -368,7 +368,7 @@
 
     Price::factory()->create([
         'price' => 1000, // £10
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasableA),
         'priceable_id' => $purchasableA->id,
@@ -382,7 +382,7 @@
 
     Price::factory()->create([
         'price' => 1000, // £10
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasableB),
         'priceable_id' => $purchasableB->id,
@@ -465,7 +465,7 @@
 
     Price::factory()->create([
         'price' => 1000, // £10
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasableA),
         'priceable_id' => $purchasableA->id,
@@ -479,7 +479,7 @@
 
     Price::factory()->create([
         'price' => 2000, // £20
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasableB),
         'priceable_id' => $purchasableB->id,
@@ -562,7 +562,7 @@
 
     Price::factory()->create([
         'price' => 1000, // £10
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasableA),
         'priceable_id' => $purchasableA->id,
@@ -576,7 +576,7 @@
 
     Price::factory()->create([
         'price' => 2000, // £20
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasableB),
         'priceable_id' => $purchasableB->id,
@@ -642,7 +642,7 @@
 
     Price::factory()->create([
         'price' => 1000, // £10
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasableA),
         'priceable_id' => $purchasableA->id,
@@ -707,7 +707,7 @@
 
     Price::factory()->create([
         'price' => 1000, // £10
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasableA),
         'priceable_id' => $purchasableA->id,
@@ -715,7 +715,7 @@
 
     Price::factory()->create([
         'price' => 1000, // £10
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasableB),
         'priceable_id' => $purchasableB->id,
@@ -723,7 +723,7 @@
 
     Price::factory()->create([
         'price' => 1000, // £10
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasableC),
         'priceable_id' => $purchasableC->id,
@@ -801,7 +801,7 @@
 
     Price::factory()->create([
         'price' => 1000,
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasable),
         'priceable_id' => $purchasable->id,
@@ -879,7 +879,7 @@
 
     Price::factory()->create([
         'price' => 1000,
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasable),
         'priceable_id' => $purchasable->id,
@@ -956,7 +956,7 @@
 
     Price::factory()->create([
         'price' => 1000, // £10
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasableA),
         'priceable_id' => $purchasableA->id,
@@ -1018,7 +1018,7 @@
 
     Price::factory()->create([
         'price' => 1000, // £10
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasableA),
         'priceable_id' => $purchasableA->id,
@@ -1080,7 +1080,7 @@
 
     Price::factory()->create([
         'price' => 1000, // £10
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasableA),
         'priceable_id' => $purchasableA->id,
@@ -1143,7 +1143,7 @@
 
     Price::factory()->create([
         'price' => 1000, // £10
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasableA),
         'priceable_id' => $purchasableA->id,
@@ -1205,7 +1205,7 @@
 
     Price::factory()->create([
         'price' => 1000, // £10
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasableA),
         'priceable_id' => $purchasableA->id,
@@ -1273,7 +1273,7 @@
 
     Price::factory()->create([
         'price' => 1000, // £10
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasableA),
         'priceable_id' => $purchasableA->id,
@@ -1335,7 +1335,7 @@
 
     Price::factory()->create([
         'price' => 1000, // £10
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasableA),
         'priceable_id' => $purchasableA->id,
@@ -1420,7 +1420,7 @@
 
     Price::factory()->create([
         'price' => 1000, // £10
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasableA),
         'priceable_id' => $purchasableA->id,
@@ -1498,7 +1498,7 @@
 
     Price::factory()->create([
         'price' => 1000, // £10
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasableA),
         'priceable_id' => $purchasableA->id,
@@ -1570,7 +1570,7 @@
 
     Price::factory()->create([
         'price' => 15, // £0.15
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasableA),
         'priceable_id' => $purchasableA->id,
@@ -1578,7 +1578,7 @@
 
     Price::factory()->create([
         'price' => 20, // £0.20
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasableB),
         'priceable_id' => $purchasableB->id,
@@ -1586,7 +1586,7 @@
 
     Price::factory()->create([
         'price' => 40, // £0.40
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasableC),
         'priceable_id' => $purchasableC->id,
@@ -1594,7 +1594,7 @@
 
     Price::factory()->create([
         'price' => 40, // £0.40
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasableD),
         'priceable_id' => $purchasableD->id,
@@ -1602,7 +1602,7 @@
 
     Price::factory()->create([
         'price' => 40, // £0.40
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasableE),
         'priceable_id' => $purchasableE->id,
@@ -1702,7 +1702,7 @@
 
     Price::factory()->create([
         'price' => 1000, // £10
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasableA),
         'priceable_id' => $purchasableA->id,
diff --git a/tests/core/Unit/DiscountTypes/BuyXGetYTest.php b/tests/core/Unit/DiscountTypes/BuyXGetYTest.php
index 50ad29953b..e887f34236 100644
--- a/tests/core/Unit/DiscountTypes/BuyXGetYTest.php
+++ b/tests/core/Unit/DiscountTypes/BuyXGetYTest.php
@@ -1,6 +1,7 @@
 <?php
 
 uses(\Lunar\Tests\Core\TestCase::class);
+
 use Lunar\DiscountTypes\AmountOff;
 use Lunar\DiscountTypes\BuyXGetY;
 use Lunar\Models\Cart;
@@ -145,7 +146,7 @@
 
     Price::factory()->create([
         'price' => 1000, // £10
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasableA),
         'priceable_id' => $purchasableA->id,
@@ -159,7 +160,7 @@
 
     Price::factory()->create([
         'price' => 1000, // £10
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasableB),
         'priceable_id' => $purchasableB->id,
@@ -244,7 +245,7 @@
 
     Price::factory()->create([
         'price' => 1000, // £10
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasableA),
         'priceable_id' => $purchasableA->id,
@@ -252,7 +253,7 @@
 
     Price::factory()->create([
         'price' => 1000, // £10
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasableB),
         'priceable_id' => $purchasableB->id,
@@ -340,7 +341,7 @@
 
     Price::factory()->create([
         'price' => 1000, // £10
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasableA),
         'priceable_id' => $purchasableA->id,
@@ -348,7 +349,7 @@
 
     Price::factory()->create([
         'price' => 1000, // £10
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasableB),
         'priceable_id' => $purchasableB->id,
@@ -452,7 +453,7 @@
 
     Price::factory()->create([
         'price' => 1000, // £10
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasableA),
         'priceable_id' => $purchasableA->id,
@@ -460,7 +461,7 @@
 
     Price::factory()->create([
         'price' => 500, // £5
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasableB),
         'priceable_id' => $purchasableB->id,
@@ -587,7 +588,7 @@
 
     Price::factory()->create([
         'price' => 1064, // $10.64
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasableA),
         'priceable_id' => $purchasableA->id,
@@ -595,7 +596,7 @@
 
     Price::factory()->create([
         'price' => 2280, // $22.80
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasableB),
         'priceable_id' => $purchasableB->id,
@@ -724,7 +725,7 @@
 
     Price::factory()->create([
         'price' => 1000, // £10
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasableA),
         'priceable_id' => $purchasableA->id,
@@ -732,7 +733,7 @@
 
     Price::factory()->create([
         'price' => 500, // £5
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasableB),
         'priceable_id' => $purchasableB->id,
@@ -740,7 +741,7 @@
 
     Price::factory()->create([
         'price' => 200, // £2
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasableC),
         'priceable_id' => $purchasableC->id,
diff --git a/tests/core/Unit/Jobs/Collections/UpdateProductPositionsTest.php b/tests/core/Unit/Jobs/Collections/UpdateProductPositionsTest.php
index 4856197961..74461f262a 100644
--- a/tests/core/Unit/Jobs/Collections/UpdateProductPositionsTest.php
+++ b/tests/core/Unit/Jobs/Collections/UpdateProductPositionsTest.php
@@ -1,6 +1,7 @@
 <?php
 
 uses(\Lunar\Tests\Core\TestCase::class);
+
 use Lunar\Jobs\Collections\UpdateProductPositions;
 use Lunar\Models\Collection;
 use Lunar\Models\Currency;
@@ -32,7 +33,7 @@
                 'priceable_type' => ProductVariant::class,
                 'priceable_id' => $variant->id,
                 'currency_id' => $currency->id,
-                'tier' => 1,
+                'quantity_break' => 1,
                 'price' => $prices[$index],
             ]);
         }
diff --git a/tests/core/Unit/Managers/DiscountManagerTest.php b/tests/core/Unit/Managers/DiscountManagerTest.php
index 60b2ceb8ec..ad825cf688 100644
--- a/tests/core/Unit/Managers/DiscountManagerTest.php
+++ b/tests/core/Unit/Managers/DiscountManagerTest.php
@@ -298,7 +298,7 @@
 
     Price::factory()->create([
         'price' => 1000, // £10
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasableA),
         'priceable_id' => $purchasableA->id,
diff --git a/tests/core/Unit/Managers/PricingManagerTest.php b/tests/core/Unit/Managers/PricingManagerTest.php
index 19b214050e..cf5451778d 100644
--- a/tests/core/Unit/Managers/PricingManagerTest.php
+++ b/tests/core/Unit/Managers/PricingManagerTest.php
@@ -40,7 +40,7 @@
         'priceable_type' => ProductVariant::class,
         'priceable_id' => $variant->id,
         'currency_id' => $currency->id,
-        'tier' => 1,
+        'quantity_break' => 1,
     ]);
 
     Price::factory()->create([
@@ -48,7 +48,7 @@
         'priceable_type' => ProductVariant::class,
         'priceable_id' => $variant->id,
         'currency_id' => $currency->id,
-        'tier' => 10,
+        'quantity_break' => 10,
     ]);
 
     Price::factory()->create([
@@ -56,7 +56,7 @@
         'priceable_type' => ProductVariant::class,
         'priceable_id' => $variant->id,
         'currency_id' => $currency->id,
-        'tier' => 1,
+        'quantity_break' => 1,
         'customer_group_id' => CustomerGroup::factory(),
     ]);
 
@@ -64,7 +64,7 @@
 
     expect($pricing)->toBeInstanceOf(PricingResponse::class);
     expect($pricing->customerGroupPrices)->toHaveCount(0);
-    expect($pricing->tiered)->toHaveCount(1);
+    expect($pricing->quantityBreaks)->toHaveCount(1);
     expect($pricing->base->id)->toEqual($base->id);
     expect($pricing->matched->id)->toEqual($base->id);
 });
@@ -90,7 +90,7 @@
         'priceable_type' => ProductVariant::class,
         'priceable_id' => $variant->id,
         'currency_id' => $currency->id,
-        'tier' => 1,
+        'quantity_break' => 1,
     ]);
 
     $pricing = $manager->for($variant)->get();
@@ -123,7 +123,7 @@
         'priceable_type' => ProductVariant::class,
         'priceable_id' => $variant->id,
         'currency_id' => $currency->id,
-        'tier' => 1,
+        'quantity_break' => 1,
     ]);
 
     $customerGroupPrice = Price::factory()->create([
@@ -131,7 +131,7 @@
         'priceable_type' => ProductVariant::class,
         'priceable_id' => $variant->id,
         'currency_id' => $currency->id,
-        'tier' => 1,
+        'quantity_break' => 1,
         'customer_group_id' => $customerGroups->first()->id,
     ]);
 
@@ -152,7 +152,7 @@
     expect($pricing->matched->id)->toEqual($base->id);
 });
 
-test('can fetch tiered price', function () {
+test('can fetch quantity break price', function () {
     $manager = new PricingManager();
 
     $currency = Currency::factory()->create([
@@ -173,31 +173,31 @@
         'priceable_type' => ProductVariant::class,
         'priceable_id' => $variant->id,
         'currency_id' => $currency->id,
-        'tier' => 1,
+        'quantity_break' => 1,
     ]);
 
-    $tiered10 = Price::factory()->create([
+    $break10 = Price::factory()->create([
         'price' => 90,
         'priceable_type' => ProductVariant::class,
         'priceable_id' => $variant->id,
         'currency_id' => $currency->id,
-        'tier' => 10,
+        'quantity_break' => 10,
     ]);
 
-    $tiered20 = Price::factory()->create([
+    $break20 = Price::factory()->create([
         'price' => 80,
         'priceable_type' => ProductVariant::class,
         'priceable_id' => $variant->id,
         'currency_id' => $currency->id,
-        'tier' => 20,
+        'quantity_break' => 20,
     ]);
 
-    $tiered30 = Price::factory()->create([
+    $break30 = Price::factory()->create([
         'price' => 70,
         'priceable_type' => ProductVariant::class,
         'priceable_id' => $variant->id,
         'currency_id' => $currency->id,
-        'tier' => 30,
+        'quantity_break' => 30,
     ]);
 
     $pricing = $manager->qty(1)->for($variant)->get();
@@ -213,32 +213,32 @@
     $pricing = $manager->qty(10)->for($variant)->get();
 
     expect($pricing->base->id)->toEqual($base->id);
-    expect($pricing->matched->id)->toEqual($tiered10->id);
+    expect($pricing->matched->id)->toEqual($break10->id);
 
     $pricing = $manager->qty(15)->for($variant)->get();
 
     expect($pricing->base->id)->toEqual($base->id);
-    expect($pricing->matched->id)->toEqual($tiered10->id);
+    expect($pricing->matched->id)->toEqual($break10->id);
 
     $pricing = $manager->qty(20)->for($variant)->get();
 
     expect($pricing->base->id)->toEqual($base->id);
-    expect($pricing->matched->id)->toEqual($tiered20->id);
+    expect($pricing->matched->id)->toEqual($break20->id);
 
     $pricing = $manager->qty(25)->for($variant)->get();
 
     expect($pricing->base->id)->toEqual($base->id);
-    expect($pricing->matched->id)->toEqual($tiered20->id);
+    expect($pricing->matched->id)->toEqual($break20->id);
 
     $pricing = $manager->qty(30)->for($variant)->get();
 
     expect($pricing->base->id)->toEqual($base->id);
-    expect($pricing->matched->id)->toEqual($tiered30->id);
+    expect($pricing->matched->id)->toEqual($break30->id);
 
     $pricing = $manager->qty(100)->for($variant)->get();
 
     expect($pricing->base->id)->toEqual($base->id);
-    expect($pricing->matched->id)->toEqual($tiered30->id);
+    expect($pricing->matched->id)->toEqual($break30->id);
 });
 
 test('can match based on currency', function () {
@@ -267,7 +267,7 @@
         'priceable_type' => ProductVariant::class,
         'priceable_id' => $variant->id,
         'currency_id' => $defaultCurrency->id,
-        'tier' => 1,
+        'quantity_break' => 1,
     ]);
 
     $additional = Price::factory()->create([
@@ -275,7 +275,7 @@
         'priceable_type' => ProductVariant::class,
         'priceable_id' => $variant->id,
         'currency_id' => $secondCurrency->id,
-        'tier' => 1,
+        'quantity_break' => 1,
     ]);
 
     $pricing = $manager->qty(1)->for($variant)->get();
@@ -318,7 +318,7 @@ function can_fetch_correct_price_for_user()
         'priceable_type' => ProductVariant::class,
         'priceable_id' => $variant->id,
         'currency_id' => $defaultCurrency->id,
-        'tier' => 1,
+        'quantity_break' => 1,
     ]);
 
     $groupPrice = Price::factory()->create([
@@ -326,7 +326,7 @@ function can_fetch_correct_price_for_user()
         'priceable_type' => ProductVariant::class,
         'priceable_id' => $variant->id,
         'currency_id' => $defaultCurrency->id,
-        'tier' => 1,
+        'quantity_break' => 1,
         'customer_group_id' => $group->id,
     ]);
 
@@ -372,7 +372,7 @@ function can_fetch_correct_price_for_user()
         'priceable_type' => ProductVariant::class,
         'priceable_id' => $variant->id,
         'currency_id' => $currency->id,
-        'tier' => 1,
+        'quantity_break' => 1,
     ]);
 
     $pricing = $manager->for($variant)->get();
diff --git a/tests/core/Unit/Models/AttributeTest.php b/tests/core/Unit/Models/AttributeTest.php
index 783dee1b2c..f21d757d90 100644
--- a/tests/core/Unit/Models/AttributeTest.php
+++ b/tests/core/Unit/Models/AttributeTest.php
@@ -20,6 +20,9 @@
             'name' => [
                 'en' => 'Meta Description',
             ],
+            'description' => [
+                'en' => 'Meta Description',
+            ],
             'handle' => 'meta_description',
             'section' => 'product_variant',
             'type' => \Lunar\FieldTypes\Text::class,
diff --git a/tests/core/Unit/Models/CartTest.php b/tests/core/Unit/Models/CartTest.php
index c76645566d..58ad5642c7 100644
--- a/tests/core/Unit/Models/CartTest.php
+++ b/tests/core/Unit/Models/CartTest.php
@@ -398,7 +398,7 @@
 
     Price::factory()->create([
         'price' => 100,
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasable),
         'priceable_id' => $purchasable->id,
@@ -419,7 +419,7 @@
 
     Price::factory()->create([
         'price' => 158,
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasable),
         'priceable_id' => $purchasable->id,
@@ -468,7 +468,7 @@
 
     Price::factory()->create([
         'price' => 100,
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasable),
         'priceable_id' => $purchasable->id,
@@ -489,7 +489,7 @@
 
     Price::factory()->create([
         'price' => 158,
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasable),
         'priceable_id' => $purchasable->id,
@@ -531,7 +531,7 @@
 
     Price::factory()->create([
         'price' => 100,
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasable),
         'priceable_id' => $purchasable->id,
@@ -555,7 +555,7 @@
 
     Price::factory()->create([
         'price' => 100,
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasable),
         'priceable_id' => $purchasable->id,
@@ -583,7 +583,7 @@
 
     Price::factory()->create([
         'price' => 100,
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasable),
         'priceable_id' => $purchasable->id,
@@ -607,7 +607,7 @@
 
     Price::factory()->create([
         'price' => 100,
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasable),
         'priceable_id' => $purchasable->id,
@@ -684,7 +684,7 @@
 
     Price::factory()->create([
         'price' => 100,
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasable),
         'priceable_id' => $purchasable->id,
@@ -773,7 +773,7 @@
 
     Price::factory()->create([
         'price' => 100,
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($variant),
         'priceable_id' => $variant->id,
@@ -812,7 +812,7 @@
 
     Price::factory()->create([
         'price' => 100,
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($variant),
         'priceable_id' => $variant->id,
@@ -871,7 +871,7 @@
 
     Price::factory()->create([
         'price' => 100,
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasable),
         'priceable_id' => $purchasable->id,
@@ -932,7 +932,7 @@
 
     Price::factory()->create([
         'price' => 100,
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasable),
         'priceable_id' => $purchasable->id,
diff --git a/tests/core/Unit/Models/PriceTest.php b/tests/core/Unit/Models/PriceTest.php
index 9bd7567955..23aaeeed82 100644
--- a/tests/core/Unit/Models/PriceTest.php
+++ b/tests/core/Unit/Models/PriceTest.php
@@ -1,6 +1,7 @@
 <?php
 
 uses(\Lunar\Tests\Core\TestCase::class);
+
 use Illuminate\Support\Facades\Config;
 use Lunar\DataTypes\Price as DataTypesPrice;
 use Lunar\Models\Currency;
@@ -22,7 +23,7 @@
         'priceable_id' => $variant->id,
         'priceable_type' => ProductVariant::class,
         'price' => 123,
-        'tier' => 1,
+        'quantity_break' => 1,
     ];
 
     Price::factory()->create($data);
@@ -42,7 +43,7 @@
         'priceable_id' => $variant->id,
         'priceable_type' => ProductVariant::class,
         'price' => 123,
-        'tier' => 1,
+        'quantity_break' => 1,
     ]);
 
     expect($price->price)->toBeInstanceOf(DataTypesPrice::class);
@@ -63,7 +64,7 @@ function can_handle_non_int_values()
         'priceable_id' => $variant->id,
         'priceable_type' => ProductVariant::class,
         'price' => 12.99,
-        'tier' => 1,
+        'quantity_break' => 1,
     ]);
 
     expect($price->price->value)->toEqual(1299);
@@ -80,7 +81,7 @@ function can_handle_non_int_values()
         'priceable_id' => $variant->id,
         'priceable_type' => ProductVariant::class,
         'price' => 12.995,
-        'tier' => 1,
+        'quantity_break' => 1,
     ]);
 
     expect($price->price->value)->toEqual(12995);
@@ -92,7 +93,7 @@ function can_handle_non_int_values()
         'priceable_id' => $variant->id,
         'priceable_type' => ProductVariant::class,
         'price' => 1299,
-        'tier' => 1,
+        'quantity_break' => 1,
     ]);
 
     expect($price->price->value)->toEqual(1299);
@@ -109,7 +110,7 @@ function can_handle_non_int_values()
         'priceable_id' => $variant->id,
         'priceable_type' => ProductVariant::class,
         'price' => '1,250.950',
-        'tier' => 1,
+        'quantity_break' => 1,
     ]);
 
     expect($price->price->value)->toEqual(1250950);
@@ -121,7 +122,7 @@ function can_handle_non_int_values()
         'priceable_id' => $variant->id,
         'priceable_type' => ProductVariant::class,
         'price' => '1,250.955',
-        'tier' => 1,
+        'quantity_break' => 1,
     ]);
 
     expect($price->price->value)->toEqual(1250955);
@@ -143,7 +144,7 @@ function can_handle_non_int_values()
         'priceable_type' => ProductVariant::class,
         'price' => 12.99,
         'compare_price' => 13.99,
-        'tier' => 1,
+        'quantity_break' => 1,
     ]);
 
     expect($price->compare_price)->toBeInstanceOf(DataTypesPrice::class);
@@ -176,7 +177,7 @@ function can_handle_non_int_values()
         'priceable_id' => $variant->id,
         'priceable_type' => ProductVariant::class,
         'price' => 123,
-        'tier' => 1,
+        'quantity_break' => 1,
     ]);
 
     Price::factory()->create([
@@ -184,7 +185,7 @@ function can_handle_non_int_values()
         'priceable_id' => $variant->id,
         'priceable_type' => ProductVariant::class,
         'price' => 99,
-        'tier' => 1,
+        'quantity_break' => 1,
     ]);
 
     Price::factory()->create([
@@ -192,7 +193,7 @@ function can_handle_non_int_values()
         'priceable_id' => $variant->id,
         'priceable_type' => ProductVariant::class,
         'price' => 101,
-        'tier' => 5,
+        'quantity_break' => 5,
     ]);
 
     Price::factory()->create([
@@ -201,7 +202,7 @@ function can_handle_non_int_values()
         'priceable_id' => $variant->id,
         'priceable_type' => ProductVariant::class,
         'price' => 75,
-        'tier' => 1,
+        'quantity_break' => 1,
     ]);
 
     // Check we get the default currency price
@@ -241,7 +242,7 @@ function can_handle_non_int_values()
         'priceable_id' => $variant->id,
         'priceable_type' => ProductVariant::class,
         'price' => 999,
-        'tier' => 1,
+        'quantity_break' => 1,
     ]);
 
     expect($price->priceExTax()->value)->toEqual(833);
@@ -263,7 +264,7 @@ function can_handle_non_int_values()
         'priceable_id' => $variant->id,
         'priceable_type' => ProductVariant::class,
         'price' => 833,
-        'tier' => 1,
+        'quantity_break' => 1,
     ]);
 
     expect($price->priceIncTax()->value)->toEqual(1000);
diff --git a/tests/core/Unit/Models/ProductOptionTest.php b/tests/core/Unit/Models/ProductOptionTest.php
index 88c7fa504e..45577be4fe 100644
--- a/tests/core/Unit/Models/ProductOptionTest.php
+++ b/tests/core/Unit/Models/ProductOptionTest.php
@@ -1,6 +1,7 @@
 <?php
 
 uses(\Lunar\Tests\Core\TestCase::class);
+
 use Illuminate\Database\QueryException;
 use Illuminate\Support\Str;
 use Lunar\Models\ProductOption;
@@ -15,7 +16,6 @@
         'id' => $productOption->id,
         'name' => json_encode($productOption->name),
         'handle' => $productOption->handle,
-        'position' => $productOption->position,
     ]);
 
     $this->assertDatabaseCount((new ProductOption)->getTable(), 1);
@@ -46,25 +46,6 @@
     $this->assertDatabaseCount((new ProductOption)->getTable(), 2);
 });
 
-test('can update all product option positions', function () {
-    $productOptions = ProductOption::factory(10)->create()->each(function ($productOption) {
-        $productOption->update([
-            'position' => $productOption->id,
-        ]);
-    });
-
-    expect($productOptions->pluck('position')->toArray())->toEqual(range(1, 10));
-
-    $position = 10;
-    foreach ($productOptions as $productOption) {
-        $productOption->position = $position;
-        $productOption->save();
-        $position--;
-    }
-
-    expect($productOptions->pluck('position')->toArray())->toEqual(array_reverse(range(1, 10)));
-});
-
 test('can delete product option', function () {
     $productOption = ProductOption::factory()->create();
     $this->assertDatabaseCount((new ProductOption)->getTable(), 1);
diff --git a/tests/core/Unit/Models/ProductTest.php b/tests/core/Unit/Models/ProductTest.php
index d2b4e684a3..737a8e119b 100644
--- a/tests/core/Unit/Models/ProductTest.php
+++ b/tests/core/Unit/Models/ProductTest.php
@@ -1,6 +1,7 @@
 <?php
 
 uses(\Lunar\Tests\Core\TestCase::class);
+
 use Illuminate\Database\Eloquent\Collection as EloquentCollection;
 use Lunar\Facades\DB;
 use Lunar\Models\Brand;
@@ -34,6 +35,32 @@
     expect($product->attribute_data)->toEqual($attribute_data);
 });
 
+test('can fetch product options', function () {
+    $attribute_data = collect([
+        'meta_title' => new \Lunar\FieldTypes\Text('I like cake'),
+        'pack_qty' => new \Lunar\FieldTypes\Number(12345),
+        'description' => new \Lunar\FieldTypes\TranslatedText(collect([
+            'en' => new \Lunar\FieldTypes\Text('Blue'),
+            'fr' => new \Lunar\FieldTypes\Text('Bleu'),
+        ])),
+    ]);
+
+    $product = Product::factory()
+        ->for(ProductType::factory())
+        ->create([
+            'attribute_data' => $attribute_data,
+        ]);
+
+    $productOptions = \Lunar\Models\ProductOption::factory(2)->create();
+
+    foreach ($productOptions as $index => $productOption) {
+        $product->productOptions()->attach($productOption, ['position' => $index + 1]);
+    }
+
+    expect($product->refresh()->productOptions)->toHaveCount(2);
+
+})->group('momo');
+
 test('can fetch using status scope', function () {
     $attribute_data = collect([
         'meta_title' => new \Lunar\FieldTypes\Text('I like cake'),
diff --git a/tests/core/Unit/Models/ProductVariantTest.php b/tests/core/Unit/Models/ProductVariantTest.php
index 25e36e3c36..2b4ccaca64 100644
--- a/tests/core/Unit/Models/ProductVariantTest.php
+++ b/tests/core/Unit/Models/ProductVariantTest.php
@@ -1,6 +1,7 @@
 <?php
 
 uses(\Lunar\Tests\Core\TestCase::class);
+
 use Illuminate\Support\Facades\Config;
 use Lunar\Exceptions\MissingCurrencyPriceException;
 use Lunar\Facades\Pricing;
@@ -57,30 +58,30 @@
         [
             'price' => 100,
             'currency_id' => $currency->id,
-            'tier' => 1,
+            'quantity_break' => 1,
         ],
         [
             'price' => 90,
             'currency_id' => $currency->id,
             'customer_group_id' => $groupA->id,
-            'tier' => 1,
+            'quantity_break' => 1,
         ],
         [
             'price' => 80,
             'currency_id' => $currency->id,
             'customer_group_id' => $groupB->id,
-            'tier' => 1,
+            'quantity_break' => 1,
         ],
         [
             'price' => 30,
             'currency_id' => $currency->id,
             'customer_group_id' => $groupB->id,
-            'tier' => 5,
+            'quantity_break' => 5,
         ],
         [
             'price' => 60,
             'currency_id' => $currency->id,
-            'tier' => 5,
+            'quantity_break' => 5,
         ],
     ]);
 
@@ -118,12 +119,12 @@
         [
             'price' => 100,
             'currency_id' => $currencyA->id,
-            'tier' => 1,
+            'quantity_break' => 1,
         ],
         [
             'price' => 200,
             'currency_id' => $currencyB->id,
-            'tier' => 1,
+            'quantity_break' => 1,
         ],
     ]);
 
@@ -209,21 +210,21 @@
     Price::factory()->create([
         'price' => 10000,
         'currency_id' => $currency->id,
-        'tier' => 1,
+        'quantity_break' => 1,
         'priceable_type' => ProductVariant::class,
         'priceable_id' => $genericProductVariant->id,
     ]);
     Price::factory()->create([
         'price' => 8000,
         'currency_id' => $currency->id,
-        'tier' => 10,
+        'quantity_break' => 10,
         'priceable_type' => ProductVariant::class,
         'priceable_id' => $genericProductVariant->id,
     ]);
     Price::factory()->create([
         'price' => 400,
         'currency_id' => $currency->id,
-        'tier' => 1,
+        'quantity_break' => 1,
         'priceable_type' => ProductVariant::class,
         'priceable_id' => $foodProductVariant->id,
     ]);
diff --git a/tests/core/Unit/Pipelines/Cart/ApplyShippingTest.php b/tests/core/Unit/Pipelines/Cart/ApplyShippingTest.php
index 6181832e4d..12a60bbd72 100644
--- a/tests/core/Unit/Pipelines/Cart/ApplyShippingTest.php
+++ b/tests/core/Unit/Pipelines/Cart/ApplyShippingTest.php
@@ -1,6 +1,7 @@
 <?php
 
 uses(\Lunar\Tests\Core\TestCase::class);
+
 use Lunar\DataTypes\Price as PriceDataType;
 use Lunar\DataTypes\ShippingOption;
 use Lunar\Facades\ShippingManifest;
@@ -27,7 +28,7 @@
 
     Price::factory()->create([
         'price' => 100,
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasable),
         'priceable_id' => $purchasable->id,
@@ -107,7 +108,7 @@
 
     Price::factory()->create([
         'price' => 100,
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasable),
         'priceable_id' => $purchasable->id,
diff --git a/tests/core/Unit/Pipelines/Cart/CalculateLinesTest.php b/tests/core/Unit/Pipelines/Cart/CalculateLinesTest.php
index 0c03648c3e..f8f2af2c89 100644
--- a/tests/core/Unit/Pipelines/Cart/CalculateLinesTest.php
+++ b/tests/core/Unit/Pipelines/Cart/CalculateLinesTest.php
@@ -1,6 +1,7 @@
 <?php
 
 uses(\Lunar\Tests\Core\TestCase::class);
+
 use Lunar\Models\Cart;
 use Lunar\Models\Currency;
 use Lunar\Models\Price;
@@ -22,7 +23,7 @@
 
     Price::factory()->create([
         'price' => $incomingUnitPrice,
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasable),
         'priceable_id' => $purchasable->id,
diff --git a/tests/core/Unit/Pipelines/Order/Creation/CleanUpOrderLinesTest.php b/tests/core/Unit/Pipelines/Order/Creation/CleanUpOrderLinesTest.php
index fc215275a2..cbcb172e87 100644
--- a/tests/core/Unit/Pipelines/Order/Creation/CleanUpOrderLinesTest.php
+++ b/tests/core/Unit/Pipelines/Order/Creation/CleanUpOrderLinesTest.php
@@ -1,6 +1,7 @@
 <?php
 
 uses(\Lunar\Tests\Core\TestCase::class);
+
 use Lunar\DataTypes\Price;
 use Lunar\DataTypes\ShippingOption;
 use Lunar\Facades\ShippingManifest;
@@ -47,7 +48,7 @@
 
     \Lunar\Models\Price::factory()->create([
         'price' => 100,
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasable),
         'priceable_id' => $purchasable->id,
@@ -55,7 +56,7 @@
 
     \Lunar\Models\Price::factory()->create([
         'price' => 100,
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasableB),
         'priceable_id' => $purchasableB->id,
diff --git a/tests/core/Unit/Pipelines/Order/Creation/CreateOrderLinesTest.php b/tests/core/Unit/Pipelines/Order/Creation/CreateOrderLinesTest.php
index 48b4a6d9cf..641fffc4fa 100644
--- a/tests/core/Unit/Pipelines/Order/Creation/CreateOrderLinesTest.php
+++ b/tests/core/Unit/Pipelines/Order/Creation/CreateOrderLinesTest.php
@@ -1,6 +1,7 @@
 <?php
 
 uses(\Lunar\Tests\Core\TestCase::class);
+
 use Lunar\Models\Cart;
 use Lunar\Models\Currency;
 use Lunar\Models\Order;
@@ -21,7 +22,7 @@
 
     Price::factory()->create([
         'price' => 100,
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasable),
         'priceable_id' => $purchasable->id,
diff --git a/tests/core/Unit/Pipelines/Order/Creation/FillOrderFromCartTest.php b/tests/core/Unit/Pipelines/Order/Creation/FillOrderFromCartTest.php
index e1fbd62b31..340dda9d96 100644
--- a/tests/core/Unit/Pipelines/Order/Creation/FillOrderFromCartTest.php
+++ b/tests/core/Unit/Pipelines/Order/Creation/FillOrderFromCartTest.php
@@ -1,6 +1,7 @@
 <?php
 
 uses(\Lunar\Tests\Core\TestCase::class);
+
 use Lunar\Models\Cart;
 use Lunar\Models\Currency;
 use Lunar\Models\Order;
@@ -21,7 +22,7 @@
 
     Price::factory()->create([
         'price' => 100,
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasable),
         'priceable_id' => $purchasable->id,
diff --git a/tests/shipping/TestUtils.php b/tests/shipping/TestUtils.php
index 716f4d6d26..bc20010335 100644
--- a/tests/shipping/TestUtils.php
+++ b/tests/shipping/TestUtils.php
@@ -26,7 +26,7 @@ public function createCart($currency = null, $price = 100, $quantity = 1)
 
         Price::factory()->create([
             'price' => $price,
-            'tier' => 1,
+            'quantity_break' => 1,
             'currency_id' => $currency->id,
             'priceable_type' => get_class($purchasable),
             'priceable_id' => $purchasable->id,
diff --git a/tests/shipping/Unit/Drivers/ShippingMethods/CollectionTest.php b/tests/shipping/Unit/Drivers/ShippingMethods/CollectionTest.php
index 2ada975c9e..a16cb110c1 100644
--- a/tests/shipping/Unit/Drivers/ShippingMethods/CollectionTest.php
+++ b/tests/shipping/Unit/Drivers/ShippingMethods/CollectionTest.php
@@ -27,18 +27,22 @@
     ]);
 
     $shippingMethod = ShippingMethod::factory()->create([
-        'shipping_zone_id' => $shippingZone->id,
         'driver' => 'free-shipping',
         'data' => [],
     ]);
 
+    $shippingRate = \Lunar\Shipping\Models\ShippingRate::factory()->create([
+        'shipping_method_id' => $shippingMethod->id,
+        'shipping_zone_id' => $shippingZone->id,
+    ]);
+
     $cart = $this->createCart($currency, 500);
 
     $driver = new Collection();
 
     $request = new ShippingOptionRequest(
         cart: $cart,
-        shippingMethod: $shippingMethod
+        shippingRate: $shippingRate
     );
 
     $shippingOption = $driver->resolve($request);
diff --git a/tests/shipping/Unit/Drivers/ShippingMethods/FlatRateTest.php b/tests/shipping/Unit/Drivers/ShippingMethods/FlatRateTest.php
index f8a9f678d7..2b8d3619b5 100644
--- a/tests/shipping/Unit/Drivers/ShippingMethods/FlatRateTest.php
+++ b/tests/shipping/Unit/Drivers/ShippingMethods/FlatRateTest.php
@@ -27,7 +27,6 @@
     ]);
 
     $shippingMethod = ShippingMethod::factory()->create([
-        'shipping_zone_id' => $shippingZone->id,
         'driver' => 'flat-rate',
         'data' => [
             'minimum_spend' => [
@@ -36,10 +35,16 @@
         ],
     ]);
 
-    $shippingMethod->prices()->createMany([
+    $shippingRate = \Lunar\Shipping\Models\ShippingRate::factory()
+        ->create([
+            'shipping_method_id' => $shippingMethod->id,
+            'shipping_zone_id' => $shippingZone->id,
+        ]);
+
+    $shippingRate->prices()->createMany([
         [
             'price' => 600,
-            'tier' => 1,
+            'quantity_break' => 1,
             'currency_id' => $currency->id,
         ],
     ]);
@@ -50,7 +55,7 @@
 
     $request = new ShippingOptionRequest(
         cart: $cart,
-        shippingMethod: $shippingMethod
+        shippingRate: $shippingRate
     );
 
     $shippingOption = $driver->resolve($request);
diff --git a/tests/shipping/Unit/Drivers/ShippingMethods/FreeShippingTest.php b/tests/shipping/Unit/Drivers/ShippingMethods/FreeShippingTest.php
index 387a3c5c2c..2aeccd90e7 100644
--- a/tests/shipping/Unit/Drivers/ShippingMethods/FreeShippingTest.php
+++ b/tests/shipping/Unit/Drivers/ShippingMethods/FreeShippingTest.php
@@ -27,7 +27,6 @@
     ]);
 
     $shippingMethod = ShippingMethod::factory()->create([
-        'shipping_zone_id' => $shippingZone->id,
         'driver' => 'free-shipping',
         'data' => [
             'minimum_spend' => [
@@ -36,13 +35,19 @@
         ],
     ]);
 
+    $shippingRate = \Lunar\Shipping\Models\ShippingRate::factory()
+        ->create([
+            'shipping_method_id' => $shippingMethod->id,
+            'shipping_zone_id' => $shippingZone->id,
+        ]);
+
     $cart = $this->createCart($currency, 500);
 
     $driver = new FreeShipping();
 
     $request = new ShippingOptionRequest(
         cart: $cart,
-        shippingMethod: $shippingMethod
+        shippingRate: $shippingRate
     );
 
     $shippingOption = $driver->resolve($request);
@@ -64,7 +69,6 @@
     ]);
 
     $shippingMethod = ShippingMethod::factory()->create([
-        'shipping_zone_id' => $shippingZone->id,
         'driver' => 'free-shipping',
         'data' => [
             'minimum_spend' => [
@@ -73,13 +77,19 @@
         ],
     ]);
 
+    $shippingRate = \Lunar\Shipping\Models\ShippingRate::factory()
+        ->create([
+            'shipping_method_id' => $shippingMethod->id,
+            'shipping_zone_id' => $shippingZone->id,
+        ]);
+
     $cart = $this->createCart($currency, 50);
 
     $driver = new FreeShipping();
 
     $request = new ShippingOptionRequest(
         cart: $cart,
-        shippingMethod: $shippingMethod
+        shippingRate: $shippingRate
     );
 
     $shippingOption = $driver->resolve($request);
@@ -101,7 +111,6 @@
     ]);
 
     $shippingMethod = ShippingMethod::factory()->create([
-        'shipping_zone_id' => $shippingZone->id,
         'driver' => 'free-shipping',
         'data' => [
             'minimum_spend' => [
@@ -110,13 +119,19 @@
         ],
     ]);
 
+    $shippingRate = \Lunar\Shipping\Models\ShippingRate::factory()
+        ->create([
+            'shipping_method_id' => $shippingMethod->id,
+            'shipping_zone_id' => $shippingZone->id,
+        ]);
+
     $cart = $this->createCart($currency, 10000);
 
     $driver = new FreeShipping();
 
     $request = new ShippingOptionRequest(
+        shippingRate: $shippingRate,
         cart: $cart,
-        shippingMethod: $shippingMethod
     );
 
     $shippingOption = $driver->resolve($request);
diff --git a/tests/shipping/Unit/Drivers/ShippingMethods/ShipByTest.php b/tests/shipping/Unit/Drivers/ShippingMethods/ShipByTest.php
index 05cdafd179..a4c9ebdadf 100644
--- a/tests/shipping/Unit/Drivers/ShippingMethods/ShipByTest.php
+++ b/tests/shipping/Unit/Drivers/ShippingMethods/ShipByTest.php
@@ -27,35 +27,40 @@
     ]);
 
     $shippingMethod = ShippingMethod::factory()->create([
-        'shipping_zone_id' => $shippingZone->id,
         'driver' => 'ship-by',
         'data' => [
             'charge_by' => 'cart_total',
         ],
     ]);
 
-    $shippingMethod->prices()->createMany([
+    $shippingRate = \Lunar\Shipping\Models\ShippingRate::factory()
+        ->create([
+            'shipping_method_id' => $shippingMethod->id,
+            'shipping_zone_id' => $shippingZone->id,
+        ]);
+
+    $shippingRate->prices()->createMany([
         [
             'price' => 1000,
-            'tier' => 1,
+            'quantity_break' => 1,
             'currency_id' => $currency->id,
         ],
         [
             'price' => 500,
-            'tier' => 700,
+            'quantity_break' => 700,
             'currency_id' => $currency->id,
         ],
     ]);
 
-    expect($shippingMethod->prices)->toHaveCount(2);
+    expect($shippingRate->prices)->toHaveCount(2);
 
     $cart = $this->createCart($currency, 100);
 
     $driver = new ShipBy();
 
     $request = new ShippingOptionRequest(
+        shippingRate: $shippingRate,
         cart: $cart,
-        shippingMethod: $shippingMethod
     );
 
     $shippingOption = $driver->resolve($request);
@@ -69,8 +74,8 @@
     $driver = new ShipBy();
 
     $request = new ShippingOptionRequest(
-        cart: $cart,
-        shippingMethod: $shippingMethod
+        shippingRate: $shippingRate,
+        cart: $cart
     );
 
     $shippingOption = $driver->resolve($request);
@@ -80,6 +85,67 @@
     expect($shippingOption->price->value)->toEqual(500);
 });
 
+test('can get shipping option by cart total when prices include tax', function () {
+
+    \Illuminate\Support\Facades\Config::set('lunar.pricing.stored_inclusive_of_tax', true);
+
+    $currency = Currency::factory()->create([
+        'default' => true,
+    ]);
+
+    TaxClass::factory()->create([
+        'default' => true,
+    ]);
+
+    $shippingZone = ShippingZone::factory()->create([
+        'type' => 'countries',
+    ]);
+
+    $shippingMethod = ShippingMethod::factory()->create([
+        'driver' => 'ship-by',
+        'data' => [
+            'charge_by' => 'cart_total',
+        ],
+    ]);
+
+    $shippingRate = \Lunar\Shipping\Models\ShippingRate::factory()
+        ->create([
+            'shipping_method_id' => $shippingMethod->id,
+            'shipping_zone_id' => $shippingZone->id,
+        ]);
+
+    $shippingRate->prices()->createMany([
+        [
+            'price' => 1000,
+            'quantity_break' => 1,
+            'currency_id' => $currency->id,
+        ],
+        [
+            'price' => 500,
+            'quantity_break' => 700,
+            'currency_id' => $currency->id,
+        ],
+    ]);
+
+    expect($shippingRate->prices)->toHaveCount(2);
+
+    $cart = $this->createCart($currency, 700);
+
+    $driver = new ShipBy();
+
+    $request = new ShippingOptionRequest(
+        shippingRate: $shippingRate,
+        cart: $cart,
+    );
+
+    $shippingOption = $driver->resolve($request);
+
+    expect($shippingOption)->toBeInstanceOf(ShippingOption::class);
+
+    expect($shippingOption->price->value)->toEqual(500);
+
+})->group('thisone');
+
 test('can get shipping option if outside tier without default price', function () {
     // Boom.
     $currency = Currency::factory()->create([
@@ -95,30 +161,35 @@
     ]);
 
     $shippingMethod = ShippingMethod::factory()->create([
-        'shipping_zone_id' => $shippingZone->id,
         'driver' => 'ship-by',
         'data' => [
             'charge_by' => 'cart_total',
         ],
     ]);
 
-    $shippingMethod->prices()->createMany([
+    $shippingRate = \Lunar\Shipping\Models\ShippingRate::factory()
+        ->create([
+            'shipping_method_id' => $shippingMethod->id,
+            'shipping_zone_id' => $shippingZone->id,
+        ]);
+
+    $shippingRate->prices()->createMany([
         [
             'price' => 500,
-            'tier' => 700,
+            'quantity_break' => 700,
             'currency_id' => $currency->id,
         ],
     ]);
 
-    expect($shippingMethod->prices)->toHaveCount(1);
+    expect($shippingRate->prices)->toHaveCount(1);
 
     $cart = $this->createCart($currency, 100);
 
     $driver = new ShipBy();
 
     $request = new ShippingOptionRequest(
-        cart: $cart,
-        shippingMethod: $shippingMethod
+        shippingRate: $shippingRate,
+        cart: $cart
     );
 
     $this->expectException(\ErrorException::class);
diff --git a/tests/shipping/Unit/Managers/ShippingManagerTest.php b/tests/shipping/Unit/Managers/ShippingManagerTest.php
index 63b349f203..a2e28e71c5 100644
--- a/tests/shipping/Unit/Managers/ShippingManagerTest.php
+++ b/tests/shipping/Unit/Managers/ShippingManagerTest.php
@@ -19,7 +19,7 @@
     expect($resolver)->toBeInstanceOf(ShippingZoneResolver::class);
 });
 
-test('can fetch expected shipping methods', function () {
+test('can fetch expected shipping rates', function () {
     $currency = Currency::factory()->create([
         'default' => true,
     ]);
@@ -37,7 +37,6 @@
     $shippingZone->countries()->attach($country);
 
     $shippingMethod = ShippingMethod::factory()->create([
-        'shipping_zone_id' => $shippingZone->id,
         'driver' => 'ship-by',
         'data' => [
             'minimum_spend' => [
@@ -46,20 +45,26 @@
         ],
     ]);
 
-    $shippingMethod->prices()->createMany([
+    $shippingRate = \Lunar\Shipping\Models\ShippingRate::factory()
+        ->create([
+            'shipping_method_id' => $shippingMethod->id,
+            'shipping_zone_id' => $shippingZone->id,
+        ]);
+
+    $shippingRate->prices()->createMany([
         [
             'price' => 600,
-            'tier' => 1,
+            'quantity_break' => 1,
             'currency_id' => $currency->id,
         ],
         [
             'price' => 500,
-            'tier' => 700,
+            'quantity_break' => 700,
             'currency_id' => $currency->id,
         ],
         [
             'price' => 0,
-            'tier' => 800,
+            'quantity_break' => 800,
             'currency_id' => $currency->id,
         ],
     ]);
@@ -73,9 +78,9 @@
         ])->toArray()
     );
 
-    $shippingMethods = Shipping::shippingMethods(
+    $shippingRates = Shipping::shippingRates(
         $cart->refresh()->calculate()
     )->get();
 
-    expect($shippingMethods)->toHaveCount(1);
+    expect($shippingRates)->toHaveCount(1);
 });
diff --git a/tests/shipping/Unit/Resolvers/ShippingOptionResolverTest.php b/tests/shipping/Unit/Resolvers/ShippingOptionResolverTest.php
index db60ef882c..0f51e1de3a 100644
--- a/tests/shipping/Unit/Resolvers/ShippingOptionResolverTest.php
+++ b/tests/shipping/Unit/Resolvers/ShippingOptionResolverTest.php
@@ -32,7 +32,6 @@
     $shippingZone->countries()->attach($country);
 
     $shippingMethod = ShippingMethod::factory()->create([
-        'shipping_zone_id' => $shippingZone->id,
         'driver' => 'ship-by',
         'data' => [
             'minimum_spend' => [
@@ -41,20 +40,26 @@
         ],
     ]);
 
-    $shippingMethod->prices()->createMany([
+    $shippingRate = \Lunar\Shipping\Models\ShippingRate::factory()
+        ->create([
+            'shipping_method_id' => $shippingMethod->id,
+            'shipping_zone_id' => $shippingZone->id,
+        ]);
+
+    $shippingRate->prices()->createMany([
         [
             'price' => 600,
-            'tier' => 1,
+            'quantity_break' => 1,
             'currency_id' => $currency->id,
         ],
         [
             'price' => 500,
-            'tier' => 700,
+            'quantity_break' => 700,
             'currency_id' => $currency->id,
         ],
         [
             'price' => 0,
-            'tier' => 800,
+            'quantity_break' => 800,
             'currency_id' => $currency->id,
         ],
     ]);
@@ -68,7 +73,7 @@
         ])->toArray()
     );
 
-    $shippingMethods = Shipping::shippingMethods(
+    $shippingRates = Shipping::shippingRates(
         $cart->refresh()->calculate()
     )->get();
 
@@ -76,7 +81,7 @@
         $cart->refresh()->calculate()
     )->get(
         new ShippingOptionLookup(
-            shippingMethods: $shippingMethods
+            shippingRates: $shippingRates
         )
     );
 
diff --git a/tests/shipping/Unit/Resolvers/ShippingMethodResolverTest.php b/tests/shipping/Unit/Resolvers/ShippingRateResolverTest.php
similarity index 75%
rename from tests/shipping/Unit/Resolvers/ShippingMethodResolverTest.php
rename to tests/shipping/Unit/Resolvers/ShippingRateResolverTest.php
index 9a11a9edfc..cc5d5329f5 100644
--- a/tests/shipping/Unit/Resolvers/ShippingMethodResolverTest.php
+++ b/tests/shipping/Unit/Resolvers/ShippingRateResolverTest.php
@@ -16,7 +16,7 @@
 uses(\Illuminate\Foundation\Testing\RefreshDatabase::class);
 uses(\Lunar\Tests\Shipping\TestUtils::class);
 
-test('can fetch shipping methods by country', function () {
+test('can fetch shipping rates by country', function () {
     $currency = Currency::factory()->create([
         'default' => true,
     ]);
@@ -34,7 +34,6 @@
     $shippingZone->countries()->attach($country);
 
     $shippingMethod = ShippingMethod::factory()->create([
-        'shipping_zone_id' => $shippingZone->id,
         'driver' => 'ship-by',
         'data' => [
             'minimum_spend' => [
@@ -43,20 +42,25 @@
         ],
     ]);
 
-    $shippingMethod->prices()->createMany([
+    $shippingRate = \Lunar\Shipping\Models\ShippingRate::factory()->create([
+        'shipping_method_id' => $shippingMethod->id,
+        'shipping_zone_id' => $shippingZone->id,
+    ]);
+
+    $shippingRate->prices()->createMany([
         [
             'price' => 600,
-            'tier' => 1,
+            'quantity_break' => 1,
             'currency_id' => $currency->id,
         ],
         [
             'price' => 500,
-            'tier' => 700,
+            'quantity_break' => 700,
             'currency_id' => $currency->id,
         ],
         [
             'price' => 0,
-            'tier' => 800,
+            'quantity_break' => 800,
             'currency_id' => $currency->id,
         ],
     ]);
@@ -70,12 +74,12 @@
         ])->toArray()
     );
 
-    $shippingMethods = Shipping::shippingMethods(
+    $shippingRates = Shipping::shippingRates(
         $cart->refresh()->calculate()
     )->get();
 
-    expect($shippingMethods)->toHaveCount(1);
-    expect($shippingMethods->first()->id)->toEqual($shippingMethod->id);
+    expect($shippingRates)->toHaveCount(1);
+    expect($shippingRates->first()->id)->toEqual($shippingRate->id);
 
     $cart = $this->createCart($currency, 500);
 
@@ -88,14 +92,14 @@
         ])->toArray()
     );
 
-    $shippingMethods = Shipping::shippingMethods(
+    $shippingRates = Shipping::shippingRates(
         $cart->refresh()->calculate()
     )->get();
 
-    expect($shippingMethods)->toBeEmpty();
+    expect($shippingRates)->toBeEmpty();
 });
 
-test('can fetch shipping methods by state', function () {
+test('can fetch shipping rates by state', function () {
     $currency = Currency::factory()->create([
         'default' => true,
     ]);
@@ -117,7 +121,6 @@
     $shippingZone->states()->attach($state);
 
     $shippingMethod = ShippingMethod::factory()->create([
-        'shipping_zone_id' => $shippingZone->id,
         'driver' => 'ship-by',
         'data' => [
             'minimum_spend' => [
@@ -126,20 +129,25 @@
         ],
     ]);
 
-    $shippingMethod->prices()->createMany([
+    $shippingRate = \Lunar\Shipping\Models\ShippingRate::factory()->create([
+        'shipping_method_id' => $shippingMethod->id,
+        'shipping_zone_id' => $shippingZone->id,
+    ]);
+
+    $shippingRate->prices()->createMany([
         [
             'price' => 600,
-            'tier' => 1,
+            'quantity_break' => 1,
             'currency_id' => $currency->id,
         ],
         [
             'price' => 500,
-            'tier' => 700,
+            'quantity_break' => 700,
             'currency_id' => $currency->id,
         ],
         [
             'price' => 0,
-            'tier' => 800,
+            'quantity_break' => 800,
             'currency_id' => $currency->id,
         ],
     ]);
@@ -153,15 +161,15 @@
         ])->toArray()
     );
 
-    $shippingMethods = Shipping::shippingMethods(
+    $shippingRates = Shipping::shippingRates(
         $cart->refresh()->calculate()
     )->get();
 
-    expect($shippingMethods)->toHaveCount(1);
-    expect($shippingMethods->first()->id)->toEqual($shippingMethod->id);
+    expect($shippingRates)->toHaveCount(1);
+    expect($shippingRates->first()->id)->toEqual($shippingRate->id);
 });
 
-test('can fetch shipping methods by postcode', function () {
+test('can fetch shipping rates by postcode', function () {
     $currency = Currency::factory()->create([
         'default' => true,
     ]);
@@ -183,7 +191,6 @@
     $shippingZone->countries()->attach($country);
 
     $shippingMethod = ShippingMethod::factory()->create([
-        'shipping_zone_id' => $shippingZone->id,
         'driver' => 'ship-by',
         'data' => [
             'minimum_spend' => [
@@ -192,20 +199,25 @@
         ],
     ]);
 
-    $shippingMethod->prices()->createMany([
+    $shippingRate = \Lunar\Shipping\Models\ShippingRate::factory()->create([
+        'shipping_method_id' => $shippingMethod->id,
+        'shipping_zone_id' => $shippingZone->id,
+    ]);
+
+    $shippingRate->prices()->createMany([
         [
             'price' => 600,
-            'tier' => 1,
+            'quantity_break' => 1,
             'currency_id' => $currency->id,
         ],
         [
             'price' => 500,
-            'tier' => 700,
+            'quantity_break' => 700,
             'currency_id' => $currency->id,
         ],
         [
             'price' => 0,
-            'tier' => 800,
+            'quantity_break' => 800,
             'currency_id' => $currency->id,
         ],
     ]);
@@ -220,15 +232,15 @@
         ])->toArray()
     );
 
-    $shippingMethods = Shipping::shippingMethods(
+    $shippingRates = Shipping::shippingRates(
         $cart->refresh()->calculate()
     )->get();
 
-    expect($shippingMethods)->toHaveCount(1);
-    expect($shippingMethods->first()->id)->toEqual($shippingMethod->id);
+    expect($shippingRates)->toHaveCount(1);
+    expect($shippingRates->first()->id)->toEqual($shippingRate->id);
 });
 
-test('can reject shipping methods when stock is not available', function () {
+test('can reject shipping rates when stock is not available', function () {
     $currency = Currency::factory()->create([
         'default' => true,
     ]);
@@ -250,7 +262,6 @@
     $shippingZone->countries()->attach($country);
 
     $shippingMethod = ShippingMethod::factory()->create([
-        'shipping_zone_id' => $shippingZone->id,
         'driver' => 'ship-by',
         'data' => [
             'minimum_spend' => [
@@ -260,20 +271,25 @@
         'stock_available' => 1,
     ]);
 
-    $shippingMethod->prices()->createMany([
+    $shippingRate = \Lunar\Shipping\Models\ShippingRate::factory()->create([
+        'shipping_method_id' => $shippingMethod->id,
+        'shipping_zone_id' => $shippingZone->id,
+    ]);
+
+    $shippingRate->prices()->createMany([
         [
             'price' => 600,
-            'tier' => 1,
+            'quantity_break' => 1,
             'currency_id' => $currency->id,
         ],
         [
             'price' => 500,
-            'tier' => 700,
+            'quantity_break' => 700,
             'currency_id' => $currency->id,
         ],
         [
             'price' => 0,
-            'tier' => 800,
+            'quantity_break' => 800,
             'currency_id' => $currency->id,
         ],
     ]);
@@ -285,7 +301,7 @@
 
     Price::factory()->create([
         'price' => 200,
-        'tier' => 1,
+        'quantity_break' => 1,
         'currency_id' => $currency->id,
         'priceable_type' => get_class($purchasable),
         'priceable_id' => $purchasable->id,
@@ -305,9 +321,9 @@
         ])->toArray()
     );
 
-    $shippingMethods = Shipping::shippingMethods(
+    $shippingRates = Shipping::shippingRates(
         $cart->refresh()->calculate()
     )->get();
 
-    expect($shippingMethods)->toHaveCount(0);
+    expect($shippingRates)->toHaveCount(0);
 });