diff --git a/app/Criteria/MyCriteria.php b/app/Criteria/MyCriteria.php new file mode 100644 index 000000000..6e32e67b2 --- /dev/null +++ b/app/Criteria/MyCriteria.php @@ -0,0 +1,27 @@ + 'Value Per Channel', 'is_filterable' => 'Use in Layered Navigation', 'is_configurable' => 'Use To Create Configurable Product', - 'admin_name' => 'Admin Name' + 'admin_name' => 'Admin Name', + 'is_visible_on_front' => 'Visible on Product View Page on Front-end' ], 'families' => [ 'families' => 'Families', diff --git a/packages/Webkul/Admin/src/Resources/views/catalog/attributes/create.blade.php b/packages/Webkul/Admin/src/Resources/views/catalog/attributes/create.blade.php index 35112e645..b6b37b540 100644 --- a/packages/Webkul/Admin/src/Resources/views/catalog/attributes/create.blade.php +++ b/packages/Webkul/Admin/src/Resources/views/catalog/attributes/create.blade.php @@ -154,6 +154,14 @@ + +
+ + +
diff --git a/packages/Webkul/Admin/src/Resources/views/catalog/attributes/edit.blade.php b/packages/Webkul/Admin/src/Resources/views/catalog/attributes/edit.blade.php index 1eb333a36..b2d5ddcf6 100644 --- a/packages/Webkul/Admin/src/Resources/views/catalog/attributes/edit.blade.php +++ b/packages/Webkul/Admin/src/Resources/views/catalog/attributes/edit.blade.php @@ -210,6 +210,18 @@ + +
+ + +
diff --git a/packages/Webkul/Attribute/src/Database/Migrations/2018_07_05_130148_create_attributes_table.php b/packages/Webkul/Attribute/src/Database/Migrations/2018_07_05_130148_create_attributes_table.php index e3db08efa..e042a1dee 100644 --- a/packages/Webkul/Attribute/src/Database/Migrations/2018_07_05_130148_create_attributes_table.php +++ b/packages/Webkul/Attribute/src/Database/Migrations/2018_07_05_130148_create_attributes_table.php @@ -27,6 +27,7 @@ class CreateAttributesTable extends Migration $table->boolean('is_filterable')->default(0); $table->boolean('is_configurable')->default(0); $table->boolean('is_user_defined')->default(1); + $table->boolean('is_visible_on_front')->default(0); $table->timestamps(); }); } diff --git a/packages/Webkul/Attribute/src/Models/Attribute.php b/packages/Webkul/Attribute/src/Models/Attribute.php index cba74b3c5..650606293 100644 --- a/packages/Webkul/Attribute/src/Models/Attribute.php +++ b/packages/Webkul/Attribute/src/Models/Attribute.php @@ -10,7 +10,7 @@ class Attribute extends TranslatableModel { public $translatedAttributes = ['name']; - protected $fillable = ['code', 'admin_name', 'type', 'position', 'is_required', 'is_unique', 'value_per_locale', 'value_per_channel', 'is_filterable', 'is_configurable']; + protected $fillable = ['code', 'admin_name', 'type', 'position', 'is_required', 'is_unique', 'value_per_locale', 'value_per_channel', 'is_filterable', 'is_configurable', 'is_visible_on_front']; protected $with = ['options']; @@ -22,14 +22,6 @@ class Attribute extends TranslatableModel return $this->hasMany(AttributeOption::class); } - /** - * Get the options. - */ - public function filter_attributes() - { - - } - /** * Scope a query to only include popular users. * diff --git a/packages/Webkul/Core/src/Core.php b/packages/Webkul/Core/src/Core.php index fbeedbd8e..5c2d895c0 100644 --- a/packages/Webkul/Core/src/Core.php +++ b/packages/Webkul/Core/src/Core.php @@ -2,6 +2,7 @@ namespace Webkul\Core; +use Carbon\Carbon; use Webkul\Core\Models\Channel as ChannelModel; use Webkul\Core\Models\Locale as LocaleModel; use Webkul\Core\Models\Currency as CurrencyModel; @@ -16,7 +17,12 @@ class Core * @return Collection */ public function getAllChannels() { - return ChannelModel::all(); + static $channels; + + if($channels) + return $channels; + + return $channels = ChannelModel::all(); } /** @@ -25,7 +31,12 @@ class Core * @return mixed */ public function getCurrentChannel() { - return ChannelModel::first(); + static $channel; + + if($channel) + return $channel; + + return $channel = ChannelModel::first(); } /** @@ -34,7 +45,12 @@ class Core * @return string */ public function getCurrentChannelCode() { - return ($channel = $this->getCurrentChannel()) ? $channel->code : ''; + static $channelCode; + + if($channelCode) + return $channelCode; + + return ($channel = $this->getCurrentChannel()) ? $channelCode = $channel->code : ''; } /** @@ -43,7 +59,12 @@ class Core * @return Collection */ public function getAllLocales() { - return LocaleModel::all(); + static $locales; + + if($locales) + return $locales; + + return $locales = LocaleModel::all(); } /** @@ -53,7 +74,12 @@ class Core */ public function getAllCurrencies() { - return CurrencyModel::all(); + static $currencies; + + if($currencies) + return $currencies; + + return $currencies = CurrencyModel::all(); } /** @@ -63,9 +89,12 @@ class Core */ public function getCurrentCurrency() { - $currency = CurrencyModel::first(); + static $currency; - return $currency; + if($currency) + return $currency; + + return $currency = CurrencyModel::first(); } /** @@ -75,7 +104,12 @@ class Core */ public function getCurrentCurrencyCode() { - return ($currency = $this->getCurrentCurrency()) ? $currency->code : ''; + static $currencyCode; + + if($currencyCode) + return $currencyCode; + + return ($currency = $this->getCurrentCurrency()) ? $currencyCode = $currency->code : ''; } /** @@ -85,7 +119,12 @@ class Core */ public function getCurrentCurrencySymbol() { - return $this->getCurrentCurrency()->symbol; + static $currencySymbol; + + if($currencySymbol) + return $currencySymbol; + + return $currencySymbol = $this->getCurrentCurrency()->symbol; } /** @@ -195,4 +234,24 @@ class Core public function getAllTaxRates() { return TaxRate::all(); } + + /** + * Format date using current channel. + * + * @param date|null $date + * @param string $format + * @return string + */ + public function formatDate($date = null, $format = 'd-m-Y H:i:s') + { + $channel = $this->getCurrentChannel(); + + if (is_null($date)) { + $date = Carbon::now(); + } + + $date->setTimezone($channel->timezone); + + return $date->format($format); + } } \ No newline at end of file diff --git a/packages/Webkul/Core/src/Http/helpers.php b/packages/Webkul/Core/src/Http/helpers.php index 22b011aed..bcc988671 100644 --- a/packages/Webkul/Core/src/Http/helpers.php +++ b/packages/Webkul/Core/src/Http/helpers.php @@ -13,7 +13,7 @@ { $results = []; - while (list($key, $values) = each($input)) { + foreach ($input as $key => $values) { if (empty($values)) { continue; } @@ -41,7 +41,7 @@ $results = array_merge($results, $append); } } - + return $results; } } diff --git a/packages/Webkul/Customer/src/Models/Customer.php b/packages/Webkul/Customer/src/Models/Customer.php index d75c7d0cc..1468c6d60 100644 --- a/packages/Webkul/Customer/src/Models/Customer.php +++ b/packages/Webkul/Customer/src/Models/Customer.php @@ -17,4 +17,8 @@ class Customer extends Authenticatable protected $table = 'customers'; protected $fillable = ['first_name','last_name','gender','date_of_birth','phone','email','password']; protected $hidden = ['password','remember_token']; + + public function getNameAttribute() { + return ucfirst($this->first_name) . ' ' . ucfirst($this->last_name); + } } diff --git a/packages/Webkul/Product/src/Contracts/Criteria/AttributeToSelectCriteria.php b/packages/Webkul/Product/src/Contracts/Criteria/AttributeToSelectCriteria.php new file mode 100644 index 000000000..cc86260e7 --- /dev/null +++ b/packages/Webkul/Product/src/Contracts/Criteria/AttributeToSelectCriteria.php @@ -0,0 +1,84 @@ +attribute = $attribute; + } + + /** + * Set attributes in attributeToSelect variable + * + * @param array|string $attributes + * + * @return mixed + */ + public function addAttribueToSelect($attributes) + { + $this->attributeToSelect = $attributes; + + return $this; + } + + /** + * Apply criteria in query repository + * + * @param string $model + * @param RepositoryInterface $repository + * + * @return mixed + */ + public function apply($model, RepositoryInterface $repository) + { + $model = $model->select('products.*'); + + foreach ($this->attributeToSelect as $code) { + $attribute = $this->attribute->findOneByField('code', $code); + + if(!$attribute) + continue; + + $productValueAlias = 'pav_' . $attribute->code; + + $model = $model->leftJoin('product_attribute_values as ' . $productValueAlias, function($qb) use($attribute, $productValueAlias) { + + $qb = $this->applyChannelLocaleFilter($attribute, $qb, $productValueAlias); + + $qb->on('products.id', $productValueAlias . '.product_id') + ->where($productValueAlias . '.attribute_id', $attribute->id); + }); + + $model = $model->addSelect($productValueAlias . '.' . ProductAttributeValue::$attributeTypeFields[$attribute->type] . ' as ' . $code); + } + + return $model; + } +} diff --git a/packages/Webkul/Product/src/Contracts/Criteria/FilterByAttributesCriteria.php b/packages/Webkul/Product/src/Contracts/Criteria/FilterByAttributesCriteria.php new file mode 100644 index 000000000..b7795fdbd --- /dev/null +++ b/packages/Webkul/Product/src/Contracts/Criteria/FilterByAttributesCriteria.php @@ -0,0 +1,88 @@ +attribute = $attribute; + } + + /** + * Apply criteria in query repository + * + * @param string $model + * @param RepositoryInterface $repository + * + * @return mixed + */ + public function apply($model, RepositoryInterface $repository) + { + $model = $model->leftJoin('products as variants', 'products.id', '=', 'variants.parent_id'); + + $model = $model->where(function($query1) use($model) { + $aliases = [ + 'products' => 'filter_', + 'variants' => 'variant_filter_' + ]; + + foreach($aliases as $table => $alias) { + $query1 = $query1->orWhere(function($query2) use($model, $table, $alias) { + + foreach (request()->input() as $code => $value) { + $aliasTemp = $alias . $code; + + $attribute = $this->attribute->findOneByField('code', $code); + + if(!$attribute) + continue; + + $model = $model->leftJoin('product_attribute_values as ' . $aliasTemp, $table . '.id', '=', $aliasTemp . '.product_id'); + + $query2 = $this->applyChannelLocaleFilter($attribute, $query2, $aliasTemp); + + $column = ProductAttributeValue::$attributeTypeFields[$attribute->type]; + + $temp = explode(',', $value); + if($attribute->type != 'price') { + $query2 = $query2->where($aliasTemp . '.attribute_id', $attribute->id); + + $query2 = $query2->where(function($query3) use($aliasTemp, $column, $temp) { + foreach($temp as $code => $filterValue) { + $query3 = $query3->orWhere($aliasTemp . '.' . $column, $filterValue); + } + }); + } else { + $query2 = $query2->where($aliasTemp . '.' . $column, '>=', current($temp)) + ->where($aliasTemp . '.' . $column, '<=', end($temp)) + ->where($aliasTemp . '.attribute_id', $attribute->id); + } + } + }); + } + }); + + return $model; + } +} \ No newline at end of file diff --git a/packages/Webkul/Product/src/Contracts/Criteria/FilterByCategoryCriteria.php b/packages/Webkul/Product/src/Contracts/Criteria/FilterByCategoryCriteria.php new file mode 100644 index 000000000..ea7952fd1 --- /dev/null +++ b/packages/Webkul/Product/src/Contracts/Criteria/FilterByCategoryCriteria.php @@ -0,0 +1,44 @@ +categoryId = $categoryId; + } + + /** + * Apply criteria in query repository + * + * @param string $model + * @param RepositoryInterface $repository + * + * @return mixed + */ + public function apply($model, RepositoryInterface $repository) + { + $model = $model->leftJoin('product_categories', 'products.id', '=', 'product_categories.product_id') + ->where('product_categories.category_id', $this->categoryId); + + return $model; + } +} diff --git a/packages/Webkul/Product/src/Contracts/Criteria/SortCriteria.php b/packages/Webkul/Product/src/Contracts/Criteria/SortCriteria.php new file mode 100644 index 000000000..5a2eddb00 --- /dev/null +++ b/packages/Webkul/Product/src/Contracts/Criteria/SortCriteria.php @@ -0,0 +1,71 @@ +attribute = $attribute; + } + + /** + * Apply criteria in query repository + * + * @param string $model + * @param RepositoryInterface $repository + * + * @return mixed + */ + public function apply($model, RepositoryInterface $repository) + { + $params = request()->input(); + + if(isset($params['sort'])) { + if($params['sort'] == 'name' || $params['sort'] == 'price') { + $attribute = $this->attribute->findOneByField('code', $params['sort']); + + $alias = 'sort_' . $params['sort']; + + $model = $model->leftJoin('product_attribute_values as ' . $alias, function($qb) use($attribute, $alias) { + + $qb = $this->applyChannelLocaleFilter($attribute, $qb, $alias); + + $qb->on('products.id', $alias . '.product_id') + ->where($alias . '.attribute_id', $attribute->id); + }); + + $model = $model->addSelect($alias . '.' . ProductAttributeValue::$attributeTypeFields[$attribute->type] . ' as ' . $attribute->code); + + $model = $model->orderBy($attribute->code, $params['order']); + + } else { + $model = $model->orderBy($params['sort'], $params['order']); + } + } else { + $model = $model->orderBy('created_at', 'desc'); + } + + return $model; + } +} diff --git a/packages/Webkul/Product/src/Database/Eloquent/Builder.php b/packages/Webkul/Product/src/Database/Eloquent/Builder.php new file mode 100644 index 000000000..9fe67f9b1 --- /dev/null +++ b/packages/Webkul/Product/src/Database/Eloquent/Builder.php @@ -0,0 +1,39 @@ +model->getPerPage(); + + $results = ($total = $this->toBase()->getCountForPagination($columns)) + ? $this->forPage($page, $perPage)->get($columns) + : $this->model->newCollection(); + + return $this->paginator($results, $total, $perPage, $page, [ + 'path' => Paginator::resolveCurrentPath(), + 'pageName' => $pageName, + ]); + } +} \ No newline at end of file diff --git a/packages/Webkul/Product/src/Models/Product.php b/packages/Webkul/Product/src/Models/Product.php index 3835c8dbe..4dfdbedce 100644 --- a/packages/Webkul/Product/src/Models/Product.php +++ b/packages/Webkul/Product/src/Models/Product.php @@ -111,7 +111,7 @@ class Product extends Model */ public function up_sells() { - return $this->belongsToMany(self::class, 'product_up_sells'); + return $this->belongsToMany(self::class, 'product_up_sells', 'parent_id', 'child_id'); } /** @@ -141,7 +141,7 @@ class Product extends Model public function getAttribute($key) { if (!method_exists(self::class, $key) && !in_array($key, ['parent_id', 'attribute_family_id']) && !isset($this->attributes[$key])) { - if ($this->isCustomAttribute($key)) { + if (isset($this->id) && $this->isCustomAttribute($key)) { $this->attributes[$key] = ''; $attributeModel = $this->attribute_family->custom_attributes()->where('attributes.code', $key)->first(); @@ -185,32 +185,46 @@ class Product extends Model $hiddenAttributes = $this->getHidden(); - $channel = request()->get('channel') ?: core()->getCurrentChannelCode(); + if(isset($this->id)) { + $channel = request()->get('channel') ?: core()->getCurrentChannelCode(); - $locale = request()->get('locale') ?: app()->getLocale(); + $locale = request()->get('locale') ?: app()->getLocale(); - foreach ($this->attribute_family->custom_attributes as $attribute) { - if (in_array($attribute->code, $hiddenAttributes)) { - continue; - } - - if($attribute->value_per_channel) { - if($attribute->value_per_locale) { - $attributeValue = $this->attribute_values()->where('channel', $channel)->where('locale', $locale)->where('attribute_id', $attribute->id)->first(); - } else { - $attributeValue = $this->attribute_values()->where('channel', $channel)->where('attribute_id', $attribute->id)->first(); + foreach ($this->attribute_family->custom_attributes as $attribute) { + if (in_array($attribute->code, $hiddenAttributes)) { + continue; } - } else { - if($attribute->value_per_locale) { - $attributeValue = $this->attribute_values()->where('locale', $locale)->where('attribute_id', $attribute->id)->first(); - } else { - $attributeValue = $this->attribute_values()->where('attribute_id', $attribute->id)->first(); - } - } - $attributes[$attribute->code] = $attributeValue[ProductAttributeValue::$attributeTypeFields[$attribute->type]]; + if($attribute->value_per_channel) { + if($attribute->value_per_locale) { + $attributeValue = $this->attribute_values()->where('channel', $channel)->where('locale', $locale)->where('attribute_id', $attribute->id)->first(); + } else { + $attributeValue = $this->attribute_values()->where('channel', $channel)->where('attribute_id', $attribute->id)->first(); + } + } else { + if($attribute->value_per_locale) { + $attributeValue = $this->attribute_values()->where('locale', $locale)->where('attribute_id', $attribute->id)->first(); + } else { + $attributeValue = $this->attribute_values()->where('attribute_id', $attribute->id)->first(); + } + } + + $attributes[$attribute->code] = $attributeValue[ProductAttributeValue::$attributeTypeFields[$attribute->type]]; + } } return $attributes; } + + /** + * Overrides the default Eloquent query builder + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @return \Illuminate\Database\Eloquent\Builder + */ + public function newEloquentBuilder($query) + { + return new \Webkul\Product\Database\Eloquent\Builder($query); + } + } \ No newline at end of file diff --git a/packages/Webkul/Product/src/Models/ProductReview.php b/packages/Webkul/Product/src/Models/ProductReview.php index 314fff1df..99231a737 100644 --- a/packages/Webkul/Product/src/Models/ProductReview.php +++ b/packages/Webkul/Product/src/Models/ProductReview.php @@ -3,8 +3,17 @@ namespace Webkul\Product\Models; use Illuminate\Database\Eloquent\Model; +use Webkul\Customer\Models\Customer; class ProductReview extends Model { protected $fillable = []; + + /** + * Get the product attribute family that owns the product. + */ + public function customer() + { + return $this->belongsTo(Customer::class); + } } \ No newline at end of file diff --git a/packages/Webkul/Product/src/Product/AbstractProduct.php b/packages/Webkul/Product/src/Product/AbstractProduct.php index 0f377df36..9971369c4 100644 --- a/packages/Webkul/Product/src/Product/AbstractProduct.php +++ b/packages/Webkul/Product/src/Product/AbstractProduct.php @@ -35,30 +35,4 @@ abstract class AbstractProduct return $qb; } - - /** - * Adds attributes to select - * - * @param QB $qb - * @return QB - */ - public function addSelectAttributes($qb) - { - foreach ($this->attributeToSelect as $code) { - $attribute = $this->attribute->findOneByField('code', $code); - - $productValueAlias = 'pav_' . $attribute->code; - - $qb->leftJoin('product_attribute_values as ' . $productValueAlias, function($leftJoin) use($attribute, $productValueAlias) { - - $leftJoin->on('products.id', $productValueAlias . '.product_id'); - - $leftJoin = $this->applyChannelLocaleFilter($attribute, $leftJoin, $productValueAlias)->where($productValueAlias . '.attribute_id', $attribute->id); - }); - - $qb->addSelect($productValueAlias . '.' . ProductAttributeValue::$attributeTypeFields[$attribute->type] . ' as ' . $code); - } - - return $qb; - } } \ No newline at end of file diff --git a/packages/Webkul/Product/src/Product/Collection.php b/packages/Webkul/Product/src/Product/Collection.php deleted file mode 100644 index e691166c6..000000000 --- a/packages/Webkul/Product/src/Product/Collection.php +++ /dev/null @@ -1,94 +0,0 @@ -product = $product; - - $this->attribute = $attribute; - } - - /** - * @param array $attributes - * @return Void - */ - public function addAttributesToSelect($attributes) - { - $this->attributeToSelect = array_unique( - array_merge($this->attributeToSelect, $attributes) - ); - - return $this; - } - - /** - * @param integer $categoryId - * @return Collection - */ - public function getCollection($categoryId = null) - { - $qb = $this->product->getModel() - ->select('products.*') - ->join('product_categories', 'products.id', '=', 'product_categories.product_id') - ->where('product_categories.category_id', $categoryId); - - $this->addSelectAttributes($qb); - - // foreach (request()->input() as $code => $value) { - // $filterAlias = 'filter_' . $code; - - // $qb->leftJoin('product_attribute_values as ' . $filterAlias, 'products.id', '=', $filterAlias . '.product_id'); - - // $qb->where($filterAlias . '.' . ProductAttributeValue::$attributeTypeFields[$attribute->type], $value); - // } - - // if(0) { - // $qb->orderBy('id', 'desc'); - // } - - return $qb->paginate(9); - } -} \ No newline at end of file diff --git a/packages/Webkul/Product/src/Product/Review.php b/packages/Webkul/Product/src/Product/Review.php index 1c08e9b41..7028d3d8a 100644 --- a/packages/Webkul/Product/src/Product/Review.php +++ b/packages/Webkul/Product/src/Product/Review.php @@ -1,16 +1,39 @@ reviews->average('rating')); } + + /** + * Returns the total review of the product + * + * @param Product $product + * @return integer + */ + public function getTotalReviews($product) + { + return $product->reviews()->count(); + } + + /** + * Returns the formated created at date + * + * @param ProductReview $review + * @return integer + */ + public function formatDate($reviewCreatedAt) + { + return core()->formatDate($reviewCreatedAt, 'd, M Y'); + } } \ No newline at end of file diff --git a/packages/Webkul/Product/src/Product/Toolbar.php b/packages/Webkul/Product/src/Product/Toolbar.php new file mode 100644 index 000000000..f048562e2 --- /dev/null +++ b/packages/Webkul/Product/src/Product/Toolbar.php @@ -0,0 +1,142 @@ + 'from-a-z', + 'name-desc' => 'from-z-a', + 'created_at-desc' => 'newest-first', + 'created_at-asc' => 'oldest-first', + 'price-asc' => 'cheapest-first', + 'price-desc' => 'expansive-first' + ]; + } + /** + * Returns available limits + * + * @param string $key + * @return string + */ + public function getAvailableLimits() + { + return [9, 15, 21, 28]; + } + + /** + * Returns the sort order url + * + * @param string $key + * @return string + */ + public function getOrderUrl($key) + { + $keys = explode('-', $key); + + return request()->fullUrlWithQuery([ + 'sort' => current($keys), + 'order' => end($keys) + ]); + } + + /** + * Returns the limit url + * + * @param integer $limit + * @return string + */ + public function getLimitUrl($limit) + { + return request()->fullUrlWithQuery([ + 'limit' => $limit + ]); + } + + /** + * Returns the mode url + * + * @param string $mode + * @return string + */ + public function getModeUrl($mode) + { + return request()->fullUrlWithQuery([ + 'mode' => $mode + ]); + } + + /** + * Checks if sort order is active + * + * @param string $key + * @return boolean + */ + public function isOrderCurrent($key) + { + $params = request()->input(); + + if(isset($params['sort']) && $key == $params['sort'] . '-' . $params['order']) + return true; + elseif(!isset($params['sort']) && $key == 'created_at-desc') + return true; + + return false; + } + + /** + * Checks if limit is active + * + * @param integer $limit + * @return boolean + */ + public function isLimitCurrent($limit) + { + $params = request()->input(); + + if(isset($params['limit']) && $limit == $params['limit']) + return true; + + return false; + } + + /** + * Checks if mode is active + * + * @param string $key + * @return boolean + */ + public function isModeActive($key) + { + $params = request()->input(); + + if(isset($params['mode']) && $key == $params['mode']) + return true; + + return false; + } + + /** + * Returns the current mode + * + * @param string $mode + * @return string + */ + public function getCurrentMode() + { + $params = request()->input(); + + if(isset($params['mode'])) + return $params['mode']; + + return 'grid'; + } +} \ No newline at end of file diff --git a/packages/Webkul/Product/src/Product/View.php b/packages/Webkul/Product/src/Product/View.php new file mode 100644 index 000000000..412e07719 --- /dev/null +++ b/packages/Webkul/Product/src/Product/View.php @@ -0,0 +1,31 @@ +attribute_family->custom_attributes; + + foreach($attributes as $attribute) { + if($attribute->is_visible_on_front) { + $data[] = [ + 'code' => $attribute->code, + 'label' => $attribute->name, + 'value' => $product->{$attribute->code}, + ]; + } + } + + return $data; + } +} \ No newline at end of file diff --git a/packages/Webkul/Product/src/Repositories/ProductAttributeValueRepository.php b/packages/Webkul/Product/src/Repositories/ProductAttributeValueRepository.php index d7e4f0701..ca7aaf449 100644 --- a/packages/Webkul/Product/src/Repositories/ProductAttributeValueRepository.php +++ b/packages/Webkul/Product/src/Repositories/ProductAttributeValueRepository.php @@ -74,7 +74,7 @@ class ProductAttributeValueRepository extends Repository */ public function isValueUnique($productId, $attributeId, $column, $value) { - $result = $this->resetScope()->model->where($column, $value)->where('attribute_id', '!=', $attributeId)->where('product_id', '!=', $productId)->get(); + $result = $this->resetScope()->model->where($column, $value)->where('attribute_id', '=', $attributeId)->where('product_id', '!=', $productId)->get(); return $result->count() ? false : true; } diff --git a/packages/Webkul/Product/src/Repositories/ProductRepository.php b/packages/Webkul/Product/src/Repositories/ProductRepository.php index f472bbe05..9bbb48d87 100644 --- a/packages/Webkul/Product/src/Repositories/ProductRepository.php +++ b/packages/Webkul/Product/src/Repositories/ProductRepository.php @@ -10,6 +10,11 @@ use Webkul\Product\Repositories\ProductAttributeValueRepository; use Webkul\Product\Repositories\ProductInventoryRepository; use Webkul\Product\Repositories\ProductImageRepository; use Webkul\Product\Models\ProductAttributeValue; +use Webkul\Product\Contracts\Criteria\SortCriteria; +use Webkul\Product\Contracts\Criteria\AttributeToSelectCriteria; +use Webkul\Product\Contracts\Criteria\FilterByAttributesCriteria; +use Webkul\Product\Contracts\Criteria\FilterByCategoryCriteria; +use Illuminate\Database\Eloquent\ModelNotFoundException; /** * Product Repository @@ -177,6 +182,8 @@ class ProductRepository extends Repository } } + $previousVariantIds = $product->variants->pluck('id'); + if(isset($data['variants'])) { foreach ($data['variants'] as $variantId => $variantData) { if (str_contains($variantId, 'variant_')) { @@ -187,13 +194,22 @@ class ProductRepository extends Repository $this->createVariant($product, $permutation, $variantData); } else { + if(is_numeric($index = $previousVariantIds->search($variantId))) { + $previousVariantIds->forget($index); + } + $variantData['channel'] = $data['channel']; $variantData['locale'] = $data['locale']; + $this->updateVariant($variantData, $variantId); } } } + foreach ($previousVariantIds as $variantId) { + $this->delete($variantId); + } + $this->productInventory->saveInventories($data, $product); $this->productImage->uploadImages($data, $product); @@ -358,4 +374,53 @@ class ProductRepository extends Repository return false; } + + /** + * @param integer $categoryId + * @return Collection + */ + public function findAllByCategory($categoryId = null) + { + $this->pushCriteria(app(SortCriteria::class)); + $this->pushCriteria(app(FilterByAttributesCriteria::class)); + $this->pushCriteria(new FilterByCategoryCriteria($categoryId)); + $this->pushCriteria(app(AttributeToSelectCriteria::class)->addAttribueToSelect([ + 'name', + 'description', + 'short_description', + 'price', + 'special_price', + 'special_price_from', + 'special_price_to' + ])); + + $params = request()->input(); + + return $this->scopeQuery(function($query){ + return $query->distinct()->addSelect('products.*'); + })->paginate(isset($params['limit']) ? $params['limit'] : 9, ['products.id']); + } + + /** + * Retrive product from slug + * + * @param string $slug + * @return mixed + */ + public function findBySlugOrFail($slug) + { + $attribute = $this->attribute->findOneByField('code', 'url_key'); + + $attributeValue = $this->attributeValue->findOneWhere([ + 'attribute_id' => $attribute->id, + ProductAttributeValue::$attributeTypeFields[$attribute->type] => $slug + ]); + + if($attributeValue && $attributeValue->product) + return $attributeValue->product; + + throw (new ModelNotFoundException)->setModel( + get_class($this->model), $slug + ); + } } \ No newline at end of file diff --git a/packages/Webkul/Product/src/Repositories/ProductReviewRepository.php b/packages/Webkul/Product/src/Repositories/ProductReviewRepository.php new file mode 100644 index 000000000..e8bff8b5e --- /dev/null +++ b/packages/Webkul/Product/src/Repositories/ProductReviewRepository.php @@ -0,0 +1,24 @@ + + * @copyright 2018 Webkul Software Pvt Ltd (http://www.webkul.com) + */ +class ProductReviewRepository extends Repository +{ + /** + * Specify Model class name + * + * @return mixed + */ + function model() + { + return 'Webkul\Product\Models\ProductReview'; + } +} \ No newline at end of file diff --git a/packages/Webkul/Shop/src/Http/Controllers/ProductController.php b/packages/Webkul/Shop/src/Http/Controllers/ProductController.php new file mode 100644 index 000000000..b94ba5ade --- /dev/null +++ b/packages/Webkul/Shop/src/Http/Controllers/ProductController.php @@ -0,0 +1,57 @@ + + * @copyright 2018 Webkul Software Pvt Ltd (http://www.webkul.com) + */ +class ProductController extends Controller +{ + + /** + * Contains route related configuration + * + * @var array + */ + protected $_config; + + /** + * ProductRepository object + * + * @var array + */ + protected $product; + + /** + * Create a new controller instance. + * + * @param Webkul\Product\Repositories\ProductRepository $product + * @return void + */ + public function __construct( Product $product) + { + $this->product = $product; + + $this->_config = request('_config'); + } + + /** + * Display a listing of the resource. + * + * @param string $slug + * @return \Illuminate\Http\Response + */ + public function index($slug) + { + $product = $this->product->findBySlugOrFail($slug); + + return view($this->_config['view'], compact('product')); + } +} diff --git a/packages/Webkul/Shop/src/Http/Controllers/ReviewController.php b/packages/Webkul/Shop/src/Http/Controllers/ReviewController.php new file mode 100644 index 000000000..68d85afab --- /dev/null +++ b/packages/Webkul/Shop/src/Http/Controllers/ReviewController.php @@ -0,0 +1,100 @@ + + * @copyright 2018 Webkul Software Pvt Ltd (http://www.webkul.com) + */ +class ReviewController extends Controller +{ + + /** + * Contains route related configuration + * + * @var array + */ + protected $_config; + + /** + * ProductRepository object + * + * @var array + */ + protected $product; + + /** + * ProductReviewRepository object + * + * @var array + */ + protected $productReview; + + /** + * Create a new controller instance. + * + * @param Webkul\Product\Repositories\ProductRepository $product + * @param Webkul\Product\Repositories\ProductReviewRepository $productReview + * @return void + */ + public function __construct(Product $product, ProductReview $productReview) + { + $this->product = $product; + + $this->productReview = $productReview; + + $this->_config = request('_config'); + } + + /** + * Display a listing of the resource. + * + * @param string $slug + * @return \Illuminate\Http\Response + */ + public function index($slug) + { + $product = $this->product->findBySlugOrFail($slug); + + return view($this->_config['view'], compact('product')); + } + + /** + * Show the form for creating a new resource. + * + * @param string $slug + * @return \Illuminate\Http\Response + */ + public function create($slug) + { + $product = $this->product->findBySlugOrFail($slug); + + return view($this->_config['view'], compact('product')); + } + + /** + * Store a newly created resource in storage. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + */ + public function store(Request $request) + { + $this->validate(request(), [ + 'name' => 'required', + ]); + + $this->productReview->create(request()->all()); + + session()->flash('success', 'Review submitted successfully.'); + + return redirect()->route($this->_config['redirect']); + } +} diff --git a/packages/Webkul/Shop/src/Http/routes.php b/packages/Webkul/Shop/src/Http/routes.php index 4bf0917f8..ab10753f7 100644 --- a/packages/Webkul/Shop/src/Http/routes.php +++ b/packages/Webkul/Shop/src/Http/routes.php @@ -8,9 +8,29 @@ Route::group(['middleware' => ['web']], function () { Route::get('/categories/{slug}', 'Webkul\Shop\Http\Controllers\CategoryController@index')->defaults('_config', [ 'view' => 'shop::products.index' - ]); + ])->name('shop.categories.index'); - Route::view('/products/{slug}', 'shop::store.product.details.index'); + Route::get('/products/{slug}', 'Webkul\Shop\Http\Controllers\ProductController@index')->defaults('_config', [ + 'view' => 'shop::products.view' + ])->name('shop.products.index'); + + + // Product Review routes + Route::get('/reviews/{slug}', 'Webkul\Shop\Http\Controllers\ReviewController@index')->defaults('_config', [ + 'view' => 'shop::products.reviews.index' + ])->name('shop.reviews.index'); + + Route::get('/reviews/create/{slug}', 'Webkul\Shop\Http\Controllers\ReviewController@create')->defaults('_config', [ + 'view' => 'shop::products.reviews.create' + ])->name('shop.reviews.create'); + + Route::post('/reviews/create/{slug}', 'Webkul\Core\Http\Controllers\ReviewController@store')->defaults('_config', [ + 'redirect' => 'admin.reviews.index' + ])->name('admin.reviews.store'); + + + // Route::view('/products/{slug}', 'shop::store.product.details.index'); + Route::view('/cart', 'shop::store.product.view.cart.index'); //customer routes starts here Route::prefix('customer')->group(function () { diff --git a/packages/Webkul/Shop/src/Resources/assets/sass/_variables.scss b/packages/Webkul/Shop/src/Resources/assets/sass/_variables.scss index cbea7f1fd..dde936d7c 100644 --- a/packages/Webkul/Shop/src/Resources/assets/sass/_variables.scss +++ b/packages/Webkul/Shop/src/Resources/assets/sass/_variables.scss @@ -1,4 +1,6 @@ //shop variables +$font-color: #242424; +$font-size-base: 16px; $font-name: "Montserrat", sans-serif; $background-color: #f2f2f2; $footer-back: #f2f2f2; @@ -23,6 +25,7 @@ $horizontal-rule-color: #E8E8E8; //product $real-price:#A5A5A5; $product-font-color: #242424; +$product-special-price-color: #FF6472; $product-price-color: #FF6472; $dark-blue-shade: #0031F0; $bar-color: #D8D8D8; diff --git a/packages/Webkul/Shop/src/Resources/assets/sass/app.scss b/packages/Webkul/Shop/src/Resources/assets/sass/app.scss index 8746abf03..43f8929c8 100644 --- a/packages/Webkul/Shop/src/Resources/assets/sass/app.scss +++ b/packages/Webkul/Shop/src/Resources/assets/sass/app.scss @@ -8,7 +8,8 @@ body { margin: 0; padding: 0; font-weight: 500; - font-size: 14px; + color: $font-color; + font-size: $font-size-base; } * { @@ -196,7 +197,7 @@ body { .nav a { display:block; - color: #242424; + color: $font-color; text-decoration: none; padding: 0.8em 0.3em 0.8em 0.5em; text-transform: uppercase; @@ -356,13 +357,13 @@ section.slider-block { .layered-filter-wrapper { width: 25%; float: left; - margin-right: 20px; + padding-right: 20px; min-height: 1px; .filter-title { border-bottom: 1px solid #E8E8E8; font-size: 16px; - color: #242424; + color: $font-color; padding: 10px 0; } @@ -442,6 +443,7 @@ section.slider-block { } } + .main-container-wrapper { margin-left: 10%; margin-right: 10%; @@ -450,6 +452,11 @@ section.slider-block { width: 100%; } + .main { + display: inline-block; + width: 75% + } + .product-grid { display: grid; grid-gap: 15px; @@ -470,58 +477,129 @@ section.slider-block { display: flex; flex-flow: column; justify-content: center; + } + } - .product-image img { - align-self: center; - width: 100%; - margin-bottom: 14px; + .product-card { + + .product-image img { + align-self: center; + width: 100%; + margin-bottom: 14px; + } + + .product-name { + font-size: 16px; + margin-bottom: 14px; + width: 100%; + color: $font-color; + + a { + color: $font-color; + } + } + + .product-description { + font-size: 16px; + margin-bottom: 14px; + display: none; + } + + .product-ratings { + width: 100%; + margin-bottom: 14px; + } + + .cart-fav-seg { + display: inline-flex; + width: 100%; + align-items: center; + + .addtocart { + border-radius: 0px; + margin-right: 10px; + text-transform: uppercase; + } + } + } + + .product-list { + .product-card { + width: 100%; + display: inline-block; + margin-bottom: 20px; + + .product-image { + float: left; + width: 30%; + height: 350px; + + img { + height: 100%; + } } - .product-name { + .product-information { + float: right; + width: 70%; + padding-left: 30px; + } + + &:last-child { + margin-bottom: 0; + } + } + } + + .top-toolbar { + width: 100%; + display: inline-block; + margin-bottom: 25px; + + .page-info { + float: left; + color: $font-color; + line-height: 45px; + } + + .pager { + float: right; + + label { font-size: 16px; - margin-bottom: 14px; - width: 100%; + margin-right: 5px; } - .product-price { + select { + background: #FFFFFF; + border: 1px solid #C7C7C7; + border-radius: 3px; font-size: 16px; - margin-bottom: 14px; - width: 100%; - font-weight: 600; + color: $font-color; + padding: 10px; + } - .price-label { - font-size: 14px; - font-weight: 400; - } + .view-mode { + display: inline-block; + margin-right: 20px; - .regular-price { - font-size: 16px; - color: #A5A5A5; - text-decoration: line-through; - margin-right: 10px; - } + a, span { + display: inline-block; + vertical-align: middle; - .special-price { - font-size: 16px; - color: #FF6472; + &.grid-view { + margin-right: 10px; + } } } - .product-ratings { - width: 100%; - margin-bottom: 14px; + .sorter { + display: inline-block; + margin-right: 10px; } - .cart-fav-seg { - display: inline-flex; - width: 100%; - align-items: center; - - .addtocart { - border-radius: 0px; - margin-right: 10px; - text-transform: uppercase; - } + .limiter { + display: inline-block; } } } @@ -601,6 +679,30 @@ section.slider-block { } } +.product-price { + font-size: 16px; + margin-bottom: 14px; + width: 100%; + font-weight: 600; + + .price-label { + font-size: 14px; + font-weight: 400; + } + + .regular-price { + font-size: 16px; + color: #A5A5A5; + text-decoration: line-through; + margin-right: 10px; + } + + .special-price { + font-size: 16px; + color: #FF6472; + } +} + .footer { background-color: $footer-back; padding-left: 10%; @@ -804,7 +906,7 @@ section.slider-block { .section-head { .profile-heading { font-size: 28px; - color: #242424; + color: $font-color; text-transform: capitalize; text-align: left; } @@ -935,18 +1037,8 @@ section.product-detail, section.product-review { margin-bottom: 20px; } - .price { + .product-price { margin-bottom: 14px; - - .main-price { - font-size: 24px; - color: $product-price-color; - } - - .real-price { - color: $real-price; - text-decoration-line: line-through; - } } } } @@ -1082,12 +1174,12 @@ section.product-detail, section.product-review { margin-bottom: 14px; } - .price { + .product-price { margin-bottom: 14px; + font-size: 24px; - .main-price { + .special-price { font-size: 24px; - color: $product-price-color; } } @@ -1099,9 +1191,21 @@ section.product-detail, section.product-review { margin-bottom: 14px; } - hr{ - border-top: 1px solid $horizontal-rule-color; - margin-bottom: 17px; + .full-specifications { + td { + padding: 10px 0; + color: #5E5E5E; + + &:first-child { + padding-right: 40px; + } + } + } + + .accordian .accordian-header { + font-size: 16px; + padding-left: 0; + font-weight: 600; } .attributes { @@ -1183,21 +1287,21 @@ section.product-detail, section.product-review { font-size: 16px; } - .full-specification{ - - } - .rating-reviews { margin-top: 30px; - .title{ + .title { margin-bottom: 15px; + font-weight: 600; } .overall { + margin-bottom: 5px; - .number{ - font-size: 34px; + .review-info { + .number { + font-size: 34px; + } } button { @@ -1205,7 +1309,6 @@ section.product-detail, section.product-review { border-radius: 0px !important; } - margin-bottom: 5px; } .reviews { @@ -1217,11 +1320,23 @@ section.product-detail, section.product-review { .stars { margin-bottom: 15px; + display: inline-block; + + .icon { + width: 18px; + height: 18px; + } } + .message { margin-bottom: 10px; } + + .reviewer-details { + color: #5E5E5E; + } } + .view-all { margin-top:15px; color: $logo-color; @@ -1230,33 +1345,7 @@ section.product-detail, section.product-review { } } } - } - - // .related-products-wrapper { - // margin-bottom: 80px; - - // .title{ - // margin-bottom: 22px; - // text-align: center; - // } - - // .horizontal-rule { - // height: 1px; - // background: $horizontal-rule-color; - // width: 148px; - // margin-bottom: 24px; - // margin-left:auto; - // margin-right:auto; - // } - - // .related-products { - // display: grid; - // grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); - // grid-gap: 10px; - // } - - // } } /* cart pages and elements css begins here */ @@ -1485,14 +1574,25 @@ section.cart { } -.related-products-wrapper { +.attached-products-wrapper { margin-bottom: 80px; - .title{ - margin-bottom: 22px; + .title { + margin-bottom: 40px; font-size: 18px; color: $product-font-color; text-align: center; + position: relative; + + .border-bottom { + border-bottom: 1px solid rgba(162, 162, 162, 0.2); + display: inline-block; + width: 100px; + position: absolute; + top: 40px; + left: 50%; + margin-left: -50px; + } } .horizontal-rule { @@ -1503,11 +1603,4 @@ section.cart { margin-left:auto; margin-right:auto; } - - .related-products { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); - grid-gap: 10px; - } - } diff --git a/packages/Webkul/Shop/src/Resources/assets/sass/icons.scss b/packages/Webkul/Shop/src/Resources/assets/sass/icons.scss index 32fa03309..e8ce2979e 100644 --- a/packages/Webkul/Shop/src/Resources/assets/sass/icons.scss +++ b/packages/Webkul/Shop/src/Resources/assets/sass/icons.scss @@ -2,10 +2,23 @@ display: inline-block; background-size: cover; } + .dropdown-right-icon{ background-image:URL('../images/icon-dropdown-left.svg'); width: 8px; height: 8px; margin-left:auto; margin-bottom: 2px; +} + +.grid-view-icon { + background-image:URL('../images/icon-grid-view.svg'); + width: 24px; + height: 24px; +} + +.list-view-icon { + background-image:URL('../images/icon-list-view.svg'); + width: 24px; + height: 24px; } \ No newline at end of file diff --git a/packages/Webkul/Shop/src/Resources/lang/en/app.php b/packages/Webkul/Shop/src/Resources/lang/en/app.php index 3ad47913a..914f33216 100644 --- a/packages/Webkul/Shop/src/Resources/lang/en/app.php +++ b/packages/Webkul/Shop/src/Resources/lang/en/app.php @@ -35,6 +35,20 @@ return [ 'products' => [ 'layered-nav-title' => 'Shop By', 'price-label' => 'As low as', - 'remove-filter-link-title' => 'Clear All' + 'remove-filter-link-title' => 'Clear All', + 'sort-by' => 'Sort By', + 'from-a-z' => 'From A-Z', + 'from-z-a' => 'From Z-A', + 'newest-first' => 'Newest First', + 'oldest-first' => 'Oldest First', + 'cheapest-first' => 'Cheapest First', + 'expansive-first' => 'Expansive First', + 'show' => 'Show', + 'pager-info' => 'Showing :showing of :total Items', + 'description' => 'Description', + 'specification' => 'Specification', + 'total-reviews' => ':total Reviews', + 'by' => 'By :name', + 'up-sell-title' => 'We found other products you might like!' ] ]; \ No newline at end of file diff --git a/packages/Webkul/Shop/src/Resources/views/checkout/cart/index.blade.php b/packages/Webkul/Shop/src/Resources/views/checkout/cart/index.blade.php new file mode 100644 index 000000000..e69de29bb diff --git a/packages/Webkul/Shop/src/Resources/views/checkout/onepage/index.blade.php b/packages/Webkul/Shop/src/Resources/views/checkout/onepage/index.blade.php new file mode 100644 index 000000000..e69de29bb diff --git a/packages/Webkul/Shop/src/Resources/views/products/add-to-cart.blade.php b/packages/Webkul/Shop/src/Resources/views/products/add-to-cart.blade.php index 5aa534dc9..b156d03f0 100644 --- a/packages/Webkul/Shop/src/Resources/views/products/add-to-cart.blade.php +++ b/packages/Webkul/Shop/src/Resources/views/products/add-to-cart.blade.php @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/packages/Webkul/Shop/src/Resources/views/products/card.blade.php b/packages/Webkul/Shop/src/Resources/views/products/card.blade.php deleted file mode 100644 index 451993e5e..000000000 --- a/packages/Webkul/Shop/src/Resources/views/products/card.blade.php +++ /dev/null @@ -1,20 +0,0 @@ -
-
- -
- -
- {{ $product->name }} -
- - @include ('shop::products.price', ['product' => $product]) - - @if ($product->reviews->count()) - - @include ('shop::products.review', ['product' => $product]) - - @endif - - @include ('shop::products.add-to', ['product' => $product]) - -
\ No newline at end of file diff --git a/packages/Webkul/Shop/src/Resources/views/products/index.blade.php b/packages/Webkul/Shop/src/Resources/views/products/index.blade.php index 303f53038..308e4c098 100644 --- a/packages/Webkul/Shop/src/Resources/views/products/index.blade.php +++ b/packages/Webkul/Shop/src/Resources/views/products/index.blade.php @@ -2,24 +2,40 @@ @section('content-wrapper') - @include ('shop::products.layered-navigation') + @include ('shop::products.list.layered-navigation')
-
+ @inject ('productRepository', 'Webkul\Product\Repositories\ProductRepository') - @inject ('productHelper', 'Webkul\Product\Product\Collection') - - getCollection($category->id); ?> - - @foreach ($products as $product) + findAllByCategory($category->id); ?> - @include ('shop::products.card', ['product' => $product]) + @include ('shop::products.list.toolbar') - @endforeach + @inject ('toolbarHelper', 'Webkul\Product\Product\Toolbar') -
+ @if ($toolbarHelper->getCurrentMode() == 'grid') +
+ + @foreach ($products as $product) + @include ('shop::products.list.card', ['product' => $product]) + + @endforeach + +
+ @else +
+ + @foreach ($products as $product) + + @include ('shop::products.list.card', ['product' => $product]) + + @endforeach + +
+ @endif +
{{ $products->appends(request()->input())->links() }} @@ -28,8 +44,4 @@
-@stop - -@push('scripts') - -@endpush \ No newline at end of file +@stop \ No newline at end of file diff --git a/packages/Webkul/Shop/src/Resources/views/products/list/card.blade.php b/packages/Webkul/Shop/src/Resources/views/products/list/card.blade.php new file mode 100644 index 000000000..34f182e1a --- /dev/null +++ b/packages/Webkul/Shop/src/Resources/views/products/list/card.blade.php @@ -0,0 +1,35 @@ +
+
+ + + +
+ +
+ +
+ + {{ $product->id }} + + + {{ $product->name }} + +
+ +
+ {{ $product->short_description }} +
+ + @include ('shop::products.price', ['product' => $product]) + + @if ($product->reviews->count()) + + @include ('shop::products.review', ['product' => $product]) + + @endif + + @include ('shop::products.add-to', ['product' => $product]) + +
+ +
\ No newline at end of file diff --git a/packages/Webkul/Shop/src/Resources/views/products/layered-navigation.blade.php b/packages/Webkul/Shop/src/Resources/views/products/list/layered-navigation.blade.php similarity index 97% rename from packages/Webkul/Shop/src/Resources/views/products/layered-navigation.blade.php rename to packages/Webkul/Shop/src/Resources/views/products/list/layered-navigation.blade.php index 2ebce2670..ed9764ac9 100644 --- a/packages/Webkul/Shop/src/Resources/views/products/layered-navigation.blade.php +++ b/packages/Webkul/Shop/src/Resources/views/products/list/layered-navigation.blade.php @@ -1,7 +1,9 @@ @inject ('attributeRepository', 'Webkul\Attribute\Repositories\AttributeRepository')
+ +
@push('scripts') @@ -129,8 +131,8 @@ sliderConfig: { value: [ - 100, - 250 + 0, + 0 ], max: 500, processStyle: { @@ -171,7 +173,7 @@ clearFilters () { if(this.attribute.type == 'price') { - this.sliderConfig.value = [100, 250]; + this.sliderConfig.value = [0, 0]; } this.appliedFilters = []; diff --git a/packages/Webkul/Shop/src/Resources/views/products/list/toolbar.blade.php b/packages/Webkul/Shop/src/Resources/views/products/list/toolbar.blade.php new file mode 100644 index 000000000..f189f9b3f --- /dev/null +++ b/packages/Webkul/Shop/src/Resources/views/products/list/toolbar.blade.php @@ -0,0 +1,67 @@ +@inject ('toolbarHelper', 'Webkul\Product\Product\Toolbar') + +
+ +
+ {{ __('shop::app.products.pager-info', ['showing' => $products->firstItem() . '-' . $products->lastItem(), 'total' => $products->total()]) }} +
+ +
+ +
+ @if ($toolbarHelper->isModeActive('grid')) + + + + @else + + + + @endif + + @if ($toolbarHelper->isModeActive('list')) + + + + @else + + + + @endif +
+ +
+ + + +
+ +
+ + + +
+ +
+ +
\ No newline at end of file diff --git a/packages/Webkul/Shop/src/Resources/views/products/reviews/create.blade.php b/packages/Webkul/Shop/src/Resources/views/products/reviews/create.blade.php new file mode 100644 index 000000000..e69de29bb diff --git a/packages/Webkul/Shop/src/Resources/views/products/reviews/index.blade.php b/packages/Webkul/Shop/src/Resources/views/products/reviews/index.blade.php new file mode 100644 index 000000000..e69de29bb diff --git a/packages/Webkul/Shop/src/Resources/views/products/reviews/product.blade.php b/packages/Webkul/Shop/src/Resources/views/products/reviews/product.blade.php new file mode 100644 index 000000000..e69de29bb diff --git a/packages/Webkul/Shop/src/Resources/views/products/view.blade.php b/packages/Webkul/Shop/src/Resources/views/products/view.blade.php new file mode 100644 index 000000000..8e776dd5f --- /dev/null +++ b/packages/Webkul/Shop/src/Resources/views/products/view.blade.php @@ -0,0 +1,65 @@ +@extends('shop::layouts.master') + +@section('content-wrapper') +
+
+ + Home > Men > Slit Open Jeans + +
+ +
+ + @include ('shop::products.view.gallery') + +
+ +
+ {{ $product->name }} +
+ +
+ + 75 Ratings & 11 Reviews +
+ + @include ('shop::products.price', ['product' => $product]) + + @include ('shop::products.view.stock') + +
+ +
+ {{ $product->short_description }} +
+ + @if ($product->type == 'configurable') + + @include ('shop::products.view.configurable-options') + + @endif + + +
+ {{ __('shop::app.products.description') }} + +
+ +
+
+ {{ $product->description }} +
+
+
+ + @include ('shop::products.view.attributes') + + @include ('shop::products.view.reviews') + +
+
+ + @include ('shop::products.view.up-sells') + +
+@endsection diff --git a/packages/Webkul/Shop/src/Resources/views/products/view/attributes.blade.php b/packages/Webkul/Shop/src/Resources/views/products/view/attributes.blade.php new file mode 100644 index 000000000..c78988d35 --- /dev/null +++ b/packages/Webkul/Shop/src/Resources/views/products/view/attributes.blade.php @@ -0,0 +1,23 @@ +@inject ('productViewHelper', 'Webkul\Product\Product\View') + + +
+ {{ __('shop::app.products.specification') }} + +
+ +
+ + + @foreach ($productViewHelper->getAdditionalData($product) as $attribute) + + + + + + + @endforeach + +
{{ $attribute['label'] }} - {{ $attribute['value'] }}
+
+
\ No newline at end of file diff --git a/packages/Webkul/Shop/src/Resources/views/products/view/configurable-options.blade.php b/packages/Webkul/Shop/src/Resources/views/products/view/configurable-options.blade.php new file mode 100644 index 000000000..1ebdb1f39 --- /dev/null +++ b/packages/Webkul/Shop/src/Resources/views/products/view/configurable-options.blade.php @@ -0,0 +1,33 @@ +
+ +
+
Color
+ +
+
+
+
+
+
+ +
+
Size
+ +
+
XL
+
XXL
+
XXXL
+
+
+ +
+
Quantity
+ +
+
1
+
+
+ +
+ +
\ No newline at end of file diff --git a/packages/Webkul/Shop/src/Resources/views/products/view/gallery.blade.php b/packages/Webkul/Shop/src/Resources/views/products/view/gallery.blade.php new file mode 100644 index 000000000..bbe5e664d --- /dev/null +++ b/packages/Webkul/Shop/src/Resources/views/products/view/gallery.blade.php @@ -0,0 +1,16 @@ +
+ +
+ + + + +
+ +
+ + + +
+ +
\ No newline at end of file diff --git a/packages/Webkul/Shop/src/Resources/views/products/view/reviews.blade.php b/packages/Webkul/Shop/src/Resources/views/products/view/reviews.blade.php new file mode 100644 index 000000000..b84816615 --- /dev/null +++ b/packages/Webkul/Shop/src/Resources/views/products/view/reviews.blade.php @@ -0,0 +1,71 @@ +@inject ('reviewHelper', 'Webkul\Product\Product\Review') + +@if ($total = $reviewHelper->getTotalReviews($product)) +
+
+ Ratings & Reviews +
+ +
+
+ + + {{ $reviewHelper->getAverageRating($product) }} + + + + @for ($i = 1; $i <= $reviewHelper->getAverageRating($product); $i++) + + + + @endfor + + +
+ {{ __('shop::app.products.total-reviews', ['total' => $total]) }} +
+ +
+ + Write Review + +
+ +
+ + @foreach ($product->reviews()->paginate(5) as $review) +
+
+ {{ $review->title }} +
+ + + @for ($i = 1; $i <= $review->rating; $i++) + + + + @endfor + + +
+ {{ $review->comment }} +
+ +
+ + {{ __('shop::app.products.by', ['name' => $review->customer->name]) }}, + + + + {{ $reviewHelper->formatDate($review->created_at) }} + +
+
+ @endforeach + + View All + +
+
+
+@endif \ No newline at end of file diff --git a/packages/Webkul/Shop/src/Resources/views/products/view/stock.blade.php b/packages/Webkul/Shop/src/Resources/views/products/view/stock.blade.php new file mode 100644 index 000000000..e765ddda0 --- /dev/null +++ b/packages/Webkul/Shop/src/Resources/views/products/view/stock.blade.php @@ -0,0 +1,3 @@ +
+ InStock +
\ No newline at end of file diff --git a/packages/Webkul/Shop/src/Resources/views/products/view/up-sells.blade.php b/packages/Webkul/Shop/src/Resources/views/products/view/up-sells.blade.php new file mode 100644 index 000000000..e9b561e75 --- /dev/null +++ b/packages/Webkul/Shop/src/Resources/views/products/view/up-sells.blade.php @@ -0,0 +1,20 @@ +@if ($product->up_sells()->count()) +
+ +
+ {{ __('shop::app.products.up-sell-title') }} + +
+ +
+ + @foreach ($product->up_sells()->paginate(4) as $up_sell_product) + + @include ('shop::products.list.card', ['product' => $up_sell_product]) + + @endforeach + +
+ +
+@endif \ No newline at end of file diff --git a/packages/Webkul/User/src/Http/Controllers/AccountController.php b/packages/Webkul/User/src/Http/Controllers/AccountController.php index be7fb8772..94b89fde9 100644 --- a/packages/Webkul/User/src/Http/Controllers/AccountController.php +++ b/packages/Webkul/User/src/Http/Controllers/AccountController.php @@ -56,8 +56,12 @@ class AccountController extends Controller 'password' => 'nullable|confirmed' ]); + $data = request()->all(); - $user->update(request(['name', 'email', 'password'])); + if(!$data['password']) + unset($data['password']); + + $user->update($data); session()->flash('success', 'Account changes saved successfully.'); diff --git a/packages/Webkul/User/src/Http/Controllers/UserController.php b/packages/Webkul/User/src/Http/Controllers/UserController.php index 41b00bd49..e89e3c8a8 100644 --- a/packages/Webkul/User/src/Http/Controllers/UserController.php +++ b/packages/Webkul/User/src/Http/Controllers/UserController.php @@ -118,7 +118,12 @@ class UserController extends Controller */ public function update(UserForm $request, $id) { - $this->admin->update(request()->all(), $id); + $data = request()->all(); + + if(!$data['password']) + unset($data['password']); + + $this->admin->update($data, $id); session()->flash('success', 'User updated successfully.'); diff --git a/public/themes/default/assets/css/shop.css b/public/themes/default/assets/css/shop.css index 57c7a7337..e13993b9c 100644 --- a/public/themes/default/assets/css/shop.css +++ b/public/themes/default/assets/css/shop.css @@ -11,11 +11,24 @@ margin-bottom: 2px; } +.grid-view-icon { + background-image: URL("../images/icon-grid-view.svg"); + width: 24px; + height: 24px; +} + +.list-view-icon { + background-image: URL("../images/icon-list-view.svg"); + width: 24px; + height: 24px; +} + body { margin: 0; padding: 0; font-weight: 500; - font-size: 14px; + color: #242424; + font-size: 16px; } * { @@ -430,7 +443,7 @@ section.slider-block div.slider-content div.slider-control .light-right-icon { .layered-filter-wrapper { width: 25%; float: left; - margin-right: 20px; + padding-right: 20px; min-height: 1px; } @@ -521,6 +534,11 @@ section.slider-block div.slider-content div.slider-control .light-right-icon { width: 100%; } +.main-container-wrapper .main { + display: inline-block; + width: 75%; +} + .main-container-wrapper .product-grid { display: grid; grid-gap: 15px; @@ -551,49 +569,36 @@ section.slider-block div.slider-content div.slider-control .light-right-icon { justify-content: center; } -.main-container-wrapper .product-grid .product-card .product-image img { +.main-container-wrapper .product-card .product-image img { -ms-flex-item-align: center; align-self: center; width: 100%; margin-bottom: 14px; } -.main-container-wrapper .product-grid .product-card .product-name { +.main-container-wrapper .product-card .product-name { font-size: 16px; margin-bottom: 14px; width: 100%; + color: #242424; } -.main-container-wrapper .product-grid .product-card .product-price { +.main-container-wrapper .product-card .product-name a { + color: #242424; +} + +.main-container-wrapper .product-card .product-description { font-size: 16px; margin-bottom: 14px; - width: 100%; - font-weight: 600; + display: none; } -.main-container-wrapper .product-grid .product-card .product-price .price-label { - font-size: 14px; - font-weight: 400; -} - -.main-container-wrapper .product-grid .product-card .product-price .regular-price { - font-size: 16px; - color: #A5A5A5; - text-decoration: line-through; - margin-right: 10px; -} - -.main-container-wrapper .product-grid .product-card .product-price .special-price { - font-size: 16px; - color: #FF6472; -} - -.main-container-wrapper .product-grid .product-card .product-ratings { +.main-container-wrapper .product-card .product-ratings { width: 100%; margin-bottom: 14px; } -.main-container-wrapper .product-grid .product-card .cart-fav-seg { +.main-container-wrapper .product-card .cart-fav-seg { display: -webkit-inline-box; display: -ms-inline-flexbox; display: inline-flex; @@ -603,12 +608,91 @@ section.slider-block div.slider-content div.slider-control .light-right-icon { align-items: center; } -.main-container-wrapper .product-grid .product-card .cart-fav-seg .addtocart { +.main-container-wrapper .product-card .cart-fav-seg .addtocart { border-radius: 0px; margin-right: 10px; text-transform: uppercase; } +.main-container-wrapper .product-list .product-card { + width: 100%; + display: inline-block; + margin-bottom: 20px; +} + +.main-container-wrapper .product-list .product-card .product-image { + float: left; + width: 30%; + height: 350px; +} + +.main-container-wrapper .product-list .product-card .product-image img { + height: 100%; +} + +.main-container-wrapper .product-list .product-card .product-information { + float: right; + width: 70%; + padding-left: 30px; +} + +.main-container-wrapper .product-list .product-card:last-child { + margin-bottom: 0; +} + +.main-container-wrapper .top-toolbar { + width: 100%; + display: inline-block; + margin-bottom: 25px; +} + +.main-container-wrapper .top-toolbar .page-info { + float: left; + color: #242424; + line-height: 45px; +} + +.main-container-wrapper .top-toolbar .pager { + float: right; +} + +.main-container-wrapper .top-toolbar .pager label { + font-size: 16px; + margin-right: 5px; +} + +.main-container-wrapper .top-toolbar .pager select { + background: #FFFFFF; + border: 1px solid #C7C7C7; + border-radius: 3px; + font-size: 16px; + color: #242424; + padding: 10px; +} + +.main-container-wrapper .top-toolbar .pager .view-mode { + display: inline-block; + margin-right: 20px; +} + +.main-container-wrapper .top-toolbar .pager .view-mode a, .main-container-wrapper .top-toolbar .pager .view-mode span { + display: inline-block; + vertical-align: middle; +} + +.main-container-wrapper .top-toolbar .pager .view-mode a.grid-view, .main-container-wrapper .top-toolbar .pager .view-mode span.grid-view { + margin-right: 10px; +} + +.main-container-wrapper .top-toolbar .pager .sorter { + display: inline-block; + margin-right: 10px; +} + +.main-container-wrapper .top-toolbar .pager .limiter { + display: inline-block; +} + .main-container-wrapper .bottom-toolbar { display: block; margin-top: 40px; @@ -690,6 +774,30 @@ section.slider-block div.slider-content div.slider-control .light-right-icon { width: 100%; } +.product-price { + font-size: 16px; + margin-bottom: 14px; + width: 100%; + font-weight: 600; +} + +.product-price .price-label { + font-size: 14px; + font-weight: 400; +} + +.product-price .regular-price { + font-size: 16px; + color: #A5A5A5; + text-decoration: line-through; + margin-right: 10px; +} + +.product-price .special-price { + font-size: 16px; + color: #FF6472; +} + .footer { background-color: #f2f2f2; padding-left: 10%; @@ -1037,21 +1145,10 @@ section.product-detail div.layouter .mixed-group .details .product-name, section margin-bottom: 20px; } -section.product-detail div.layouter .mixed-group .details .price, section.product-review div.layouter .mixed-group .details .price { +section.product-detail div.layouter .mixed-group .details .product-price, section.product-review div.layouter .mixed-group .details .product-price { margin-bottom: 14px; } -section.product-detail div.layouter .mixed-group .details .price .main-price, section.product-review div.layouter .mixed-group .details .price .main-price { - font-size: 24px; - color: #FF6472; -} - -section.product-detail div.layouter .mixed-group .details .price .real-price, section.product-review div.layouter .mixed-group .details .price .real-price { - color: #A5A5A5; - -webkit-text-decoration-line: line-through; - text-decoration-line: line-through; -} - section.product-detail div.layouter .rating-reviews, section.product-review div.layouter .rating-reviews { margin-top: 30px; } @@ -1213,13 +1310,13 @@ section.product-detail div.layouter .details .rating, section.product-review div margin-bottom: 14px; } -section.product-detail div.layouter .details .price, section.product-review div.layouter .details .price { +section.product-detail div.layouter .details .product-price, section.product-review div.layouter .details .product-price { margin-bottom: 14px; + font-size: 24px; } -section.product-detail div.layouter .details .price .main-price, section.product-review div.layouter .details .price .main-price { +section.product-detail div.layouter .details .product-price .special-price, section.product-review div.layouter .details .product-price .special-price { font-size: 24px; - color: #FF6472; } section.product-detail div.layouter .details .stock-status, section.product-review div.layouter .details .stock-status { @@ -1230,9 +1327,19 @@ section.product-detail div.layouter .details .description, section.product-revie margin-bottom: 14px; } -section.product-detail div.layouter .details hr, section.product-review div.layouter .details hr { - border-top: 1px solid #E8E8E8; - margin-bottom: 17px; +section.product-detail div.layouter .details .full-specifications td, section.product-review div.layouter .details .full-specifications td { + padding: 10px 0; + color: #5E5E5E; +} + +section.product-detail div.layouter .details .full-specifications td:first-child, section.product-review div.layouter .details .full-specifications td:first-child { + padding-right: 40px; +} + +section.product-detail div.layouter .details .accordian .accordian-header, section.product-review div.layouter .details .accordian .accordian-header { + font-size: 16px; + padding-left: 0; + font-weight: 600; } section.product-detail div.layouter .details .attributes, section.product-review div.layouter .details .attributes { @@ -1320,13 +1427,14 @@ section.product-detail div.layouter .details .rating-reviews, section.product-re section.product-detail div.layouter .details .rating-reviews .title, section.product-review div.layouter .details .rating-reviews .title { margin-bottom: 15px; + font-weight: 600; } section.product-detail div.layouter .details .rating-reviews .overall, section.product-review div.layouter .details .rating-reviews .overall { margin-bottom: 5px; } -section.product-detail div.layouter .details .rating-reviews .overall .number, section.product-review div.layouter .details .rating-reviews .overall .number { +section.product-detail div.layouter .details .rating-reviews .overall .review-info .number, section.product-review div.layouter .details .rating-reviews .overall .review-info .number { font-size: 34px; } @@ -1346,12 +1454,22 @@ section.product-detail div.layouter .details .rating-reviews .reviews .review, s section.product-detail div.layouter .details .rating-reviews .reviews .review .stars, section.product-review div.layouter .details .rating-reviews .reviews .review .stars { margin-bottom: 15px; + display: inline-block; +} + +section.product-detail div.layouter .details .rating-reviews .reviews .review .stars .icon, section.product-review div.layouter .details .rating-reviews .reviews .review .stars .icon { + width: 18px; + height: 18px; } section.product-detail div.layouter .details .rating-reviews .reviews .review .message, section.product-review div.layouter .details .rating-reviews .reviews .review .message { margin-bottom: 10px; } +section.product-detail div.layouter .details .rating-reviews .reviews .review .reviewer-details, section.product-review div.layouter .details .rating-reviews .reviews .review .reviewer-details { + color: #5E5E5E; +} + section.product-detail div.layouter .details .rating-reviews .reviews .view-all, section.product-review div.layouter .details .rating-reviews .reviews .view-all { margin-top: 15px; color: #0031f0; @@ -1591,18 +1709,29 @@ section.cart .cart-content .right-side .coupon-section .after-coupon-amount .amo font-weight: bold; } -.related-products-wrapper { +.attached-products-wrapper { margin-bottom: 80px; } -.related-products-wrapper .title { - margin-bottom: 22px; +.attached-products-wrapper .title { + margin-bottom: 40px; font-size: 18px; color: #242424; text-align: center; + position: relative; } -.related-products-wrapper .horizontal-rule { +.attached-products-wrapper .title .border-bottom { + border-bottom: 1px solid rgba(162, 162, 162, 0.2); + display: inline-block; + width: 100px; + position: absolute; + top: 40px; + left: 50%; + margin-left: -50px; +} + +.attached-products-wrapper .horizontal-rule { height: 1px; background: #E8E8E8; width: 148px; @@ -1610,9 +1739,3 @@ section.cart .cart-content .right-side .coupon-section .after-coupon-amount .amo margin-left: auto; margin-right: auto; } - -.related-products-wrapper .related-products { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); - grid-gap: 10px; -}