Merge pull request #28 from jitendra-webkul/jitendra

Product details page added
This commit is contained in:
JItendra Singh 2018-08-30 18:53:32 +05:30 committed by GitHub
commit 70ddd4a914
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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',
'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',

View File

@ -154,6 +154,14 @@
<option value="1">{{ __('admin::app.catalog.attributes.yes') }}</option>
</select>
</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>
</accordian>

View File

@ -210,6 +210,18 @@
</option>
</select>
</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>
</accordian>

View File

@ -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();
});
}

View File

@ -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.
*

View File

@ -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;
@ -14,7 +15,12 @@ class Core
* @return Collection
*/
public function getAllChannels() {
return ChannelModel::all();
static $channels;
if($channels)
return $channels;
return $channels = ChannelModel::all();
}
/**
@ -23,7 +29,12 @@ class Core
* @return mixed
*/
public function getCurrentChannel() {
return ChannelModel::first();
static $channel;
if($channel)
return $channel;
return $channel = ChannelModel::first();
}
/**
@ -32,7 +43,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 : '';
}
/**
@ -41,7 +57,12 @@ class Core
* @return Collection
*/
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()
{
return CurrencyModel::all();
static $currencies;
if($currencies)
return $currencies;
return $currencies = CurrencyModel::all();
}
/**
@ -61,9 +87,12 @@ class Core
*/
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()
{
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()
{
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);
/**
* 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 = [];
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;
}
}

View File

@ -16,4 +16,8 @@ class Customer extends Authenticatable
protected $table = 'customers';
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()
{
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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

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
namespace Webkul\Shop\Product;
namespace Webkul\Product\Product;
class Review extends AbstractProduct
{
/**
* Returns the product's avg rating
*
* @param Product $product
* @return float
*/
public function getAverageRating($product)
{
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)
{
$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;
}

View File

@ -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
);
}
}

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', [
'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('/products/{slug}', 'shop::store.product.details.index');
// Route::view('/products/{slug}', 'shop::products.view');
//customer routes starts here
Route::prefix('customer')->group(function () {

View File

@ -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;

View File

@ -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%;
@ -451,6 +453,11 @@ section.slider-block {
width: 100%;
}
.main {
display: inline-block;
width: 75%
}
.product-grid {
display: grid;
grid-gap: 30px;
@ -471,58 +478,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;
}
}
}
@ -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 {
background-color: $footer-back;
padding-left: 10%;
@ -805,7 +907,7 @@ section.slider-block {
.section-head {
.profile-heading {
font-size: 28px;
color: #242424;
color: $font-color;
text-transform: capitalize;
text-align: left;
}
@ -912,18 +1014,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;
}
}
}
}
@ -1059,12 +1151,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;
}
}
@ -1076,9 +1168,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 {
@ -1160,21 +1264,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 {
@ -1182,7 +1286,6 @@ section.product-detail, section.product-review {
border-radius: 0px !important;
}
margin-bottom: 5px;
}
.reviews {
@ -1194,11 +1297,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;
@ -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 */
@ -1462,14 +1551,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 {
@ -1480,11 +1580,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;
}
}

View File

@ -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;
}

View File

@ -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!'
]
];

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')
@include ('shop::products.layered-navigation')
@include ('shop::products.list.layered-navigation')
<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 = $productHelper->getCollection($category->id); ?>
@foreach ($products as $product)
<?php $products = $productRepository->findAllByCategory($category->id); ?>
@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">
{{ $products->appends(request()->input())->links() }}
@ -28,8 +44,4 @@
</div>
@stop
@push('scripts')
@endpush
@stop

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')
<div class="layered-filter-wrapper">
<layered-navigation></layered-navigation>
</div>
@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 = [];

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,
'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.');

View File

@ -116,7 +116,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.');

View File

@ -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;
}
@ -522,6 +535,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: 30px;
@ -552,49 +570,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;
@ -604,12 +609,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;
@ -691,6 +775,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%;
@ -1013,21 +1121,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;
}
@ -1189,13 +1286,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 {
@ -1206,9 +1303,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 {
@ -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 {
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;
}
@ -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 {
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;
@ -1567,18 +1685,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;
@ -1586,9 +1715,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;
}