Product details page added

This commit is contained in:
jitendra 2018-08-30 18:52:15 +05:30
parent bc087010ee
commit 0c8b5a03e2
52 changed files with 1746 additions and 369 deletions

View File

@ -0,0 +1,27 @@
<?php
namespace App\Criteria;
use Prettus\Repository\Contracts\CriteriaInterface;
use Prettus\Repository\Contracts\RepositoryInterface;
/**
* Class MyCriteria.
*
* @package namespace App\Criteria;
*/
class MyCriteria implements CriteriaInterface
{
/**
* Apply criteria in query repository
*
* @param string $model
* @param RepositoryInterface $repository
*
* @return mixed
*/
public function apply($model, RepositoryInterface $repository)
{
return $model;
}
}

View File

@ -133,7 +133,8 @@ return [
'value_per_channel' => 'Value Per Channel', 'value_per_channel' => 'Value Per Channel',
'is_filterable' => 'Use in Layered Navigation', 'is_filterable' => 'Use in Layered Navigation',
'is_configurable' => 'Use To Create Configurable Product', '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' => 'Families', 'families' => 'Families',

View File

@ -154,6 +154,14 @@
<option value="1">{{ __('admin::app.catalog.attributes.yes') }}</option> <option value="1">{{ __('admin::app.catalog.attributes.yes') }}</option>
</select> </select>
</div> </div>
<div class="control-group">
<label for="is_visible_on_front">{{ __('admin::app.catalog.attributes.is_visible_on_front') }}</label>
<select class="control" id="is_visible_on_front" name="is_visible_on_front">
<option value="0">{{ __('admin::app.catalog.attributes.no') }}</option>
<option value="1">{{ __('admin::app.catalog.attributes.yes') }}</option>
</select>
</div>
</div> </div>
</accordian> </accordian>

View File

@ -210,6 +210,18 @@
</option> </option>
</select> </select>
</div> </div>
<div class="control-group">
<label for="is_visible_on_front">{{ __('admin::app.catalog.attributes.is_visible_on_front') }}</label>
<select class="control" id="is_visible_on_front" name="is_visible_on_front">
<option value="0" {{ $attribute->is_visible_on_front ? '' : 'selected' }}>
{{ __('admin::app.catalog.attributes.no') }}
</option>
<option value="1" {{ $attribute->is_visible_on_front ? 'selected' : '' }}>
{{ __('admin::app.catalog.attributes.yes') }}
</option>
</select>
</div>
</div> </div>
</accordian> </accordian>

View File

@ -27,6 +27,7 @@ class CreateAttributesTable extends Migration
$table->boolean('is_filterable')->default(0); $table->boolean('is_filterable')->default(0);
$table->boolean('is_configurable')->default(0); $table->boolean('is_configurable')->default(0);
$table->boolean('is_user_defined')->default(1); $table->boolean('is_user_defined')->default(1);
$table->boolean('is_visible_on_front')->default(0);
$table->timestamps(); $table->timestamps();
}); });
} }

View File

@ -10,7 +10,7 @@ class Attribute extends TranslatableModel
{ {
public $translatedAttributes = ['name']; 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']; protected $with = ['options'];
@ -22,14 +22,6 @@ class Attribute extends TranslatableModel
return $this->hasMany(AttributeOption::class); return $this->hasMany(AttributeOption::class);
} }
/**
* Get the options.
*/
public function filter_attributes()
{
}
/** /**
* Scope a query to only include popular users. * Scope a query to only include popular users.
* *

View File

@ -2,6 +2,7 @@
namespace Webkul\Core; namespace Webkul\Core;
use Carbon\Carbon;
use Webkul\Core\Models\Channel as ChannelModel; use Webkul\Core\Models\Channel as ChannelModel;
use Webkul\Core\Models\Locale as LocaleModel; use Webkul\Core\Models\Locale as LocaleModel;
use Webkul\Core\Models\Currency as CurrencyModel; use Webkul\Core\Models\Currency as CurrencyModel;
@ -14,7 +15,12 @@ class Core
* @return Collection * @return Collection
*/ */
public function getAllChannels() { public function getAllChannels() {
return ChannelModel::all(); static $channels;
if($channels)
return $channels;
return $channels = ChannelModel::all();
} }
/** /**
@ -23,7 +29,12 @@ class Core
* @return mixed * @return mixed
*/ */
public function getCurrentChannel() { public function getCurrentChannel() {
return ChannelModel::first(); static $channel;
if($channel)
return $channel;
return $channel = ChannelModel::first();
} }
/** /**
@ -32,7 +43,12 @@ class Core
* @return string * @return string
*/ */
public function getCurrentChannelCode() { public function getCurrentChannelCode() {
return ($channel = $this->getCurrentChannel()) ? $channel->code : ''; static $channelCode;
if($channelCode)
return $channelCode;
return ($channel = $this->getCurrentChannel()) ? $channelCode = $channel->code : '';
} }
/** /**
@ -41,7 +57,12 @@ class Core
* @return Collection * @return Collection
*/ */
public function getAllLocales() { public function getAllLocales() {
return LocaleModel::all(); static $locales;
if($locales)
return $locales;
return $locales = LocaleModel::all();
} }
/** /**
@ -51,7 +72,12 @@ class Core
*/ */
public function getAllCurrencies() public function getAllCurrencies()
{ {
return CurrencyModel::all(); static $currencies;
if($currencies)
return $currencies;
return $currencies = CurrencyModel::all();
} }
/** /**
@ -61,9 +87,12 @@ class Core
*/ */
public function getCurrentCurrency() public function getCurrentCurrency()
{ {
$currency = CurrencyModel::first(); static $currency;
return $currency; if($currency)
return $currency;
return $currency = CurrencyModel::first();
} }
/** /**
@ -73,7 +102,12 @@ class Core
*/ */
public function getCurrentCurrencyCode() public function getCurrentCurrencyCode()
{ {
return ($currency = $this->getCurrentCurrency()) ? $currency->code : ''; static $currencyCode;
if($currencyCode)
return $currencyCode;
return ($currency = $this->getCurrentCurrency()) ? $currencyCode = $currency->code : '';
} }
/** /**
@ -83,7 +117,12 @@ class Core
*/ */
public function getCurrentCurrencySymbol() public function getCurrentCurrencySymbol()
{ {
return $this->getCurrentCurrency()->symbol; static $currencySymbol;
if($currencySymbol)
return $currencySymbol;
return $currencySymbol = $this->getCurrentCurrency()->symbol;
} }
/** /**
@ -171,4 +210,25 @@ class Core
} }
// $timezonelist = \DateTimeZone::listIdentifiers(\DateTimeZone::ALL); // $timezonelist = \DateTimeZone::listIdentifiers(\DateTimeZone::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);
}
} }

View File

@ -13,7 +13,7 @@
{ {
$results = []; $results = [];
while (list($key, $values) = each($input)) { foreach ($input as $key => $values) {
if (empty($values)) { if (empty($values)) {
continue; continue;
} }
@ -41,7 +41,7 @@
$results = array_merge($results, $append); $results = array_merge($results, $append);
} }
} }
return $results; return $results;
} }
} }

View File

@ -16,4 +16,8 @@ class Customer extends Authenticatable
protected $table = 'customers'; protected $table = 'customers';
protected $hidden = ['password','remember_token']; protected $hidden = ['password','remember_token'];
public function getNameAttribute() {
return ucfirst($this->first_name) . ' ' . ucfirst($this->last_name);
}
} }

View File

@ -0,0 +1,84 @@
<?php
namespace Webkul\Product\Contracts\Criteria;
use Prettus\Repository\Contracts\CriteriaInterface;
use Prettus\Repository\Contracts\RepositoryInterface;
use Webkul\Product\Models\ProductAttributeValue;
use Webkul\Attribute\Repositories\AttributeRepository;
use Webkul\Product\Product\AbstractProduct;
/**
* Class MyCriteria.
*
* @package namespace App\Criteria;
*/
class AttributeToSelectCriteria extends AbstractProduct implements CriteriaInterface
{
/**
* @var AttributeRepository
*/
protected $attribute;
/**
* @var array|string
*/
protected $attributeToSelect;
/**
* @param Webkul\Attribute\Repositories\AttributeRepository $attribute
* @return void
*/
public function __construct(AttributeRepository $attribute)
{
$this->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;
}
}

View File

@ -0,0 +1,88 @@
<?php
namespace Webkul\Product\Contracts\Criteria;
use Prettus\Repository\Contracts\CriteriaInterface;
use Prettus\Repository\Contracts\RepositoryInterface;
use Webkul\Product\Models\ProductAttributeValue;
use Webkul\Attribute\Repositories\AttributeRepository;
use Webkul\Product\Product\AbstractProduct;
/**
* Class MyCriteria.
*
* @package namespace App\Criteria;
*/
class FilterByAttributesCriteria extends AbstractProduct implements CriteriaInterface
{
/**
* @var AttributeRepository
*/
protected $attribute;
/**
* @param Webkul\Attribute\Repositories\AttributeRepository $attribute
* @return void
*/
public function __construct(AttributeRepository $attribute)
{
$this->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;
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace Webkul\Product\Contracts\Criteria;
use Prettus\Repository\Contracts\CriteriaInterface;
use Prettus\Repository\Contracts\RepositoryInterface;
/**
* Class MyCriteria.
*
* @package namespace App\Criteria;
*/
class FilterByCategoryCriteria implements CriteriaInterface
{
/**
* @var integer
*/
protected $categoryId;
/**
* @param integer $categoryId
* @return void
*/
public function __construct($categoryId)
{
$this->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;
}
}

View File

@ -0,0 +1,71 @@
<?php
namespace Webkul\Product\Contracts\Criteria;
use Prettus\Repository\Contracts\CriteriaInterface;
use Prettus\Repository\Contracts\RepositoryInterface;
use Webkul\Product\Models\ProductAttributeValue;
use Webkul\Attribute\Repositories\AttributeRepository;
use Webkul\Product\Product\AbstractProduct;
/**
* Class MyCriteria.
*
* @package namespace App\Criteria;
*/
class SortCriteria extends AbstractProduct implements CriteriaInterface
{
/**
* @var AttributeRepository
*/
protected $attribute;
/**
* @param Webkul\Attribute\Repositories\AttributeRepository $attribute
* @return void
*/
public function __construct(AttributeRepository $attribute)
{
$this->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;
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace Webkul\Product\Database\Eloquent;
use Illuminate\Database\Eloquent\Builder as BaseBuilder;
use Illuminate\Pagination\Paginator;
/**
* @mixin \Illuminate\Database\Query\Builder
*/
class Builder extends BaseBuilder
{
/**
* Paginate the given query.
*
* @param int $perPage
* @param array $columns
* @param string $pageName
* @param int|null $page
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
*
* @throws \InvalidArgumentException
*/
public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null)
{
$page = $page ?: Paginator::resolveCurrentPage($pageName);
$perPage = $perPage ?: $this->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,
]);
}
}

View File

@ -111,7 +111,7 @@ class Product extends Model
*/ */
public function up_sells() 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) public function getAttribute($key)
{ {
if (!method_exists(self::class, $key) && !in_array($key, ['parent_id', 'attribute_family_id']) && !isset($this->attributes[$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] = ''; $this->attributes[$key] = '';
$attributeModel = $this->attribute_family->custom_attributes()->where('attributes.code', $key)->first(); $attributeModel = $this->attribute_family->custom_attributes()->where('attributes.code', $key)->first();
@ -185,32 +185,46 @@ class Product extends Model
$hiddenAttributes = $this->getHidden(); $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) { foreach ($this->attribute_family->custom_attributes as $attribute) {
if (in_array($attribute->code, $hiddenAttributes)) { if (in_array($attribute->code, $hiddenAttributes)) {
continue; 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();
} }
} 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; 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);
}
} }

View File

@ -3,8 +3,17 @@
namespace Webkul\Product\Models; namespace Webkul\Product\Models;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Webkul\Customer\Models\Customer;
class ProductReview extends Model class ProductReview extends Model
{ {
protected $fillable = []; protected $fillable = [];
/**
* Get the product attribute family that owns the product.
*/
public function customer()
{
return $this->belongsTo(Customer::class);
}
} }

View File

@ -35,30 +35,4 @@ abstract class AbstractProduct
return $qb; 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;
}
} }

View File

@ -1,94 +0,0 @@
<?php
namespace Webkul\Product\Product;
use Illuminate\Support\Facades\DB;
use Webkul\Product\Repositories\ProductRepository as Product;
use Webkul\Attribute\Repositories\AttributeRepository as Attribute;
class Collection extends AbstractProduct
{
/**
* ProductRepository object
*
* @var array
*/
protected $product;
/**
* AttributeRepository object
*
* @var array
*/
protected $attribute;
/**
* array object
*
* @var array
*/
protected $attributeToSelect = [
'name',
'description',
'short_description',
'price',
'special_price',
'special_price_from',
'special_price_to'
];
/**
* Create a new controller instance.
*
* @param Webkul\Product\Repositories\ProductRepository $product
* @param Webkul\Attribute\Repositories\AttributeRepository $attribute
* @return void
*/
public function __construct(Product $product, Attribute $attribute)
{
$this->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);
}
}

View File

@ -1,16 +1,39 @@
<?php <?php
namespace Webkul\Shop\Product; namespace Webkul\Product\Product;
class Review extends AbstractProduct class Review extends AbstractProduct
{ {
/** /**
* Returns the product's avg rating * Returns the product's avg rating
* *
* @param Product $product
* @return float * @return float
*/ */
public function getAverageRating($product) public function getAverageRating($product)
{ {
return round($product->reviews->average('rating')); return round($product->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');
}
} }

View File

@ -0,0 +1,142 @@
<?php
namespace Webkul\Product\Product;
class Toolbar extends AbstractProduct
{
/**
* Returns available sort orders
*
* @param string $key
* @return string
*/
public function getAvailableOrders()
{
return [
'name-asc' => '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';
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace Webkul\Product\Product;
class View extends AbstractProduct
{
/**
* Returns the visible custom attributes
*
* @param Product $product
* @return integer
*/
public function getAdditionalData($product)
{
$data = [];
$attributes = $product->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;
}
}

View File

@ -74,7 +74,7 @@ class ProductAttributeValueRepository extends Repository
*/ */
public function isValueUnique($productId, $attributeId, $column, $value) 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; return $result->count() ? false : true;
} }

View File

@ -10,6 +10,11 @@ use Webkul\Product\Repositories\ProductAttributeValueRepository;
use Webkul\Product\Repositories\ProductInventoryRepository; use Webkul\Product\Repositories\ProductInventoryRepository;
use Webkul\Product\Repositories\ProductImageRepository; use Webkul\Product\Repositories\ProductImageRepository;
use Webkul\Product\Models\ProductAttributeValue; 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 * Product Repository
@ -177,6 +182,8 @@ class ProductRepository extends Repository
} }
} }
$previousVariantIds = $product->variants->pluck('id');
if(isset($data['variants'])) { if(isset($data['variants'])) {
foreach ($data['variants'] as $variantId => $variantData) { foreach ($data['variants'] as $variantId => $variantData) {
if (str_contains($variantId, 'variant_')) { if (str_contains($variantId, 'variant_')) {
@ -187,13 +194,22 @@ class ProductRepository extends Repository
$this->createVariant($product, $permutation, $variantData); $this->createVariant($product, $permutation, $variantData);
} else { } else {
if(is_numeric($index = $previousVariantIds->search($variantId))) {
$previousVariantIds->forget($index);
}
$variantData['channel'] = $data['channel']; $variantData['channel'] = $data['channel'];
$variantData['locale'] = $data['locale']; $variantData['locale'] = $data['locale'];
$this->updateVariant($variantData, $variantId); $this->updateVariant($variantData, $variantId);
} }
} }
} }
foreach ($previousVariantIds as $variantId) {
$this->delete($variantId);
}
$this->productInventory->saveInventories($data, $product); $this->productInventory->saveInventories($data, $product);
$this->productImage->uploadImages($data, $product); $this->productImage->uploadImages($data, $product);
@ -358,4 +374,53 @@ class ProductRepository extends Repository
return false; 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
);
}
} }

View File

@ -0,0 +1,24 @@
<?php
namespace Webkul\Product\Repositories;
use Webkul\Core\Eloquent\Repository;
/**
* Product Review Reposotory
*
* @author Jitendra Singh <jitendra@webkul.com>
* @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';
}
}

View File

@ -0,0 +1,57 @@
<?php
namespace Webkul\Shop\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Webkul\Product\Repositories\ProductRepository as Product;
/**
* Product controller
*
* @author Jitendra Singh <jitendra@webkul.com>
* @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'));
}
}

View File

@ -0,0 +1,100 @@
<?php
namespace Webkul\Shop\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Webkul\Product\Repositories\ProductRepository as Product;
use Webkul\Product\Repositories\ProductReviewRepository as ProductReview;
/**
* Review controller
*
* @author Jitendra Singh <jitendra@webkul.com>
* @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']);
}
}

View File

@ -8,11 +8,30 @@ Route::group(['middleware' => ['web']], function () {
Route::get('/categories/{slug}', 'Webkul\Shop\Http\Controllers\CategoryController@index')->defaults('_config', [ Route::get('/categories/{slug}', 'Webkul\Shop\Http\Controllers\CategoryController@index')->defaults('_config', [
'view' => 'shop::products.index' 'view' => 'shop::products.index'
]); ])->name('shop.categories.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('/cart', 'shop::store.product.view.cart.index'); Route::view('/cart', 'shop::store.product.view.cart.index');
Route::view('/products/{slug}', 'shop::store.product.details.index'); // Route::view('/products/{slug}', 'shop::products.view');
//customer routes starts here //customer routes starts here
Route::prefix('customer')->group(function () { Route::prefix('customer')->group(function () {

View File

@ -1,4 +1,6 @@
//shop variables //shop variables
$font-color: #242424;
$font-size-base: 16px;
$font-name: "Montserrat", sans-serif; $font-name: "Montserrat", sans-serif;
$background-color: #f2f2f2; $background-color: #f2f2f2;
$footer-back: #f2f2f2; $footer-back: #f2f2f2;
@ -23,6 +25,7 @@ $horizontal-rule-color: #E8E8E8;
//product //product
$real-price:#A5A5A5; $real-price:#A5A5A5;
$product-font-color: #242424; $product-font-color: #242424;
$product-special-price-color: #FF6472;
$product-price-color: #FF6472; $product-price-color: #FF6472;
$dark-blue-shade: #0031F0; $dark-blue-shade: #0031F0;
$bar-color: #D8D8D8; $bar-color: #D8D8D8;

View File

@ -8,7 +8,8 @@ body {
margin: 0; margin: 0;
padding: 0; padding: 0;
font-weight: 500; font-weight: 500;
font-size: 14px; color: $font-color;
font-size: $font-size-base;
} }
* { * {
@ -196,7 +197,7 @@ body {
.nav a { .nav a {
display:block; display:block;
color: #242424; color: $font-color;
text-decoration: none; text-decoration: none;
padding: 0.8em 0.3em 0.8em 0.5em; padding: 0.8em 0.3em 0.8em 0.5em;
text-transform: uppercase; text-transform: uppercase;
@ -356,13 +357,13 @@ section.slider-block {
.layered-filter-wrapper { .layered-filter-wrapper {
width: 25%; width: 25%;
float: left; float: left;
margin-right: 20px; padding-right: 20px;
min-height: 1px; min-height: 1px;
.filter-title { .filter-title {
border-bottom: 1px solid #E8E8E8; border-bottom: 1px solid #E8E8E8;
font-size: 16px; font-size: 16px;
color: #242424; color: $font-color;
padding: 10px 0; padding: 10px 0;
} }
@ -442,6 +443,7 @@ section.slider-block {
} }
} }
.main-container-wrapper { .main-container-wrapper {
margin-left: 10%; margin-left: 10%;
margin-right: 10%; margin-right: 10%;
@ -451,6 +453,11 @@ section.slider-block {
width: 100%; width: 100%;
} }
.main {
display: inline-block;
width: 75%
}
.product-grid { .product-grid {
display: grid; display: grid;
grid-gap: 30px; grid-gap: 30px;
@ -471,58 +478,129 @@ section.slider-block {
display: flex; display: flex;
flex-flow: column; flex-flow: column;
justify-content: center; justify-content: center;
}
}
.product-image img { .product-card {
align-self: center;
width: 100%; .product-image img {
margin-bottom: 14px; 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; font-size: 16px;
margin-bottom: 14px; margin-right: 5px;
width: 100%;
} }
.product-price { select {
background: #FFFFFF;
border: 1px solid #C7C7C7;
border-radius: 3px;
font-size: 16px; font-size: 16px;
margin-bottom: 14px; color: $font-color;
width: 100%; padding: 10px;
font-weight: 600; }
.price-label { .view-mode {
font-size: 14px; display: inline-block;
font-weight: 400; margin-right: 20px;
}
.regular-price { a, span {
font-size: 16px; display: inline-block;
color: #A5A5A5; vertical-align: middle;
text-decoration: line-through;
margin-right: 10px;
}
.special-price { &.grid-view {
font-size: 16px; margin-right: 10px;
color: #FF6472; }
} }
} }
.product-ratings { .sorter {
width: 100%; display: inline-block;
margin-bottom: 14px; margin-right: 10px;
} }
.cart-fav-seg { .limiter {
display: inline-flex; display: inline-block;
width: 100%;
align-items: center;
.addtocart {
border-radius: 0px;
margin-right: 10px;
text-transform: uppercase;
}
} }
} }
} }
@ -602,6 +680,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 { .footer {
background-color: $footer-back; background-color: $footer-back;
padding-left: 10%; padding-left: 10%;
@ -805,7 +907,7 @@ section.slider-block {
.section-head { .section-head {
.profile-heading { .profile-heading {
font-size: 28px; font-size: 28px;
color: #242424; color: $font-color;
text-transform: capitalize; text-transform: capitalize;
text-align: left; text-align: left;
} }
@ -912,18 +1014,8 @@ section.product-detail, section.product-review {
margin-bottom: 20px; margin-bottom: 20px;
} }
.price { .product-price {
margin-bottom: 14px; margin-bottom: 14px;
.main-price {
font-size: 24px;
color: $product-price-color;
}
.real-price {
color: $real-price;
text-decoration-line: line-through;
}
} }
} }
} }
@ -1059,12 +1151,12 @@ section.product-detail, section.product-review {
margin-bottom: 14px; margin-bottom: 14px;
} }
.price { .product-price {
margin-bottom: 14px; margin-bottom: 14px;
font-size: 24px;
.main-price { .special-price {
font-size: 24px; font-size: 24px;
color: $product-price-color;
} }
} }
@ -1076,9 +1168,21 @@ section.product-detail, section.product-review {
margin-bottom: 14px; margin-bottom: 14px;
} }
hr{ .full-specifications {
border-top: 1px solid $horizontal-rule-color; td {
margin-bottom: 17px; padding: 10px 0;
color: #5E5E5E;
&:first-child {
padding-right: 40px;
}
}
}
.accordian .accordian-header {
font-size: 16px;
padding-left: 0;
font-weight: 600;
} }
.attributes { .attributes {
@ -1160,21 +1264,21 @@ section.product-detail, section.product-review {
font-size: 16px; font-size: 16px;
} }
.full-specification{
}
.rating-reviews { .rating-reviews {
margin-top: 30px; margin-top: 30px;
.title{ .title {
margin-bottom: 15px; margin-bottom: 15px;
font-weight: 600;
} }
.overall { .overall {
margin-bottom: 5px;
.number{ .review-info {
font-size: 34px; .number {
font-size: 34px;
}
} }
button { button {
@ -1182,7 +1286,6 @@ section.product-detail, section.product-review {
border-radius: 0px !important; border-radius: 0px !important;
} }
margin-bottom: 5px;
} }
.reviews { .reviews {
@ -1194,11 +1297,23 @@ section.product-detail, section.product-review {
.stars { .stars {
margin-bottom: 15px; margin-bottom: 15px;
display: inline-block;
.icon {
width: 18px;
height: 18px;
}
} }
.message { .message {
margin-bottom: 10px; margin-bottom: 10px;
} }
.reviewer-details {
color: #5E5E5E;
}
} }
.view-all { .view-all {
margin-top:15px; margin-top:15px;
color: $logo-color; color: $logo-color;
@ -1207,33 +1322,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 */ /* cart pages and elements css begins here */
@ -1462,14 +1551,25 @@ section.cart {
} }
.related-products-wrapper { .attached-products-wrapper {
margin-bottom: 80px; margin-bottom: 80px;
.title{ .title {
margin-bottom: 22px; margin-bottom: 40px;
font-size: 18px; font-size: 18px;
color: $product-font-color; color: $product-font-color;
text-align: center; 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 { .horizontal-rule {
@ -1480,11 +1580,4 @@ section.cart {
margin-left:auto; margin-left:auto;
margin-right:auto; margin-right:auto;
} }
.related-products {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
grid-gap: 10px;
}
} }

View File

@ -2,10 +2,23 @@
display: inline-block; display: inline-block;
background-size: cover; background-size: cover;
} }
.dropdown-right-icon{ .dropdown-right-icon{
background-image:URL('../images/icon-dropdown-left.svg'); background-image:URL('../images/icon-dropdown-left.svg');
width: 8px; width: 8px;
height: 8px; height: 8px;
margin-left:auto; margin-left:auto;
margin-bottom: 2px; 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;
} }

View File

@ -35,6 +35,20 @@ return [
'products' => [ 'products' => [
'layered-nav-title' => 'Shop By', 'layered-nav-title' => 'Shop By',
'price-label' => 'As low as', '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!'
] ]
]; ];

View File

@ -1 +1 @@
<button class="btn btn-md btn-primary addtocart">Add to Cart</button> <button class="btn btn-lg btn-primary addtocart">Add to Cart</button>

View File

@ -1,20 +0,0 @@
<div class="product-card">
<div class="product-image">
<img src="{{ bagisto_asset('images/gogs.png') }}" />
</div>
<div class="product-name">
<span>{{ $product->name }}</span>
</div>
@include ('shop::products.price', ['product' => $product])
@if ($product->reviews->count())
@include ('shop::products.review', ['product' => $product])
@endif
@include ('shop::products.add-to', ['product' => $product])
</div>

View File

@ -2,24 +2,40 @@
@section('content-wrapper') @section('content-wrapper')
@include ('shop::products.layered-navigation') @include ('shop::products.list.layered-navigation')
<div class="main" style="display: inline-block"> <div class="main" style="display: inline-block">
<div class="product-grid max-3-col"> @inject ('productRepository', 'Webkul\Product\Repositories\ProductRepository')
@inject ('productHelper', 'Webkul\Product\Product\Collection') <?php $products = $productRepository->findAllByCategory($category->id); ?>
<?php $products = $productHelper->getCollection($category->id); ?>
@foreach ($products as $product)
@include ('shop::products.card', ['product' => $product]) @include ('shop::products.list.toolbar')
@endforeach @inject ('toolbarHelper', 'Webkul\Product\Product\Toolbar')
</div> @if ($toolbarHelper->getCurrentMode() == 'grid')
<div class="product-grid max-3-col">
@foreach ($products as $product)
@include ('shop::products.list.card', ['product' => $product])
@endforeach
</div>
@else
<div class="product-list">
@foreach ($products as $product)
@include ('shop::products.list.card', ['product' => $product])
@endforeach
</div>
@endif
<div class="bottom-toolbar"> <div class="bottom-toolbar">
{{ $products->appends(request()->input())->links() }} {{ $products->appends(request()->input())->links() }}
@ -28,8 +44,4 @@
</div> </div>
@stop @stop
@push('scripts')
@endpush

View File

@ -0,0 +1,35 @@
<div class="product-card">
<div class="product-image">
<a href="{{ route('shop.products.index', $product->url_key) }}" title="{{ $product->name }}">
<img src="{{ bagisto_asset('images/gogs.png') }}" />
</a>
</div>
<div class="product-information">
<div class="product-name">
{{ $product->id }}
<a href="" title="{{ $product->name }}">
<span>{{ $product->name }}</span>
</a>
</div>
<div class="product-description">
{{ $product->short_description }}
</div>
@include ('shop::products.price', ['product' => $product])
@if ($product->reviews->count())
@include ('shop::products.review', ['product' => $product])
@endif
@include ('shop::products.add-to', ['product' => $product])
</div>
</div>

View File

@ -1,7 +1,9 @@
@inject ('attributeRepository', 'Webkul\Attribute\Repositories\AttributeRepository') @inject ('attributeRepository', 'Webkul\Attribute\Repositories\AttributeRepository')
<div class="layered-filter-wrapper"> <div class="layered-filter-wrapper">
<layered-navigation></layered-navigation> <layered-navigation></layered-navigation>
</div> </div>
@push('scripts') @push('scripts')
@ -129,8 +131,8 @@
sliderConfig: { sliderConfig: {
value: [ value: [
100, 0,
250 0
], ],
max: 500, max: 500,
processStyle: { processStyle: {
@ -171,7 +173,7 @@
clearFilters () { clearFilters () {
if(this.attribute.type == 'price') { if(this.attribute.type == 'price') {
this.sliderConfig.value = [100, 250]; this.sliderConfig.value = [0, 0];
} }
this.appliedFilters = []; this.appliedFilters = [];

View File

@ -0,0 +1,67 @@
@inject ('toolbarHelper', 'Webkul\Product\Product\Toolbar')
<div class="top-toolbar">
<div class="page-info">
{{ __('shop::app.products.pager-info', ['showing' => $products->firstItem() . '-' . $products->lastItem(), 'total' => $products->total()]) }}
</div>
<div class="pager">
<div class="view-mode">
@if ($toolbarHelper->isModeActive('grid'))
<span class="grid-view">
<i class="icon grid-view-icon"></i>
</span>
@else
<a href="{{ $toolbarHelper->getModeUrl('grid') }}" class="grid-view">
<i class="icon grid-view-icon"></i>
</a>
@endif
@if ($toolbarHelper->isModeActive('list'))
<span class="list-view">
<i class="icon list-view-icon"></i>
</span>
@else
<a href="{{ $toolbarHelper->getModeUrl('list') }}" class="list-view">
<i class="icon list-view-icon"></i>
</a>
@endif
</div>
<div class="sorter">
<label>{{ __('shop::app.products.sort-by') }}</label>
<select onchange="window.location.href = this.value">
@foreach ($toolbarHelper->getAvailableOrders() as $key => $order)
<option value="{{ $toolbarHelper->getOrderUrl($key) }}" {{ $toolbarHelper->isOrderCurrent($key) ? 'selected' : '' }}>
{{ __('shop::app.products.' . $order) }}
</option>
@endforeach
</select>
</div>
<div class="limiter">
<label>{{ __('shop::app.products.show') }}</label>
<select onchange="window.location.href = this.value">
@foreach ($toolbarHelper->getAvailableLimits() as $limit)
<option value="{{ $toolbarHelper->getLimitUrl($limit) }}" {{ $toolbarHelper->isLimitCurrent($limit) ? 'selected' : '' }}>
{{ $limit }}
</option>
@endforeach
</select>
</div>
</div>
</div>

View File

@ -0,0 +1,65 @@
@extends('shop::layouts.master')
@section('content-wrapper')
<section class="product-detail">
<div class="category-breadcrumbs">
<span class="breadcrumb">Home</span> > <span class="breadcrumb">Men</span> > <span class="breadcrumb">Slit Open Jeans</span>
</div>
<div class="layouter">
@include ('shop::products.view.gallery')
<div class="details">
<div class="product-heading">
<span>{{ $product->name }}</span>
</div>
<div class="rating">
<img src="{{ bagisto_asset('images/5star.svg') }}" />
75 Ratings & 11 Reviews
</div>
@include ('shop::products.price', ['product' => $product])
@include ('shop::products.view.stock')
<br/>
<div class="description">
{{ $product->short_description }}
</div>
@if ($product->type == 'configurable')
@include ('shop::products.view.configurable-options')
@endif
<accordian :title="{{ __('shop::app.products.description') }}" :active="true">
<div slot="header">
{{ __('shop::app.products.description') }}
<i class="icon expand-icon right"></i>
</div>
<div slot="body">
<div class="full-description">
{{ $product->description }}
</div>
</div>
</accordian>
@include ('shop::products.view.attributes')
@include ('shop::products.view.reviews')
</div>
</div>
@include ('shop::products.view.up-sells')
</section>
@endsection

View File

@ -0,0 +1,23 @@
@inject ('productViewHelper', 'Webkul\Product\Product\View')
<accordian :title="{{ __('shop::app.products.specification') }}" :active="false">
<div slot="header">
{{ __('shop::app.products.specification') }}
<i class="icon expand-icon right"></i>
</div>
<div slot="body">
<table class="full-specifications">
@foreach ($productViewHelper->getAdditionalData($product) as $attribute)
<tr>
<td>{{ $attribute['label'] }}</td>
<td> - {{ $attribute['value'] }}</td>
</tr>
@endforeach
</table>
</div>
</accordian>

View File

@ -0,0 +1,33 @@
<div class="attributes">
<div class="attribute color">
<div class="title">Color</div>
<div class="values">
<div class="colors red"></div>
<div class="colors blue"></div>
<div class="colors green"></div>
</div>
</div>
<div class="attribute size">
<div class="title">Size</div>
<div class="values">
<div class="size xl">XL</div>
<div class="size xxl">XXL</div>
<div class="size xxxl">XXXL</div>
</div>
</div>
<div class="attribute quantity">
<div class="title">Quantity</div>
<div class="values">
<div class="size">1</div>
</div>
</div>
</div>
<hr/>

View File

@ -0,0 +1,16 @@
<div class="product-image-group">
<div class="side-group">
<img src="{{ bagisto_asset('images/jeans.jpg') }}" />
<img src="{{ bagisto_asset('images/jeans.jpg') }}" />
<img src="{{ bagisto_asset('images/jeans.jpg') }}" />
<img src="{{ bagisto_asset('images/jeans.jpg') }}" />
</div>
<div class="product-hero-image">
<img src="{{ bagisto_asset('images/jeans_big.jpg') }}" />
<img class="wishlist" src="{{ bagisto_asset('images/wish.svg') }}" />
<img class="share" src="{{ bagisto_asset('images/icon-share.svg') }}" />
</div>
</div>

View File

@ -0,0 +1,71 @@
@inject ('reviewHelper', 'Webkul\Product\Product\Review')
@if ($total = $reviewHelper->getTotalReviews($product))
<div class="rating-reviews">
<div class="title">
Ratings & Reviews
</div>
<div class="overall">
<div class="review-info">
<span class="number">
{{ $reviewHelper->getAverageRating($product) }}
</span>
<span class="stars">
@for ($i = 1; $i <= $reviewHelper->getAverageRating($product); $i++)
<span class="icon star-icon"></span>
@endfor
</span>
<div class="total-reviews">
{{ __('shop::app.products.total-reviews', ['total' => $total]) }}
</div>
</div>
<a href="{{ route('shop.reviews.create', $product->url_key) }}" class="btn btn-lg btn-primary">Write Review</a>
</div>
<div class="reviews">
@foreach ($product->reviews()->paginate(5) as $review)
<div class="review">
<div class="title">
{{ $review->title }}
</div>
<span class="stars">
@for ($i = 1; $i <= $review->rating; $i++)
<span class="icon star-icon"></span>
@endfor
</span>
<div class="message">
{{ $review->comment }}
</div>
<div class="reviewer-details">
<span class="by">
{{ __('shop::app.products.by', ['name' => $review->customer->name]) }},
</span>
<span class="when">
{{ $reviewHelper->formatDate($review->created_at) }}
</span>
</div>
</div>
@endforeach
<a href="{{ route('shop.reviews.index', $product->url_key) }}" class="view-all">View All</a>
<hr/>
</div>
</div>
@endif

View File

@ -0,0 +1,3 @@
<div class="stock-status">
InStock
</div>

View File

@ -0,0 +1,20 @@
@if ($product->up_sells()->count())
<div class="attached-products-wrapper">
<div class="title">
{{ __('shop::app.products.up-sell-title') }}
<span class="border-bottom"></span>
</div>
<div class="product-grid max-4-col">
@foreach ($product->up_sells()->paginate(4) as $up_sell_product)
@include ('shop::products.list.card', ['product' => $up_sell_product])
@endforeach
</div>
</div>
@endif

View File

@ -53,9 +53,13 @@ class AccountController extends Controller
'email' => 'email|unique:admins,email,' . $user->id, 'email' => 'email|unique:admins,email,' . $user->id,
'password' => 'nullable|confirmed' 'password' => 'nullable|confirmed'
]); ]);
$user->update(request(['name', 'email', 'password'])); $data = request()->all();
if(!$data['password'])
unset($data['password']);
$user->update($data);
session()->flash('success', 'Account changes saved successfully.'); session()->flash('success', 'Account changes saved successfully.');

View File

@ -116,7 +116,12 @@ class UserController extends Controller
*/ */
public function update(UserForm $request, $id) 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.'); session()->flash('success', 'User updated successfully.');

View File

@ -11,11 +11,24 @@
margin-bottom: 2px; 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 { body {
margin: 0; margin: 0;
padding: 0; padding: 0;
font-weight: 500; 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 { .layered-filter-wrapper {
width: 25%; width: 25%;
float: left; float: left;
margin-right: 20px; padding-right: 20px;
min-height: 1px; min-height: 1px;
} }
@ -522,6 +535,11 @@ section.slider-block div.slider-content div.slider-control .light-right-icon {
width: 100%; width: 100%;
} }
.main-container-wrapper .main {
display: inline-block;
width: 75%;
}
.main-container-wrapper .product-grid { .main-container-wrapper .product-grid {
display: grid; display: grid;
grid-gap: 30px; grid-gap: 30px;
@ -552,49 +570,36 @@ section.slider-block div.slider-content div.slider-control .light-right-icon {
justify-content: center; 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; -ms-flex-item-align: center;
align-self: center; align-self: center;
width: 100%; width: 100%;
margin-bottom: 14px; margin-bottom: 14px;
} }
.main-container-wrapper .product-grid .product-card .product-name { .main-container-wrapper .product-card .product-name {
font-size: 16px; font-size: 16px;
margin-bottom: 14px; margin-bottom: 14px;
width: 100%; 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; font-size: 16px;
margin-bottom: 14px; margin-bottom: 14px;
width: 100%; display: none;
font-weight: 600;
} }
.main-container-wrapper .product-grid .product-card .product-price .price-label { .main-container-wrapper .product-card .product-ratings {
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 {
width: 100%; width: 100%;
margin-bottom: 14px; 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: -webkit-inline-box;
display: -ms-inline-flexbox; display: -ms-inline-flexbox;
display: inline-flex; display: inline-flex;
@ -604,12 +609,91 @@ section.slider-block div.slider-content div.slider-control .light-right-icon {
align-items: center; 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; border-radius: 0px;
margin-right: 10px; margin-right: 10px;
text-transform: uppercase; 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 { .main-container-wrapper .bottom-toolbar {
display: block; display: block;
margin-top: 40px; margin-top: 40px;
@ -691,6 +775,30 @@ section.slider-block div.slider-content div.slider-control .light-right-icon {
width: 100%; 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 { .footer {
background-color: #f2f2f2; background-color: #f2f2f2;
padding-left: 10%; padding-left: 10%;
@ -1013,21 +1121,10 @@ section.product-detail div.layouter .mixed-group .details .product-name, section
margin-bottom: 20px; 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; 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 { section.product-detail div.layouter .rating-reviews, section.product-review div.layouter .rating-reviews {
margin-top: 30px; margin-top: 30px;
} }
@ -1189,13 +1286,13 @@ section.product-detail div.layouter .details .rating, section.product-review div
margin-bottom: 14px; 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; 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; font-size: 24px;
color: #FF6472;
} }
section.product-detail div.layouter .details .stock-status, section.product-review div.layouter .details .stock-status { section.product-detail div.layouter .details .stock-status, section.product-review div.layouter .details .stock-status {
@ -1206,9 +1303,19 @@ section.product-detail div.layouter .details .description, section.product-revie
margin-bottom: 14px; margin-bottom: 14px;
} }
section.product-detail div.layouter .details hr, section.product-review div.layouter .details hr { section.product-detail div.layouter .details .full-specifications td, section.product-review div.layouter .details .full-specifications td {
border-top: 1px solid #E8E8E8; padding: 10px 0;
margin-bottom: 17px; 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 { section.product-detail div.layouter .details .attributes, section.product-review div.layouter .details .attributes {
@ -1296,13 +1403,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 { section.product-detail div.layouter .details .rating-reviews .title, section.product-review div.layouter .details .rating-reviews .title {
margin-bottom: 15px; margin-bottom: 15px;
font-weight: 600;
} }
section.product-detail div.layouter .details .rating-reviews .overall, section.product-review div.layouter .details .rating-reviews .overall { section.product-detail div.layouter .details .rating-reviews .overall, section.product-review div.layouter .details .rating-reviews .overall {
margin-bottom: 5px; 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; font-size: 34px;
} }
@ -1322,12 +1430,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 { 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; 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 { 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; 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 { 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; margin-top: 15px;
color: #0031f0; color: #0031f0;
@ -1567,18 +1685,29 @@ section.cart .cart-content .right-side .coupon-section .after-coupon-amount .amo
font-weight: bold; font-weight: bold;
} }
.related-products-wrapper { .attached-products-wrapper {
margin-bottom: 80px; margin-bottom: 80px;
} }
.related-products-wrapper .title { .attached-products-wrapper .title {
margin-bottom: 22px; margin-bottom: 40px;
font-size: 18px; font-size: 18px;
color: #242424; color: #242424;
text-align: center; 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; height: 1px;
background: #E8E8E8; background: #E8E8E8;
width: 148px; width: 148px;
@ -1586,9 +1715,3 @@ section.cart .cart-content .right-side .coupon-section .after-coupon-amount .amo
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
} }
.related-products-wrapper .related-products {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
grid-gap: 10px;
}