@{{ simpleProduct.name }}
-
-
@{{ (simpleProduct.special_price || simpleProduct.price) | price }}
-
@{{ simpleProduct.price | price }}
-
+
diff --git a/resources/views/product/partials/options/area.blade.php b/resources/views/product/partials/options/area.blade.php
deleted file mode 100644
index 5d1aaec2e..000000000
--- a/resources/views/product/partials/options/area.blade.php
+++ /dev/null
@@ -1,11 +0,0 @@
-
- {{ $option->title }} {{ $option->price_label }}
-
-
diff --git a/resources/views/product/partials/options/drop_down.blade.php b/resources/views/product/partials/options/drop_down.blade.php
deleted file mode 100644
index 7addfa89b..000000000
--- a/resources/views/product/partials/options/drop_down.blade.php
+++ /dev/null
@@ -1,16 +0,0 @@
-
- {{ $option->title }}
-
-
-
- @foreach($option->values as $value)
-
- @endforeach
-
diff --git a/resources/views/product/partials/options/field.blade.php b/resources/views/product/partials/options/field.blade.php
deleted file mode 100644
index 5e0b6ecda..000000000
--- a/resources/views/product/partials/options/field.blade.php
+++ /dev/null
@@ -1,12 +0,0 @@
-
- {{ $option->title }} {{ $option->price_label }}
-
-
-
diff --git a/src/Auth/MagentoCustomerTokenGuard.php b/src/Auth/MagentoCustomerTokenGuard.php
index 3d616cea2..21ba6bf5b 100644
--- a/src/Auth/MagentoCustomerTokenGuard.php
+++ b/src/Auth/MagentoCustomerTokenGuard.php
@@ -4,10 +4,21 @@
use Illuminate\Auth\TokenGuard;
use Illuminate\Contracts\Auth\Guard;
-use Illuminate\Http\Request;
+use Lcobucci\JWT\Validation\RequiredConstraintsViolated;
class MagentoCustomerTokenGuard extends TokenGuard implements Guard
{
+ public function getTokenForRequest()
+ {
+ $token = parent::getTokenForRequest();
+
+ if (empty($token)) {
+ $token = $_COOKIE[$this->inputKey] ?? null;
+ }
+
+ return $token;
+ }
+
/**
* Get the currently authenticated user.
*
@@ -43,6 +54,15 @@ public function validate(array $credentials = [])
protected function retrieveByToken($token)
{
- return config('rapidez.models.customer')::whereToken($token)->first();
+ try {
+ return config('rapidez.models.customer')::whereToken($token)->first();
+ } catch (RequiredConstraintsViolated $e) {
+ // Token is expired or invalid by incorrect signature.
+ if (isset($_COOKIE[$this->inputKey])) {
+ unset($_COOKIE[$this->inputKey]);
+ }
+ }
+
+ return null;
}
}
diff --git a/src/Http/Controllers/ProductController.php b/src/Http/Controllers/ProductController.php
index 461dc4c42..8cf98bdc8 100644
--- a/src/Http/Controllers/ProductController.php
+++ b/src/Http/Controllers/ProductController.php
@@ -13,9 +13,26 @@ public function show(int $productId)
$product = $productModel::selectForProductPage()
->withEventyGlobalScopes('productpage.scopes')
->with('options')
+ ->with('tierPrices')
->findOrFail($productId);
- $attributes = ['entity_id', 'name', 'sku', 'super_attributes', 'children', 'grouped', 'options', 'price', 'special_price', 'images', 'url', 'min_sale_qty'];
+ $attributes = [
+ 'entity_id',
+ 'name',
+ 'sku',
+ 'super_attributes',
+ 'children',
+ 'grouped',
+ 'options',
+ 'price',
+ 'special_price',
+ 'tax_class_id',
+ 'tierPrices',
+ 'images',
+ 'url',
+ 'min_sale_qty',
+ ];
+
$attributes = Eventy::filter('productpage.frontend.attributes', $attributes);
foreach ($product->super_attributes ?: [] as $superAttribute) {
diff --git a/src/Http/ViewComposers/ConfigComposer.php b/src/Http/ViewComposers/ConfigComposer.php
index 331d5223e..77d2aef70 100644
--- a/src/Http/ViewComposers/ConfigComposer.php
+++ b/src/Http/ViewComposers/ConfigComposer.php
@@ -8,6 +8,7 @@
use Illuminate\Support\Str;
use Illuminate\View\View;
use Rapidez\Core\Facades\Rapidez;
+use Rapidez\Core\Models\TaxCalculation;
class ConfigComposer
{
@@ -42,6 +43,7 @@ public function compose(View $view)
Config::set('frontend.customer_fields_show', $this->getCustomerFields());
Config::set('frontend.grid_per_page', Rapidez::config('catalog/frontend/grid_per_page', 12));
Config::set('frontend.grid_per_page_values', explode(',', Rapidez::config('catalog/frontend/grid_per_page_values', '12,24,36')));
+ Config::set('frontend.tax', $this->getTaxConfiguration());
}
public function getCustomerFields()
@@ -60,4 +62,35 @@ public function getCustomerFields()
'company' => Rapidez::config('customer/address/company_show', 'opt'),
];
}
+
+ public function getTaxConfiguration()
+ {
+ $customerGroupId = auth('magento-customer')->user()?->group_id ?? 0;
+ $values = Cache::remember('tax-configuration-' . $customerGroupId, 3600, function () use ($customerGroupId) {
+ return TaxCalculation::select('tax_country_id', 'tax_region_id', 'tax_postcode', 'rate', 'product_tax_class_id')
+ ->whereHas('customerGroups', fn ($query) => $query->where('customer_group_id', $customerGroupId))
+ ->get()
+ ->groupBy('product_tax_class_id');
+ });
+
+ return [
+ 'rates' => $values,
+ 'calculation' => [
+ 'price_includes_tax' => boolval(Rapidez::config('tax/calculation/price_includes_tax', 0)),
+ 'based_on' => Rapidez::config('tax/calculation/based_on', 'shipping'),
+ ],
+ 'display' => [
+ 'catalog' => Rapidez::config('tax/display/type', 1),
+ 'shipping' => Rapidez::config('tax/display/shipping', 1),
+ 'cart_price' => Rapidez::config('tax/cart_display/price', 1),
+ 'cart_shipping' => Rapidez::config('tax/cart_display/shipping', 1),
+ 'cart_subtotal' => Rapidez::config('tax/cart_display/subtotal', 1),
+ ],
+ 'defaults' => [
+ 'country_id' => Rapidez::config('tax/defaults/country', 'US'),
+ 'postcode' => Rapidez::config('tax/defaults/postcode', null),
+ 'region_id' => Rapidez::config('tax/defaults/region', 0),
+ ],
+ ];
+ }
}
diff --git a/src/Models/Customer.php b/src/Models/Customer.php
index e406aa752..80349d1ed 100644
--- a/src/Models/Customer.php
+++ b/src/Models/Customer.php
@@ -27,6 +27,11 @@ public function oauthTokens()
return $this->hasMany(config('rapidez.models.oauth_token'), 'customer_id');
}
+ public function customerGroup()
+ {
+ return $this->hasOne(config('rapidez.models.customer_group'), 'customer_group_id', 'group_id');
+ }
+
public function getRememberTokenName()
{
return '';
@@ -38,7 +43,7 @@ public function scopeWhereToken(Builder $query, string $token)
DecodeJwt::isJwt($token),
fn (Builder $query) => $query
->where(
- $this->qualifyColumn('customer_id'),
+ $this->getQualifiedKeyName(),
DecodeJwt::decode($token)
->claims()
->get('uid')
diff --git a/src/Models/CustomerGroup.php b/src/Models/CustomerGroup.php
new file mode 100644
index 000000000..9984161bc
--- /dev/null
+++ b/src/Models/CustomerGroup.php
@@ -0,0 +1,18 @@
+hasMany(config('rapidez.models.tax_calculation'), 'customer_tax_class_id', 'tax_class_id');
+ }
+}
diff --git a/src/Models/Product.php b/src/Models/Product.php
index a4fc372bd..e2845d1ec 100644
--- a/src/Models/Product.php
+++ b/src/Models/Product.php
@@ -10,6 +10,7 @@
use Rapidez\Core\Casts\CommaSeparatedToArray;
use Rapidez\Core\Casts\CommaSeparatedToIntegerArray;
use Rapidez\Core\Casts\DecodeHtmlEntities;
+use Rapidez\Core\Facades\Rapidez;
use Rapidez\Core\Models\Scopes\Product\WithProductAttributesScope;
use Rapidez\Core\Models\Scopes\Product\WithProductCategoryInfoScope;
use Rapidez\Core\Models\Scopes\Product\WithProductChildrenScope;
@@ -34,7 +35,7 @@ class Product extends Model
protected $primaryKey = 'entity_id';
- protected $appends = ['url'];
+ protected $appends = ['url', 'price_without_tax'];
protected static function booting(): void
{
@@ -92,14 +93,6 @@ public function gallery(): BelongsToMany
);
}
- public function views(): HasMany
- {
- return $this->hasMany(
- config('rapidez.models.product_view'),
- 'product_id',
- );
- }
-
public function options(): HasMany
{
return $this->hasMany(
@@ -116,22 +109,62 @@ public function rewrites(): HasMany
->where('entity_type', 'product');
}
+ public function tierPrices(): HasMany
+ {
+ return $this->hasMany(
+ config('rapidez.models.product_tier_price'),
+ 'entity_id'
+ )
+ ->whereIn('website_id', [0, config('rapidez.website')])
+ ->where(fn ($query) => $query
+ ->where('all_groups', 1)
+ ->orWhere('customer_group_id', auth('magento-customer')->user()?->group_id ?? 0)
+ );
+ }
+
+ public function views(): HasMany
+ {
+ return $this->hasMany(
+ config('rapidez.models.product_view'),
+ 'product_id',
+ );
+ }
+
public function scopeByIds(Builder $query, array $productIds): Builder
{
return $query->whereIn($this->getQualifiedKeyName(), $productIds);
}
- public function getPriceAttribute($price)
+ public function price(): Attribute
{
- if ($this->type_id == 'configurable') {
- return collect($this->children)->min->price;
- }
+ return Attribute::make(
+ get: function ($price) {
+ if ($this->type_id == 'configurable') {
+ $price = collect($this->children)->min->price;
+ }
- if ($this->type_id == 'grouped') {
- return collect($this->grouped)->min->price;
- }
+ if ($this->type_id == 'grouped') {
+ $price = collect($this->grouped)->min->price;
+ }
+
+ if (Rapidez::config('tax/calculation/price_includes_tax', 0)) {
+ return $price;
+ }
- return $price;
+ // TODO: Get the tax from the settings.
+ return $price * 1.21;
+ }
+ );
+ }
+
+ public function priceWithoutTax(): Attribute
+ {
+ return Attribute::make(
+ get: function () {
+ // TODO: Get the tax from the settings.
+ return $this->price / 1.21;
+ }
+ );
}
public function getSpecialPriceAttribute($specialPrice)
diff --git a/src/Models/ProductOption.php b/src/Models/ProductOption.php
index a8e157207..8d6440765 100644
--- a/src/Models/ProductOption.php
+++ b/src/Models/ProductOption.php
@@ -30,7 +30,7 @@ public function product()
protected function title(): Attribute
{
return Attribute::make(
- get: fn () => $this->titles->firstForCurrentStore()->title,
+ get: fn () => $this->titles->firstForCurrentStore()->title ?? null,
)->shouldCache();
}
diff --git a/src/Models/ProductOptionTypeValue.php b/src/Models/ProductOptionTypeValue.php
index d0f9bc55e..2413ac775 100644
--- a/src/Models/ProductOptionTypeValue.php
+++ b/src/Models/ProductOptionTypeValue.php
@@ -26,7 +26,7 @@ public function option()
protected function title(): Attribute
{
return Attribute::make(
- get: fn () => $this->titles->firstForCurrentStore()->title,
+ get: fn () => $this->titles->firstForCurrentStore()->title ?? null,
)->shouldCache();
}
diff --git a/src/Models/ProductTierPrice.php b/src/Models/ProductTierPrice.php
new file mode 100644
index 000000000..02b40f33a
--- /dev/null
+++ b/src/Models/ProductTierPrice.php
@@ -0,0 +1,17 @@
+belongsTo(config('rapidez.models.product'), 'entity_id');
+ }
+}
diff --git a/src/Models/QuoteItemOption.php b/src/Models/QuoteItemOption.php
index 7fa7cf84e..9fb2d9423 100644
--- a/src/Models/QuoteItemOption.php
+++ b/src/Models/QuoteItemOption.php
@@ -48,7 +48,7 @@ protected function value(): Attribute
protected function label(): Attribute
{
return Attribute::make(
- get: fn () => $this->option?->titles->firstForCurrentStore()->title
+ get: fn () => $this->option?->titles->firstForCurrentStore()->title ?? null
)->shouldCache();
}
diff --git a/src/Models/Scopes/Product/WithProductAttributesScope.php b/src/Models/Scopes/Product/WithProductAttributesScope.php
index 8187d79a6..5cb2bd2b5 100644
--- a/src/Models/Scopes/Product/WithProductAttributesScope.php
+++ b/src/Models/Scopes/Product/WithProductAttributesScope.php
@@ -27,6 +27,7 @@ public function apply(Builder $builder, Model $model)
$model->qualifyColumn('sku'),
$model->qualifyColumn('visibility'),
$model->qualifyColumn('type_id'),
+ $model->qualifyColumn('tax_class_id'),
$model->getQualifiedCreatedAtColumn(),
]);
diff --git a/src/Models/Scopes/Product/WithProductChildrenScope.php b/src/Models/Scopes/Product/WithProductChildrenScope.php
index ac316202f..33a5e72ab 100644
--- a/src/Models/Scopes/Product/WithProductChildrenScope.php
+++ b/src/Models/Scopes/Product/WithProductChildrenScope.php
@@ -30,6 +30,7 @@ public function apply(Builder $builder, Model $model)
$builder
->selectRaw('JSON_REMOVE(JSON_OBJECTAGG(IFNULL(children.entity_id, "null__"), JSON_OBJECT(
' . Eventy::filter('product.children.select', << $builder
+ ->join('tax_calculation_rate', 'tax_calculation.tax_calculation_rate_id', '=', 'tax_calculation_rate.tax_calculation_rate_id')
+ );
+ }
+
+ public function customerGroups()
+ {
+ return $this->hasOne(config('rapidez.models.customer_group'), 'tax_class_id', 'customer_tax_class_id');
+ }
+
+ public function products()
+ {
+ return $this->hasMany(config('rapidez.models.product'), 'tax_class_id', 'product_tax_class_id');
+ }
+}
diff --git a/src/RapidezServiceProvider.php b/src/RapidezServiceProvider.php
index 199d80f43..2b64d26b9 100644
--- a/src/RapidezServiceProvider.php
+++ b/src/RapidezServiceProvider.php
@@ -73,7 +73,7 @@ public function register()
protected function bootAuth(): self
{
auth()->extend('magento-customer', function (Application $app, string $name, array $config) {
- return new MagentoCustomerTokenGuard(auth()->createUserProvider($config['provider']), request(), 'token', 'token');
+ return new MagentoCustomerTokenGuard(auth()->createUserProvider($config['provider']), request(), 'customer_token', 'customer_token');
});
config([
diff --git a/tests/Browser/PriceTest.php b/tests/Browser/PriceTest.php
new file mode 100644
index 000000000..6f1192add
--- /dev/null
+++ b/tests/Browser/PriceTest.php
@@ -0,0 +1,91 @@
+browseWithConfigs([
+ 'tax/calculation/price_includes_tax' => 0,
+ 'tax/display/type' => 1,
+ ], function (Browser $browser) {
+ $browser
+ ->visit($this->testProduct->url)
+ ->assertSeePrice($this->testProduct->price_without_tax);
+ });
+ }
+
+ /**
+ * @test
+ */
+ public function priceBaseWithoutTaxShowWithTax()
+ {
+ $this->browseWithConfigs([
+ 'tax/calculation/price_includes_tax' => 0,
+ 'tax/display/type' => 2,
+ ], function (Browser $browser) {
+ $browser
+ ->visit($this->testProduct->url)
+ ->assertSeePrice($this->testProduct->price);
+ });
+ }
+
+ /**
+ * @test
+ */
+ public function priceBaseWithTaxShowWithoutTax()
+ {
+ dump($this->testProduct->price_without_tax);
+ $this->browseWithConfigs([
+ 'tax/calculation/price_includes_tax' => 1,
+ 'tax/display/type' => 1,
+ ], function (Browser $browser) {
+ $browser
+ ->visit($this->testProduct->url)
+ ->assertSeePrice($this->testProduct->price_without_tax);
+ });
+ }
+
+ /**
+ * @test
+ */
+ public function priceBaseWithTaxShowWithTax()
+ {
+ $this->browseWithConfigs([
+ 'tax/calculation/price_includes_tax' => 1,
+ 'tax/display/type' => 2,
+ ], function (Browser $browser) {
+ $browser
+ ->visit($this->testProduct->url)
+ ->assertSeePrice($this->testProduct->price);
+ });
+ }
+
+ /**
+ * @test
+ */
+ public function specialPrice()
+ {
+ $previousSpecialPrice = $this->testProduct->special_price;
+ $this->testProduct->newQueryWithoutScopes()->update(['special_price' => 20]);
+
+ try {
+ $this->browse(function (Browser $browser) {
+ $browser
+ ->visit($this->testProduct->url)
+ ->assertSeePrice($this->testProduct->refresh()->special_price);
+ });
+ } finally {
+ $this->testProduct->newQueryWithoutScopes()->update(['special_price' => $previousSpecialPrice]);
+ }
+ }
+
+ // TODO: Test special price dates.
+}
diff --git a/tests/DuskTestCase.php b/tests/DuskTestCase.php
index 70913ce36..4894719e3 100644
--- a/tests/DuskTestCase.php
+++ b/tests/DuskTestCase.php
@@ -8,6 +8,7 @@
use Rapidez\BladeDirectives\BladeDirectivesServiceProvider;
use Rapidez\Core\Facades\Rapidez;
use Rapidez\Core\RapidezServiceProvider;
+use TailwindMerge\Laravel\TailwindMergeServiceProvider;
use TorMorten\Eventy\EventServiceProvider;
abstract class DuskTestCase extends BaseTestCase
@@ -17,11 +18,12 @@ abstract class DuskTestCase extends BaseTestCase
protected function getPackageProviders($app)
{
return [
+ BladeDirectivesServiceProvider::class,
+ BladeHeroiconsServiceProvider::class,
+ BladeIconsServiceProvider::class,
EventServiceProvider::class,
RapidezServiceProvider::class,
- BladeIconsServiceProvider::class,
- BladeHeroiconsServiceProvider::class,
- BladeDirectivesServiceProvider::class,
+ TailwindMergeServiceProvider::class,
];
}
diff --git a/tests/DuskTestCaseSetup.php b/tests/DuskTestCaseSetup.php
index 93c6e3806..588349e7c 100644
--- a/tests/DuskTestCaseSetup.php
+++ b/tests/DuskTestCaseSetup.php
@@ -2,6 +2,8 @@
namespace Rapidez\Core\Tests;
+use Closure;
+use Illuminate\Support\Facades\DB;
use Laravel\Dusk\Browser;
use PHPUnit\Framework\Assert;
use Rapidez\Core\Models\Product;
@@ -56,12 +58,136 @@ protected function setUp(): void
return $this;
});
+ Browser::macro('assertSeePrice', function ($price) {
+ /** @var Browser $this */
+ $this->assertSee(preg_replace("/\s+/u", ' ', price($price)));
+
+ return $this;
+ });
+
$this->flat = (new Product)->getTable();
$this->testProduct = Product::selectAttributes([
'name',
'price',
+ 'special_price',
'url_key',
])->firstWhere($this->flat . '.sku', env('TEST_PRODUCT', '24-WB02'));
+
+ // We're running with the database from the
+ // michielgerritsen/magento-project-community-edition
+ // image and the tax is not configured yet.
+ if (DB::table('admin_user')->where('email', 'user@example.com')->exists()
+ && DB::table('tax_calculation_rate')->where('code', 'Default Tax Rate NL')->doesntExist()) {
+ $this->configureTax();
+ }
+ }
+
+ protected function configureTax()
+ {
+ $taxRateDefaultId = DB::table('tax_calculation_rate')->insertGetId([
+ 'tax_country_id' => 'NL',
+ 'tax_region_id' => 0,
+ 'tax_postcode' => '*',
+ 'code' => 'Default Tax Rate NL',
+ 'rate' => 21,
+ ]);
+
+ $taxRateNoneId = DB::table('tax_calculation_rate')->insertGetId([
+ 'tax_country_id' => 'NL',
+ 'tax_region_id' => 0,
+ 'tax_postcode' => '*',
+ 'code' => 'No Tax Rate NL',
+ 'rate' => 0,
+ ]);
+
+ $taxRuleDefaultId = DB::table('tax_calculation_rule')->insertGetId([
+ 'code' => 'Default Tax Rate NL',
+ 'priority' => 0,
+ 'position' => 0,
+ 'calculate_subtotal' => 0,
+ ]);
+
+ $taxRuleNoneId = DB::table('tax_calculation_rule')->insertGetId([
+ 'code' => 'No Tax Rate NL',
+ 'priority' => 0,
+ 'position' => 0,
+ 'calculate_subtotal' => 0,
+ ]);
+
+ $retailCustomerClassId = DB::table('tax_class')->where('class_name', 'Retail Customer')->pluck('class_id')->first();
+
+ $noTaxClassId = DB::table('tax_class')->insertGetId([
+ 'class_name' => 'No Tax',
+ 'class_type' => 'CUSTOMER',
+ ]);
+
+ $productTaxClassId = DB::table('tax_class')->where('class_name', 'Taxable Goods')->pluck('class_id')->first();
+
+ DB::table('tax_calculation')->insert([
+ [
+ 'tax_calculation_rate_id' => $taxRateDefaultId,
+ 'tax_calculation_rule_id' => $taxRuleDefaultId,
+ 'customer_tax_class_id' => $retailCustomerClassId,
+ 'product_tax_class_id' => $productTaxClassId,
+ ],
+ [
+ 'tax_calculation_rate_id' => $taxRateNoneId,
+ 'tax_calculation_rule_id' => $taxRuleNoneId,
+ 'customer_tax_class_id' => $noTaxClassId,
+ 'product_tax_class_id' => $productTaxClassId,
+ ],
+ ]);
+
+ DB::table('core_config_data')->upsert(
+ [
+ [
+ 'scope' => 'default',
+ 'scope_id' => 0,
+ 'path' => 'tax/defaults/country',
+ 'value' => 'NL',
+ ],
+ [
+ 'scope' => 'default',
+ 'scope_id' => 0,
+ 'path' => 'tax/display/type',
+ 'value' => 2,
+ ],
+ [
+ 'scope' => 'default',
+ 'scope_id' => 0,
+ 'path' => 'tax/display/shipping',
+ 'value' => 2,
+ ],
+ ],
+ ['scope', 'scope_id', 'path'],
+ ['value']
+ );
+
+ DB::table('customer_group')->where('customer_group_code', 'General')->update(['tax_class_id' => $noTaxClassId]);
+ }
+
+ public function browseWithConfigs(array $configs, Closure $callback)
+ {
+ $configModel = config('rapidez.models.config');
+
+ try {
+ // First we get the current value and store the new value.
+ foreach ($configs as $path => $value) {
+ $previousValues[$path] = $configModel::where(compact('path'))->first();
+ $configModel::query()->updateOrInsert(compact('path'), compact('value'));
+ }
+
+ $this->browse($callback);
+ } finally {
+ // Afterwards we update the previous values or remove new ones.
+ foreach ($configs as $path => $value) {
+ if ($previousValues[$path]) {
+ $previousValues[$path]->query()->update(['value' => $previousValues[$path]->value]);
+ } else {
+ $configModel::query()->where(compact('path'))->delete();
+ }
+ }
+ }
}
}
diff --git a/yarn.lock b/yarn.lock
index 4de33064b..1a1233983 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -344,6 +344,11 @@
lodash.merge "^4.6.2"
postcss-selector-parser "6.0.10"
+"@types/cookie@^0.5.1":
+ version "0.5.3"
+ resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.5.3.tgz#3f98076ede5e467783507284d3c19215327fff8f"
+ integrity sha512-SLg07AS9z1Ab2LU+QxzU8RCmzsja80ywjf/t5oqw+4NSH20gIGlhLOrBDm1L3PBWzPa4+wkgFQVZAjE6Ioj2ug==
+
"@types/parse-json@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
@@ -354,6 +359,11 @@
resolved "https://registry.yarnpkg.com/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz#1d12873a8e49567371f2a75fe3e7f7edca6662d8"
integrity sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==
+"@types/web-bluetooth@^0.0.18":
+ version "0.0.18"
+ resolved "https://registry.yarnpkg.com/@types/web-bluetooth/-/web-bluetooth-0.0.18.tgz#74bd1c8fd3a2058cb6fc76b188fcded50a83d866"
+ integrity sha512-v/ZHEj9xh82usl8LMR3GarzFY1IrbXJw5L4QfQhokjRV91q+SelFqxQWSep1ucXEZ22+dSTwLFkXeur25sPIbw==
+
"@vitejs/plugin-vue2@^2.2.0":
version "2.2.0"
resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue2/-/plugin-vue2-2.2.0.tgz#7453207197d6ac2b7023cedc7133b142c604c356"
@@ -373,6 +383,16 @@
postcss "^8.4.14"
source-map "^0.6.1"
+"@vueuse/core@10.5.0":
+ version "10.5.0"
+ resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-10.5.0.tgz#04d1e6d26592bb997bb755a4830ea7583c3e8612"
+ integrity sha512-z/tI2eSvxwLRjOhDm0h/SXAjNm8N5ld6/SC/JQs6o6kpJ6Ya50LnEL8g5hoYu005i28L0zqB5L5yAl8Jl26K3A==
+ dependencies:
+ "@types/web-bluetooth" "^0.0.18"
+ "@vueuse/metadata" "10.5.0"
+ "@vueuse/shared" "10.5.0"
+ vue-demi ">=0.14.6"
+
"@vueuse/core@^9.12.0":
version "9.12.0"
resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-9.12.0.tgz#e5b20f901e081c7ae5fe0e5f3af217929034eefe"
@@ -383,11 +403,32 @@
"@vueuse/shared" "9.12.0"
vue-demi "*"
+"@vueuse/integrations@^10.5.0":
+ version "10.5.0"
+ resolved "https://registry.yarnpkg.com/@vueuse/integrations/-/integrations-10.5.0.tgz#38f00bd5a1cd0160645f0c75efd5d9579061e3d6"
+ integrity sha512-fm5sXLCK0Ww3rRnzqnCQRmfjDURaI4xMsx+T+cec0ngQqHx/JgUtm8G0vRjwtonIeTBsH1Q8L3SucE+7K7upJQ==
+ dependencies:
+ "@vueuse/core" "10.5.0"
+ "@vueuse/shared" "10.5.0"
+ vue-demi ">=0.14.6"
+
+"@vueuse/metadata@10.5.0":
+ version "10.5.0"
+ resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-10.5.0.tgz#7501a88cf5cbf7a515a03f0b8bbe3cecf30cad11"
+ integrity sha512-fEbElR+MaIYyCkeM0SzWkdoMtOpIwO72x8WsZHRE7IggiOlILttqttM69AS13nrDxosnDBYdyy3C5mR1LCxHsw==
+
"@vueuse/metadata@9.12.0":
version "9.12.0"
resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-9.12.0.tgz#19a0fefcba6a66a2382af10a7a67ebad6eec1f27"
integrity sha512-9oJ9MM9lFLlmvxXUqsR1wLt1uF7EVbP5iYaHJYqk+G2PbMjY6EXvZeTjbdO89HgoF5cI6z49o2zT/jD9SVoNpQ==
+"@vueuse/shared@10.5.0":
+ version "10.5.0"
+ resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-10.5.0.tgz#b3ac8c190a5dae41db5e1b60fe304a9b4247393c"
+ integrity sha512-18iyxbbHYLst9MqU1X1QNdMHIjks6wC7XTVf0KNOv5es/Ms6gjVFCAAWTVP2JStuGqydg3DT+ExpFORUEi9yhg==
+ dependencies:
+ vue-demi ">=0.14.6"
+
"@vueuse/shared@9.12.0":
version "9.12.0"
resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-9.12.0.tgz#e6597da80084cba8fc3d6545f4c2fa9817b80428"
@@ -640,6 +681,11 @@ convert-source-map@^1.5.0, convert-source-map@^1.5.1:
dependencies:
safe-buffer "~5.1.1"
+cookie@^0.5.0:
+ version "0.5.0"
+ resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b"
+ integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==
+
core-js@^3.6.5:
version "3.26.1"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.26.1.tgz#7a9816dabd9ee846c1c0fe0e8fcad68f3709134e"
@@ -1693,6 +1739,14 @@ ts-interface-checker@^0.1.9:
resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699"
integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==
+universal-cookie@^6.1.1:
+ version "6.1.1"
+ resolved "https://registry.yarnpkg.com/universal-cookie/-/universal-cookie-6.1.1.tgz#2d619e21804c93b39ff0c77fe47dafd7a492346d"
+ integrity sha512-33S9x3CpdUnnjwTNs2Fgc41WGve2tdLtvaK2kPSbZRc5pGpz2vQFbRWMxlATsxNNe/Cy8SzmnmbuBM85jpZPtA==
+ dependencies:
+ "@types/cookie" "^0.5.1"
+ cookie "^0.5.0"
+
update-browserslist-db@^1.0.11:
version "1.0.11"
resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz#9a2a641ad2907ae7b3616506f4b977851db5b940"
@@ -1758,6 +1812,11 @@ vue-demi@*:
resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.13.11.tgz#7d90369bdae8974d87b1973564ad390182410d99"
integrity sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==
+vue-demi@>=0.14.6:
+ version "0.14.6"
+ resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.14.6.tgz#dc706582851dc1cdc17a0054f4fec2eb6df74c92"
+ integrity sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==
+
vue-highlight-words@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/vue-highlight-words/-/vue-highlight-words-1.2.0.tgz#73c2d49a46ecdb0638358b7ad749013c190ece5c"