Compare commits

1 Commits

1123 changed files with 25872 additions and 43432 deletions

View File

@@ -1,4 +1,3 @@
#Application
APP_ENV=local
APP_DEBUG=true
APP_CACHE=false
@@ -6,13 +5,10 @@ APP_INSTALLED=false
APP_KEY=
APP_URL=http://localhost
#Ignition
IGNITION_EDITOR=vscode
#Logging
LOG_CHANNEL=stack
#Database
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
@@ -20,24 +16,15 @@ DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret
#Queue
QUEUE_DRIVER=sync
#Cache
CACHE_DRIVER=file
#Session
SESSION_DRIVER=file
#File System
FILESYSTEM_DRIVER=public_storage
#Redis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
#Mail
MAIL_DRIVER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
@@ -47,23 +34,15 @@ MAIL_FROM_ADDRESS=null
MAIL_FROM_NAME=null
MAIL_ENCRYPTION=null
#Mailchimp
MAILCHIMP_APIKEY=
#AWS
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
#Scout
SCOUT_QUEUE=false
#Laravel DataTables (yajra/laravel-datatables)
MAILCHIMP_APIKEY=
DATATABLES_ERROR=null
#Debugbar for Laravel (barryvdh/laravel-debugbar)
DEBUGBAR_ENABLED=false
#Laravel Query Detector (beyondcode/laravel-query-detector)
QUERY_DETECTOR_ENABLED=false

48
.gitignore vendored
View File

@@ -1,47 +1,21 @@
#Meta
.meta/
.idea/
.phpintel/
*.sublime-project
.DS_Store
.vscode
#Env
.env
#Dependencies
node_modules/
vendor/
#Assets
public/images/
public/modules/
public/themes/
public/build/
public/pwa/
Modules/*/Assets/
Themes/*/assets/
#Fonts
Themes/Storefront/resources/assets/public/fonts/
#Storage
public/storage/
#Logs
/vendor/
/public/storage/
/public/modules/
/public/themes/
/storage/logs/*
public/phplog
storage/logs/*
Modules/*/yarn-error.log
/Modules/*/Assets/
/Themes/*/assets/
/tags
/.meta
mix-manifest.json
npm-debug.log
yarn-error.log
*.log
#Others
tools/
*.map
tags/
public/hot
mix-manifest.json
public/sitemaps/*
public/sitemap.xml
.php-cs-fixer.cache
.env

View File

@@ -18,18 +18,16 @@ class AccountAddressController extends Controller
]);
}
public function store(SaveAddressRequest $request)
{
$address = auth()->user()->addresses()->create($request->all());
return response()->json([
'address' => $address,
'message' => trans('account::messages.address_created'),
'message' => trans('account::messages.address_saved'),
]);
}
public function update(SaveAddressRequest $request, $id)
{
$address = Address::find($id);
@@ -37,11 +35,10 @@ class AccountAddressController extends Controller
return response()->json([
'address' => $address,
'message' => trans('account::messages.address_updated'),
'message' => trans('account::messages.address_saved'),
]);
}
public function destroy($id)
{
auth()->user()->addresses()->find($id)->delete();

View File

@@ -2,14 +2,12 @@
namespace Modules\Account\Http\Controllers;
use Illuminate\Http\Response;
class AccountDashboardController
{
/**
* Display a listing of the resource.
*
* @return Response
* @return \Illuminate\Http\Response
*/
public function index()
{

View File

@@ -3,7 +3,6 @@
namespace Modules\Account\Http\Controllers;
use Illuminate\Routing\Controller;
use Illuminate\Contracts\Support\Renderable;
use Modules\Address\Entities\DefaultAddress;
class AccountDefaultAddressController extends Controller
@@ -11,7 +10,7 @@ class AccountDefaultAddressController extends Controller
/**
* Update the specified resource in storage.
*
* @return Renderable
* @return \Illuminate\Contracts\Support\Renderable
*/
public function update()
{

View File

@@ -2,7 +2,6 @@
namespace Modules\Account\Http\Controllers;
use Illuminate\Http\Response;
use Modules\Order\Entities\Order;
class AccountDownloadsController
@@ -10,7 +9,7 @@ class AccountDownloadsController
/**
* Display a listing of the resource.
*
* @return Response
* @return \Illuminate\Http\Response
*/
public function index()
{
@@ -19,26 +18,23 @@ class AccountDownloadsController
]);
}
/**
* Display the specified resource.
*
* @param int $id
*
* @return Response
* @return \Illuminate\Http\Response
*/
public function show($id)
{
$file = $this->getDownloads()->firstWhere('id', decrypt($id));
if (is_null($file) || !file_exists($file->realPath())) {
if (is_null($file) || ! file_exists($file->realPath())) {
return back()->with('error', trans('storefront::account.downloads.no_file_found'));
}
return response()->download($file->realPath(), $file->filename);
}
private function getDownloads()
{
return auth()->user()

View File

@@ -2,14 +2,12 @@
namespace Modules\Account\Http\Controllers;
use Illuminate\Http\Response;
class AccountOrdersController
{
/**
* Display a listing of the resource.
*
* @return Response
* @return \Illuminate\Http\Response
*/
public function index()
{
@@ -21,13 +19,11 @@ class AccountOrdersController
return view('public.account.orders.index', compact('orders'));
}
/**
* Display the specified resource.
*
* @param int $id
*
* @return Response
* @return \Illuminate\Http\Response
*/
public function show($id)
{

View File

@@ -2,7 +2,6 @@
namespace Modules\Account\Http\Controllers;
use Illuminate\Http\Response;
use Modules\User\Http\Requests\UpdateProfileRequest;
class AccountProfileController
@@ -10,7 +9,7 @@ class AccountProfileController
/**
* Show the form for editing the specified resource.
*
* @return Response
* @return \Illuminate\Http\Response
*/
public function edit()
{
@@ -19,17 +18,15 @@ class AccountProfileController
]);
}
/**
* Update the specified resource in storage.
*
* @param UpdateProfileRequest $request
*
* @return Response
* @param \Modules\User\Http\Requests\UpdateProfileRequest $request
* @return \Illuminate\Http\Response
*/
public function update(UpdateProfileRequest $request)
{
$request->bcryptPassword();
$request->bcryptPassword($request);
auth()->user()->update($request->all());

View File

@@ -2,19 +2,22 @@
namespace Modules\Account\Http\Controllers;
use Illuminate\Contracts\View\View;
use Illuminate\Contracts\View\Factory;
use Illuminate\Contracts\Foundation\Application;
class AccountReviewController
{
/**
* Display a listing of the resource.
*
* @return Application|Factory|View
* @return \Illuminate\Http\Response
*/
public function index(): View|Factory|Application
public function index()
{
return view('public.account.reviews.index');
$reviews = auth()->user()
->reviews()
->withoutGlobalScope('approved')
->with('product.files')
->whereHas('product')
->paginate(20);
return view('public.account.reviews.index', compact('reviews'));
}
}

View File

@@ -2,14 +2,12 @@
namespace Modules\Account\Http\Controllers;
use Illuminate\Http\Response;
class AccountWishlistController
{
/**
* Display a listing of the resource.
*
* @return Response
* @return \Illuminate\Http\Response
*/
public function index()
{

View File

@@ -13,7 +13,6 @@ class SaveAddressRequest extends Request
*/
protected $availableAttributes = 'account::attributes.addresses';
/**
* Get the validation rules that apply to the request.
*

View File

@@ -1,9 +1,8 @@
<?php
return [
'profile_updated' => 'Your profile has been updated',
'default_address_updated' => 'The default address has been updated',
'address_created' => 'The address has been created',
'address_updated' => 'The address has been updated',
'address_deleted' => 'The address has been deleted',
'profile_updated' => 'Your profile has been updated.',
'default_address_updated' => 'The default address has been updated.',
'address_saved' => 'The address has been saved.',
'address_deleted' => 'The address has been deleted.',
];

View File

@@ -30,7 +30,6 @@ class CreateAddressesTable extends Migration
});
}
/**
* Reverse the migrations.
*

View File

@@ -23,7 +23,6 @@ class CreateDefaultAddressesTable extends Migration
});
}
/**
* Reverse the migrations.
*

View File

@@ -13,25 +13,21 @@ class Address extends Model
protected $appends = ['full_name', 'state_name', 'country_name'];
public function customer()
{
return $this->belongsTo(User::class);
}
public function getFullNameAttribute()
{
return $this->first_name . ' ' . $this->last_name;
}
public function getStateNameAttribute()
{
return State::name($this->country, $this->state);
}
public function getCountryNameAttribute()
{
return Country::name($this->country);

View File

@@ -6,47 +6,42 @@ use Illuminate\Database\Eloquent\Model;
class DefaultAddress extends Model
{
public $timestamps = false;
protected $with = ['address'];
protected $fillable = ['customer_id', 'address_id'];
public $timestamps = false;
public function address()
{
return $this->belongsTo(Address::class);
}
public function getAddress1Attribute()
{
return $this->address->address_1;
}
public function getAddress2Attribute()
{
return $this->address->address_1;
}
public function getCityAttribute()
{
return $this->address->city;
}
public function getStateAttribute()
{
return $this->address->state;
}
public function getZipAttribute()
{
return $this->address->zip;
}
public function getCountryAttribute()
{
return $this->address->country;

View File

@@ -2,20 +2,18 @@
namespace Modules\Admin\Http\Controllers\Admin;
use Illuminate\Http\Response;
use Modules\User\Entities\User;
use Modules\Order\Entities\Order;
use Modules\Review\Entities\Review;
use Modules\Product\Entities\Product;
use Modules\Product\Entities\SearchTerm;
use Illuminate\Database\Eloquent\Collection;
class DashboardController
{
/**
* Display the dashboard with its widgets.
*
* @return Response
* @return \Illuminate\Http\Response
*/
public function index()
{
@@ -30,17 +28,15 @@ class DashboardController
]);
}
private function getLatestSearchTerms()
{
return SearchTerm::latest('updated_at')->take(5)->get();
}
/**
* Get latest five orders.
*
* @return Collection
* @return \Illuminate\Database\Eloquent\Collection
*/
private function getLatestOrders()
{
@@ -54,11 +50,10 @@ class DashboardController
])->latest()->take(5)->get();
}
/**
* Get latest five reviews.
*
* @return Collection
* @return \Illuminate\Database\Eloquent\Collection
*/
private function getLatestReviews()
{

View File

@@ -2,7 +2,6 @@
namespace Modules\Admin\Http\Controllers\Admin;
use Illuminate\Http\Response;
use Modules\Order\Entities\Order;
class SalesAnalyticsController
@@ -10,9 +9,8 @@ class SalesAnalyticsController
/**
* Display a listing of the resource.
*
* @param Order $order
*
* @return Response
* @param \Modules\Order\Entities\Order $order
* @return \Illuminate\Http\Response
*/
public function index(Order $order)
{

View File

@@ -9,19 +9,18 @@ use Maatwebsite\Sidebar\Presentation\SidebarRenderer;
class AdminSidebarCreator
{
/**
* @var AdminSidebar
* @var \Modules\Admin\Sidebar\AdminSidebar
*/
protected $sidebar;
/**
* @var SidebarRenderer
* @var \Maatwebsite\Sidebar\Presentation\SidebarRenderer
*/
protected $renderer;
/**
* @param AdminSidebar $sidebar
* @param SidebarRenderer $renderer
* @param \Modules\Admin\Sidebar\AdminSidebar $sidebar
* @param \Maatwebsite\Sidebar\Presentation\SidebarRenderer $renderer
*/
public function __construct(AdminSidebar $sidebar, SidebarRenderer $renderer)
{
@@ -29,7 +28,6 @@ class AdminSidebarCreator
$this->renderer = $renderer;
}
public function create(View $view)
{
$view->sidebar = $this->renderer->render($this->sidebar);

View File

@@ -4,11 +4,16 @@ namespace Modules\Admin\Providers;
use Modules\Admin\Ui\Facades\Form;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\Facades\View;
use Modules\Support\Traits\AddsAsset;
use Illuminate\Foundation\AliasLoader;
use Illuminate\Support\ServiceProvider;
use Modules\Admin\Http\ViewComposers\AssetsComposer;
class AdminServiceProvider extends ServiceProvider
{
use AddsAsset;
/**
* Bootstrap any application services.
*
@@ -16,9 +21,11 @@ class AdminServiceProvider extends ServiceProvider
*/
public function boot()
{
View::composer('admin::layout', AssetsComposer::class);
Paginator::defaultSimpleView('admin::pagination.simple');
}
$this->addAdminAssets('admin.dashboard.index', ['admin.dashboard.css', 'admin.dashboard.js']);
}
/**
* Register the service provider.

View File

@@ -17,7 +17,7 @@ class SidebarServiceProvider extends ServiceProvider
*/
public function boot(SidebarManager $manager)
{
if (!config('app.installed')) {
if (! config('app.installed')) {
return;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

View File

@@ -1,4 +1,4 @@
import NProgress from "nprogress";
import NProgress from 'nprogress';
export default class {
constructor() {
@@ -14,52 +14,43 @@ export default class {
}
selectize() {
let selects = $("select.selectize").removeClass(
"form-control custom-select-black"
);
let selects = $('select.selectize').removeClass('form-control custom-select-black');
let options = _.merge(
{
valueField: "id",
labelField: "name",
searchField: "name",
delimiter: ",",
persist: true,
selectOnTab: true,
hideSelected: true,
allowEmptyOption: true,
onItemAdd(value) {
this.getItem(value)[0].innerHTML = this.getItem(
value
)[0].innerHTML.replace(/¦––\s/g, "");
},
onInitialize() {
for (let index in this.options) {
let label = this.options[index].name;
let value = this.options[index].id;
this.$control
.find(`.item[data-value="${value}"]`)
.html(
label.replace(/¦––\s/g, "") +
'<a href="javascript:void(0)" class="remove" tabindex="-1">×</a>'
);
}
},
let options = _.merge({
valueField: 'id',
labelField: 'name',
searchField: 'name',
delimiter: ',',
persist: true,
selectOnTab: true,
hideSelected: false,
allowEmptyOption: true,
onItemAdd(value) {
this.getItem(value)[0].innerHTML = this.getItem(value)[0].innerHTML.replace(/¦––\s/g, '');
},
...FleetCart.selectize
);
onInitialize() {
for (let index in this.options) {
let label = this.options[index].name;
let value = this.options[index].id;
this.$control.find(`.item[data-value="${value}"]`).html(
label.replace(/¦––\s/g, '') +
'<a href="javascript:void(0)" class="remove" tabindex="-1">×</a>'
);
}
},
}, ...FleetCart.selectize);
for (let select of selects) {
select = $(select);
let create = true;
let plugins = ["remove_button", "restore_on_backspace"];
let plugins = ['remove_button', 'restore_on_backspace'];
if (select.hasClass("prevent-creation")) {
if (select.hasClass('prevent-creation')) {
create = false;
plugins.remove("restore_on_backspace");
plugins.remove('restore_on_backspace');
}
select.selectize(_.merge(options, { create, plugins }));
@@ -67,33 +58,33 @@ export default class {
}
dateTimePicker(elements) {
elements = elements || $(".datetime-picker");
elements = elements || $('.datetime-picker');
elements = elements instanceof jQuery ? elements : $(elements);
for (let el of elements) {
$(el).flatpickr({
mode: el.hasAttribute("data-range") ? "range" : "single",
enableTime: el.hasAttribute("data-time"),
noCalender: el.hasAttribute("data-no-calender"),
mode: el.hasAttribute('data-range') ? 'range' : 'single',
enableTime: el.hasAttribute('data-time'),
noCalender: el.hasAttribute('data-no-calender'),
altInput: true,
});
}
}
changeAccordionTabState() {
$('.accordion-box [data-toggle="tab"]').on("click", (e) => {
if (!$(e.currentTarget).parent().hasClass("active")) {
$(".accordion-tab li.active").removeClass("active");
$('.accordion-box [data-toggle="tab"]').on('click', (e) => {
if (! $(e.currentTarget).parent().hasClass('active')) {
$('.accordion-tab li.active').removeClass('active');
}
});
}
preventChangingCurrentTab() {
$('[data-toggle="tab"]').on("click", (e) => {
$('[data-toggle="tab"]').on('click', (e) => {
let targetElement = $(e.currentTarget);
if (targetElement.parent().hasClass("active")) {
if (targetElement.parent().hasClass('active')) {
return false;
}
});
@@ -102,85 +93,76 @@ export default class {
removeSubmitButtonOffsetOn(tabs, tabsSelector = null) {
tabs = Array.isArray(tabs) ? tabs : [tabs];
$(tabsSelector || ".accordion-tab li > a").on("click", (e) => {
if (tabs.includes(e.currentTarget.getAttribute("href"))) {
$(tabsSelector || '.accordion-tab li > a').on('click', (e) => {
if (tabs.includes(e.currentTarget.getAttribute('href'))) {
setTimeout(() => {
$("button[type=submit]")
.parent()
.removeClass("col-md-offset-2");
$('button[type=submit]').parent().removeClass('col-md-offset-2');
}, 150);
} else {
setTimeout(() => {
$("button[type=submit]")
.parent()
.addClass("col-md-offset-2");
$('button[type=submit]').parent().addClass('col-md-offset-2');
}, 150);
}
});
}
buttonLoading() {
$(document).on("click", "[data-loading]", (e) => {
$(document).on('click', '[data-loading]', (e) => {
let button = $(e.currentTarget);
button
.data("loading-text", button.html())
.addClass("btn-loading")
.button("loading");
button.data('loading-text', button.html())
.addClass('btn-loading')
.button('loading');
});
}
stopButtonLoading(button) {
button = button instanceof jQuery ? button : $(button);
button
.data("loading-text", button.html())
.removeClass("btn-loading")
.button("reset");
button.data('loading-text', button.html())
.removeClass('btn-loading')
.button('reset');
}
confirmationModal() {
let confirmationModal = $("#confirmation-modal");
let confirmationModal = $('#confirmation-modal');
$("[data-confirm]").on("click", () => {
confirmationModal.modal("show");
$('[data-confirm]').on('click', () => {
confirmationModal.modal('show');
});
confirmationModal.find("form").on("submit", () => {
confirmationModal.find("button.delete").prop("disabled", true);
confirmationModal.find('form').on('submit', () => {
confirmationModal.find('button.delete').prop('disabled', true);
});
confirmationModal.on("hidden.bs.modal", () => {
confirmationModal.find("button.delete").prop("disabled", false);
confirmationModal.on('hidden.bs.modal', () => {
confirmationModal.find('button.delete').prop('disabled', false);
});
confirmationModal.on("shown.bs.modal", () => {
confirmationModal.find("button.delete").focus();
confirmationModal.on('shown.bs.modal', () => {
confirmationModal.find('button.delete').focus();
});
}
tooltip() {
$('[data-toggle="tooltip"]')
.tooltip({ trigger: "hover" })
.on("click", (e) => {
$(e.currentTarget).tooltip("hide");
$('[data-toggle="tooltip"]').tooltip({ trigger : 'hover' })
.on('click', (e) => {
$(e.currentTarget).tooltip('hide');
});
}
shortcuts() {
Mousetrap.bind("f1", () => {
window.open("https://docs.envaysoft.com/", "_blank");
Mousetrap.bind('f1', () => {
window.open(`http://envaysoft.com/fleetcart/docs/${FleetCart.version}`, '_blank');
});
Mousetrap.bind("?", () => {
$("#keyboard-shortcuts-modal").modal();
Mousetrap.bind('?', () => {
$('#keyboard-shortcuts-modal').modal();
});
}
nprogress() {
let inMobile = /iphone|ipod|android|ie|blackberry|fennec/i.test(
window.navigator.userAgent
);
let inMobile = /iphone|ipod|android|ie|blackberry|fennec/i.test(window.navigator.userAgent);
if (inMobile) {
return;

View File

@@ -1,6 +1,3 @@
import "datatables.net";
import "datatables.net-bs";
// Initialize state holders.
FleetCart.dataTable = { routes: {}, selected: {} };
@@ -27,7 +24,7 @@ export default class {
{
serverSide: true,
processing: true,
ajax: this.route("table", { table: true }),
ajax: this.route("index", { table: true }),
stateSave: true,
sort: true,
info: true,
@@ -37,6 +34,9 @@ export default class {
autoWidth: false,
pageLength: 20,
lengthMenu: [10, 20, 50, 100, 200],
language: {
processing: '<i class="fa fa-refresh fa-spin"></i>',
},
order: [
sortColumn.index() !== -1 ? sortColumn.index() : 1,
sortColumn.data("sort") || "desc",
@@ -74,6 +74,7 @@ export default class {
});
},
stateSaveParams(settings, data) {
delete data.start;
delete data.search;
},
},
@@ -135,6 +136,7 @@ export default class {
deleted = _.flatten(deleted.concat(ids));
this.constructor.setSelectedIds(this.selector, []);
this.constructor.reload(this.element);
},
error: (xhr) => {
@@ -143,6 +145,7 @@ export default class {
deleted = _.flatten(deleted.concat(ids));
this.constructor.setSelectedIds(this.selector, []);
this.constructor.reload(this.element);
},
});
@@ -154,6 +157,10 @@ export default class {
let url = this.route(key, { id });
$(row).addClass("clickable-row").data("href", url);
setTimeout(() => {
$(".clickable-row td:not(:first-child)").css("cursor", "pointer");
});
}
onRowClick(handler) {

View File

@@ -1,56 +0,0 @@
import Vue from "vue";
export default class {
constructor() {
this.errors = {};
}
record(errors) {
this.errors = Object.assign({}, this.errors, errors);
}
any() {
return Object.keys(this.errors).length > 0;
}
has(key) {
return this.errors.hasOwnProperty(this.normalizeKey(key));
}
get(key) {
if (this.errors[this.normalizeKey(key)]) {
return this.errors[this.normalizeKey(key)][0];
}
}
set(errors = {}) {
this.errors = Object.assign({}, this.errors, errors);
}
clear(keys) {
if (keys === undefined) {
return;
}
keys = Array.isArray(keys) ? keys : [keys];
keys.forEach((key) => {
Vue.delete(this.errors, this.normalizeKey(key));
});
}
reset() {
this.errors = {};
}
normalizeKey(key) {
let keyParts = key.split("[");
// No need to normalize the key.
if (keyParts.length === 1) {
return key;
}
return keyParts.join(".").slice(0, -1).replace(/]/g, "");
}
}

View File

@@ -1,245 +0,0 @@
$.FleetCart = {};
/* ----------------------------------
- FleetCart Options -
---------------------------------- */
$.FleetCart.options = {
animationSpeed: 300,
// Sidebar push menu toggle button selector
sidebarToggleSelector: "[data-toggle='offcanvas']",
// Activate sidebar push menu
sidebarPushMenu: true,
// BoxRefresh Plugin
enableBoxRefresh: true,
// Bootstrap.js tooltip
enableBSToppltip: true,
BSTooltipSelector: "[data-toggle='tooltip']",
// Control Sidebar Tree views
enableControlTreeView: true,
// The standard screen sizes that bootstrap uses.
screenSizes: {
xs: 480,
sm: 768,
md: 992,
lg: 1200,
},
};
/* ----------------------------------
- Implementation -
---------------------------------- */
$(function () {
// Easy access to options
var o = $.FleetCart.options;
// Set up the object
_init();
// Activate layout
$.FleetCart.layout.activate();
// Enable sidebar tree view controls
if (o.enableControlTreeView) {
$.FleetCart.tree(".sidebar");
}
// Activate sidebar push menu
if (o.sidebarPushMenu) {
$.FleetCart.pushMenu.activate(o.sidebarToggleSelector);
}
// Activate Bootstrap tooltip
if (o.enableBSToppltip) {
$("body").tooltip({
selector: o.BSTooltipSelector,
container: "body",
});
}
});
/* ----------------------------------
- Initialize the FleetCart Object -
---------------------------------- */
function _init() {
// Layout
$.FleetCart.layout = {
activate: function () {
var _this = this;
_this.fix();
$(window, ".wrapper").resize(function () {
_this.fix();
});
},
fix: function () {
var window_height = $(window).height();
$(".wrapper").css("min-height", window_height + "px");
},
};
// PushMenu
$.FleetCart.pushMenu = {
activate: function (toggleBtn) {
var screenSizes = $.FleetCart.options.screenSizes;
$(document).on("click", toggleBtn, function (e) {
e.preventDefault();
if ($(window).outerWidth() > screenSizes.md - 1) {
if ($("body").hasClass("sidebar-collapse")) {
$("body")
.removeClass("sidebar-collapse")
.trigger("expanded.pushMenu");
return;
}
$("body")
.addClass("sidebar-collapse")
.trigger("collapsed.pushMenu");
return;
}
if ($("body").hasClass("sidebar-open")) {
$("body")
.removeClass("sidebar-open")
.removeClass("sidebar-collapse")
.trigger("collapsed.pushMenu");
return;
}
$("body").addClass("sidebar-open").trigger("expanded.pushMenu");
});
$(window).on("resize", function () {
if ($(window).outerWidth() > screenSizes.md - 1) {
return;
} else {
$("body").removeClass("sidebar-collapse");
}
});
$(".content-wrapper").click(function () {
if (
$(window).width() <= screenSizes.md - 1 &&
$("body").hasClass("sidebar-open")
) {
$("body").removeClass("sidebar-open");
}
});
},
};
// Tree
$.FleetCart.tree = function (menu) {
var animationSpeed = $.FleetCart.options.animationSpeed;
$(document)
.off("click", menu + " li a")
.on("click", menu + " li a", function (e) {
var self = $(this);
var checkElement = self.next();
var activeElement = self
.closest(".sidebar-menu")
.find(".active");
if (checkElement.is(".treeview-menu")) {
self.closest(".sidebar-menu")
.find(".selected")
.removeClass("selected");
e.preventDefault();
}
if (self.parent().is(".active")) {
activeElement.toggleClass("closed");
} else {
activeElement.addClass("closed");
}
if (
checkElement.is(".treeview-menu") &&
checkElement.is(":visible") &&
!$("body").hasClass("sidebar-collapse")
) {
self.parent().removeClass("selected");
checkElement.slideUp(animationSpeed);
} else if (
checkElement.is(".treeview-menu") &&
!checkElement.is(":visible")
) {
var ul = self
.parents("ul")
.first()
.find("ul:visible")
.slideUp(animationSpeed);
self.parent().addClass("selected");
checkElement.slideDown(animationSpeed);
}
});
};
}
/* ----------------------------------
- Box Refresh Button -
---------------------------------- */
(function ($) {
$.fn.boxRefresh = function (options) {
var settings = $.extend(
{
trigger: ".refresh-btn",
source: "",
onLoadStart: function (box) {
return box;
},
onLoadDone: function (box) {
return box;
},
},
options
);
var overlay = $(
'<div class="overlay"><div class="fa fa-refresh fa-spin"></div></div>'
);
return this.each(function () {
if (settings.source === "") {
if (window.console) {
window.console.log(
"Please specify a source first - boxRefresh()"
);
}
return;
}
var box = $(this);
var rBtn = box.find(settings.trigger).first();
rBtn.on("click", function (e) {
e.preventDefault();
start(box);
box.find(".box-body").load(settings.source, function () {
done(box);
});
});
});
function start(box) {
box.append(overlay);
settings.onLoadStart.call(box);
}
function done(box) {
box.find(overlay).remove();
settings.onLoadDone.call(box);
}
};
})(jQuery);

View File

@@ -1,16 +0,0 @@
import Vue from "vue";
import VueToast from "vue-toast-notification";
Vue.use(VueToast);
export function toaster(message, options = {}) {
Vue.$toast.open({
message,
type: options.type || "default",
duration: 5000,
dismissible: true,
position: "bottom-right",
pauseOnHover: true,
...options,
});
}

View File

@@ -1,48 +1,215 @@
import "bootstrap";
import "selectize";
import "flatpickr";
import "mousetrap";
import "./FleetCart";
import "./jquery.keypressAction";
$.FleetCart = {};
import Admin from "./Admin";
import Form from "./Form";
import DataTable from "./DataTable";
import {
trans,
keypressAction,
notify,
info,
success,
warning,
error,
} from "./functions";
/* ----------------------------------
- FleetCart Options -
---------------------------------- */
$.FleetCart.options = {
animationSpeed: 300,
// Sidebar push menu toggle button selector
sidebarToggleSelector: '[data-toggle=\'offcanvas\']',
// Activate sidebar push menu
sidebarPushMenu: true,
// BoxRefresh Plugin
enableBoxRefresh: true,
// Bootstrap.js tooltip
enableBSToppltip: true,
BSTooltipSelector: '[data-toggle=\'tooltip\']',
// Control Sidebar Tree views
enableControlTreeView: true,
// The standard screen sizes that bootstrap uses.
screenSizes: {
xs: 480,
sm: 768,
md: 992,
lg: 1200,
},
};
if (
!route().current("admin.products.create") &&
!route().current("admin.products.edit")
) {
window.admin = new Admin();
/* ----------------------------------
- Implementation -
---------------------------------- */
$(function () {
// Easy access to options
var o = $.FleetCart.options;
// Set up the object
_init();
// Activate layout
$.FleetCart.layout.activate();
// Enable sidebar tree view controls
if (o.enableControlTreeView) {
$.FleetCart.tree('.sidebar');
}
// Activate sidebar push menu
if (o.sidebarPushMenu) {
$.FleetCart.pushMenu.activate(o.sidebarToggleSelector);
}
// Activate Bootstrap tooltip
if (o.enableBSToppltip) {
$('body').tooltip({
selector: o.BSTooltipSelector,
container: 'body',
});
}
});
/* ----------------------------------
- Initialize the FleetCart Object -
---------------------------------- */
function _init() {
// Layout
$.FleetCart.layout = {
activate: function () {
var _this = this;
_this.fix();
$(window, '.wrapper').resize(function () {
_this.fix();
});
},
fix: function () {
var window_height = $(window).height();
$('.wrapper').css('min-height', window_height + 'px');
}
};
// PushMenu
$.FleetCart.pushMenu = {
activate: function (toggleBtn) {
var screenSizes = $.FleetCart.options.screenSizes;
$(document).on('click', toggleBtn, function (e) {
e.preventDefault();
if ($(window).outerWidth() > (screenSizes.md - 1)) {
if ($('body').hasClass('sidebar-collapse')) {
$('body').removeClass('sidebar-collapse').trigger('expanded.pushMenu');
return;
}
$('body').addClass('sidebar-collapse').trigger('collapsed.pushMenu');
return;
}
if ($('body').hasClass('sidebar-open')) {
$('body').removeClass('sidebar-open').removeClass('sidebar-collapse').trigger('collapsed.pushMenu');
return;
}
$('body').addClass('sidebar-open').trigger('expanded.pushMenu');
});
$(window).on('resize', function () {
if ($(window).outerWidth() > (screenSizes.md - 1)) {
return;
} else {
$('body').removeClass('sidebar-collapse');
}
});
$('.content-wrapper').click(function () {
if ($(window).width() <= (screenSizes.md - 1) && $('body').hasClass('sidebar-open')) {
$('body').removeClass('sidebar-open');
}
});
}
};
// Tree
$.FleetCart.tree = function (menu) {
var animationSpeed = $.FleetCart.options.animationSpeed;
$(document).off('click', menu + ' li a')
.on('click', menu + ' li a', function (e) {
var self = $(this);
var checkElement = self.next();
var activeElement = self.closest('.sidebar-menu').find('.active');
if (checkElement.is('.treeview-menu')) {
self.closest('.sidebar-menu').find('.selected').removeClass('selected');
e.preventDefault();
}
if (self.parent().is('.active')) {
activeElement.toggleClass('closed');
} else {
activeElement.addClass('closed');
}
if ((checkElement.is('.treeview-menu')) && (checkElement.is(':visible')) && (!$('body').hasClass('sidebar-collapse'))) {
self.parent().removeClass('selected');
checkElement.slideUp(animationSpeed);
}
else if ((checkElement.is('.treeview-menu')) && (!checkElement.is(':visible'))) {
var ul = self.parents('ul').first().find('ul:visible').slideUp(animationSpeed);
self.parent().addClass('selected');
checkElement.slideDown(animationSpeed);
}
});
};
}
window.form = new Form();
window.DataTable = DataTable;
/* ----------------------------------
- Box Refresh Button -
---------------------------------- */
(function ($) {
$.fn.boxRefresh = function (options) {
var settings = $.extend({
trigger: '.refresh-btn',
source: '',
onLoadStart: function (box) {
return box;
},
onLoadDone: function (box) {
return box;
},
}, options);
window.trans = trans;
window.keypressAction = keypressAction;
window.notify = notify;
window.info = info;
window.success = success;
window.warning = warning;
window.error = error;
var overlay = $('<div class="overlay"><div class="fa fa-refresh fa-spin"></div></div>');
$.ajaxSetup({
headers: {
Authorization: FleetCart.apiToken,
"X-CSRF-TOKEN": FleetCart.csrfToken,
},
});
return this.each(function () {
if (settings.source === '') {
if (window.console) {
window.console.log('Please specify a source first - boxRefresh()');
}
$(document).on("preInit.dt", () => {
$(".dataTables_length select").addClass("custom-select-black");
});
return;
}
var box = $(this);
var rBtn = box.find(settings.trigger).first();
rBtn.on('click', function (e) {
e.preventDefault();
start(box);
box.find('.box-body').load(settings.source, function () {
done(box);
});
});
});
function start(box) {
box.append(overlay);
settings.onLoadStart.call(box);
}
function done(box) {
box.find(overlay).remove();
settings.onLoadDone.call(box);
}
};
})(jQuery);

View File

@@ -1,16 +1,11 @@
import Chart from "chart.js/auto";
import Chart from 'chart.js';
$(function () {
$.ajax({
type: "GET",
url: route("admin.sales_analytics.index"),
type: 'GET',
url: route('admin.sales_analytics.index'),
success(response) {
let data = {
labels: response.labels,
sales: [],
formatted: [],
totalOrders: [],
};
let data = { labels: response.labels, sales: [], formatted: [], totalOrders: [] };
for (let item of response.data) {
data.sales.push(item.total.amount);
@@ -24,56 +19,45 @@ $(function () {
});
function initSalesAnalyticsChart(data) {
new Chart($(".sales-analytics .chart"), {
type: "bar",
new Chart($('.sales-analytics .chart'), {
type: 'bar',
data: {
labels: data.labels,
datasets: [
{
data: data.sales,
backgroundColor: [
"rgba(255, 99, 132, 0.5)",
"rgba(54, 162, 235, 0.5)",
"rgba(255, 206, 86, 0.5)",
"rgba(75, 192, 192, 0.5)",
"rgba(153, 102, 255, 0.5)",
"rgba(255, 159, 64, 0.5)",
],
},
],
datasets: [{
data: data.sales,
backgroundColor: [
'rgba(255, 99, 132, 0.5)',
'rgba(54, 162, 235, 0.5)',
'rgba(255, 206, 86, 0.5)',
'rgba(75, 192, 192, 0.5)',
'rgba(153, 102, 255, 0.5)',
'rgba(255, 159, 64, 0.5)',
],
}],
},
barThickness: 1,
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: false,
tooltip: {
displayColors: false,
callbacks: {
label(item) {
let orders = `${trans(
"admin::dashboard.sales_analytics.orders"
)}: ${data.totalOrders[item.dataIndex]}`;
legend: {
display: false,
},
tooltips: {
displayColors: false,
callbacks: {
label(item) {
let orders = `${trans('admin::dashboard.sales_analytics.orders')}: ${data.totalOrders[item.index]}`;
let sales = `${trans('admin::dashboard.sales_analytics.sales')}: ${data.formatted[item.index]}`;
let sales = `${trans(
"admin::dashboard.sales_analytics.sales"
)}: ${data.formatted[item.dataIndex]}`;
return [orders, sales];
},
return [orders, sales];
},
},
},
scales: {
y: {
beginAtZero: true,
yAxes: [{
ticks: {
// Include the currency symbol in the ticks
callback: function (value) {
return data.formatted[0].charAt(0) + value;
},
beginAtZero: true,
},
},
}],
},
},
});

View File

@@ -1,4 +1,4 @@
import { ohSnap } from "./ohsnap";
import { ohSnap } from './ohsnap';
export function trans(langKey, replace = {}) {
let line = window.FleetCart.langs[langKey];
@@ -16,14 +16,14 @@ export function keypressAction(actions) {
export function notify(type, message, { duration = 5000, context = document }) {
let types = {
info: "blue",
success: "green",
warning: "yellow",
error: "red",
'info': 'blue',
'success': 'green',
'warning': 'yellow',
'error': 'red',
};
ohSnap(message, {
"container-id": "notification-toast",
'container-id': 'notification-toast',
context,
color: types[type],
duration,
@@ -31,60 +31,27 @@ export function notify(type, message, { duration = 5000, context = document }) {
}
export function info(message, duration) {
notify("info", message, { duration });
notify('info', message, { duration });
}
export function success(message, duration) {
notify("success", message, { duration });
notify('success', message, { duration });
}
export function warning(message, duration) {
notify("warning", message, { duration });
notify('warning', message, { duration });
}
export function error(message, duration) {
notify("error", message, { duration });
}
export function generateSlug(name) {
let slug = "";
// Change to lower case
const nameLower = name.toLowerCase();
// Letter "e"
slug = nameLower.replace(/e|é|è|ẽ|ẻ|ẹ|ê|ế|ề|ễ|ể|ệ/gi, "e");
// Letter "a"
slug = slug.replace(/a|á|à|ã|ả|ạ|ă|ắ|ằ|ẵ|ẳ|ặ|â|ấ|ầ|ẫ|ẩ|ậ/gi, "a");
// Letter "o"
slug = slug.replace(/o|ó|ò|õ|ỏ|ọ|ô|ố|ồ|ỗ|ổ|ộ|ơ|ớ|ờ|ỡ|ở|ợ/gi, "o");
// Letter "u"
slug = slug.replace(/u|ú|ù|ũ|ủ|ụ|ư|ứ|ừ|ữ|ử|ự/gi, "u");
// Letter "c"
slug = slug.replace(/ć|ĉ|č|ċ|ç/gi, "c");
// Letter "i"
slug = slug.replace(/î|ï|í|ī|į|ì/gi, "i");
// Letter (/, ', ")
slug = slug.replace(/\/|'|"|||,|\?|\.|;|]|\[|\+|=|\$|%|&|<|>|:/g, " ");
// Letter "d"
slug = slug.replace(/đ/gi, "d");
// Trim the last whitespace
slug = slug.replace(/\s*$/g, "");
// Change whitespace to "-"
slug = slug.replace(/\s+/g, "-");
return slug;
notify('error', message, { duration });
}
/**
* @see https://stackoverflow.com/a/3955096
*/
if (!Array.prototype.remove) {
if (! Array.prototype.remove) {
Array.prototype.remove = function () {
let what,
a = arguments,
L = a.length,
ax;
let what, a = arguments, L = a.length, ax;
while (L && this.length) {
what = a[--L];
@@ -101,12 +68,10 @@ if (!Array.prototype.remove) {
/**
* @see https://stackoverflow.com/a/4673436
*/
if (!String.prototype.format) {
if (! String.prototype.format) {
String.prototype.format = function () {
return this.replace(/%(\d+)%/g, (match, number) => {
return typeof arguments[number] !== "undefined"
? arguments[number]
: match;
return typeof arguments[number] !== 'undefined' ? arguments[number] : match;
});
};
}

View File

@@ -1,7 +1,43 @@
import jQuery from "jquery";
import _ from "lodash";
import Sortable from "sortablejs";
window._ = require('lodash');
window.Sortable = require('sortablejs');
window.$ = window.jQuery = require('jquery');
window.$ = window.jQuery = jQuery;
window._ = _;
window.Sortable = Sortable;
require('bootstrap');
require('selectize');
require('flatpickr');
require('jquery-slimscroll');
require('mousetrap');
require('datatables.net');
require('datatables.net-bs');
require('./app');
require('./wysiwyg');
require('./jquery.keypressAction');
import Admin from './Admin';
import Form from './Form';
import DataTable from './DataTable';
import { trans, keypressAction, notify, info, success, warning, error } from './functions';
window.admin = new Admin();
window.form = new Form();
window.DataTable = DataTable;
window.trans = trans;
window.keypressAction = keypressAction;
window.notify = notify;
window.info = info;
window.success = success;
window.warning = warning;
window.error = error;
$.ajaxSetup({
headers: {
'Authorization': FleetCart.apiToken,
'X-CSRF-TOKEN': FleetCart.csrfToken,
},
});
$(document).on('preInit.dt', () => {
$('.dataTables_length select').addClass('custom-select-black');
});

View File

@@ -1,46 +1,38 @@
import tinyMCE from "tinymce";
import tinyMCE from 'tinymce';
export default function () {
tinyMCE.baseURL = `${FleetCart.baseUrl}/build/assets/tinymce`;
tinyMCE.baseURL = `${FleetCart.baseUrl}/modules/admin/js/wysiwyg`;
tinyMCE.init({
selector: ".wysiwyg",
theme: "silver",
height: 350,
menubar: false,
branding: false,
image_advtab: true,
automatic_uploads: true,
media_alt_source: false,
media_poster: false,
relative_urls: false,
toolbar_mode: "sliding", // Possible values: floating, sliding, scrolling, wrap
directionality: FleetCart.rtl ? "rtl" : "ltr",
cache_suffix: `?v=${FleetCart.version}`,
plugins:
"lists, link, table, image, media, paste, autosave, autolink,quickbars, wordcount, code, fullscreen",
toolbar:
"styleselect | bold italic underline strikethrough blockquote | bullist numlist | alignleft aligncenter alignright alignjustify | outdent indent | forecolor removeformat | table | image media link | code fullscreen",
quickbars_selection_toolbar:
"bold italic | quicklink h2 h3 blockquote quickimage quicktable",
images_upload_handler(blobInfo, success, failure) {
let formData = new FormData();
tinyMCE.init({
selector: '.wysiwyg',
theme: 'silver',
mobile: { theme: 'mobile' },
height: 350,
menubar: false,
branding: false,
image_advtab: true,
automatic_uploads: true,
media_alt_source: false,
media_poster: false,
relative_urls: false,
directionality: FleetCart.rtl ? 'rtl' : 'ltr',
cache_suffix: `?v=${FleetCart.version}`,
plugins: 'lists, link, table, image, media, paste, autosave, autolink, wordcount, code, fullscreen',
toolbar: 'styleselect bold italic underline | bullist numlist | alignleft aligncenter alignright | outdent indent | image media link table | code fullscreen',
formData.append("file", blobInfo.blob(), blobInfo.filename());
images_upload_handler(blobInfo, success, failure) {
let formData = new FormData();
formData.append('file', blobInfo.blob(), blobInfo.filename());
$.ajax({
method: "POST",
url: route("admin.media.store"),
data: formData,
processData: false,
contentType: false,
})
.then((file) => {
success(file.path);
})
.catch((xhr) => {
failure(xhr.responseJSON.message);
});
},
});
}
$.ajax({
method: 'POST',
url: route('admin.media.store'),
data: formData,
processData: false,
contentType: false,
}).then((file) => {
success(file.path);
}).catch((xhr) => {
failure(xhr.responseJSON.message);
});
},
});

View File

@@ -1,6 +1,6 @@
.accordion-content {
background: #ffffff;
padding: 20px 5px;
padding: 20px 0px;
border-radius: 3px;
box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.1);
}
@@ -57,7 +57,7 @@
top: 12px;
font-size: 20px;
line-height: 25px;
color: #333333;
color: #000000;
transform: rotateX(180deg);
transition: all 200ms ease-in-out;
}
@@ -68,13 +68,14 @@
position: absolute;
font-family: FontAwesome;
content: "\f107";
top: 15px;
right: 15px;
right: 10px;
top: 12px;
font-size: 20px;
line-height: 25px;
color: #333333;
color: #000000;
transform: rotateX(180deg);
transition: all 200ms ease-in-out;
top: 15px;
}
.accordion-box .panel-heading [data-toggle="collapse"].collapsed:after,
@@ -84,12 +85,12 @@
}
.accordion-box .panel-heading [data-toggle="collapse"].collapsed:hover:after {
color: #333333;
color: #000000;
}
.accordion-box-content {
.panel-heading [data-toggle="collapse"].collapsed:hover:after {
color: #333333;
color: #000000;
}
.panel-group .panel + .panel {
@@ -101,20 +102,19 @@
.accordion-box {
.panel-title a {
position: relative;
padding: 10px 15px;
padding: 12px 15px;
display: block;
text-decoration: none;
outline: none;
&.has-error {
border-left: 2px solid #ff3366;
&.has-error.collapsed {
border-left: 3px solid #ff3366;
}
}
.panel-body a {
color: #333333;
display: block;
padding: 12px 15px;
padding: 14px 15px;
transition: 200ms ease-in-out;
&:hover {
@@ -175,7 +175,7 @@
border-bottom: none;
> li {
float: none !important;
float: none;
z-index: 0;
> a {
@@ -193,7 +193,7 @@
}
&.has-error > a {
border-left: 2px solid #ff3366;
border-left: 3px solid #ff3366;
}
&.active > a {
@@ -206,12 +206,9 @@
&:focus {
border-left: 3px solid #0068e1;
border-bottom-color: #e9e9e9;
border-right: 0;
}
}
&.active.has-error > a {
border-left: 2px solid #ff3366;
}
}
&.nav-tabs > li.active > a {
@@ -226,22 +223,19 @@
.content-accordion {
&.panel-group {
margin-bottom: 12px;
margin-bottom: 15px;
}
.panel {
border: 1px solid #ebebed;
border-radius: 3px;
box-shadow: none;
border: none;
border: 1px solid #e9e9e9;
}
.panel-heading {
background: #f6f6f7;
padding: 0;
border-radius: 0;
}
.panel-title [data-toggle="collapse"] {
.panel-title a {
display: block;
padding: 15px;
@@ -250,19 +244,9 @@
&:focus {
color: #333333;
}
&.has-error {
border-left: 2px solid #ff3366;
border-radius: 2px 0 0 0;
&.collapsed {
border-radius: 2px 0 0 2px;
}
}
}
.panel-default > .panel-heading + .panel-collapse > .panel-body {
border-top-color: #ebebed;
border-top-color: #e9e9e9;
}
}

View File

@@ -1,69 +1,68 @@
.alert {
border: none;
color: #555555;
font-size: 14px;
border-radius: 3px;
border: none;
color: #555555;
font-size: 15px;
padding: 12px 15px;
border-radius: 3px;
.close {
right: 0;
outline: 0;
opacity: 0.5;
color: #626060;
text-shadow: none;
font-weight: normal;
transition: 150ms ease-in-out;
.close {
top: 4px;
right: 0;
outline: 0;
opacity: 0.5;
color: #626060;
text-shadow: none;
font-weight: normal;
transition: 200ms ease-in-out;
&:hover {
opacity: 0.9;
}
}
&:hover {
opacity: 0.9;
}
}
.alert-text {
display: block;
}
.alert-text {
display: block;
margin: 6px 20px 0 45px;
}
}
.alert-icon {
float: left;
width: 30px;
height: 30px;
display: table;
border-radius: 50%;
text-align: center;
float: left;
width: 30px;
height: 30px;
display: table;
border-radius: 50%;
text-align: center;
> i {
font-size: 18px;
display: table-cell;
vertical-align: middle;
}
> i {
font-size: 18px;
display: table-cell;
vertical-align: middle;
}
}
.alert-success {
background: #deedee;
border-left: 3px solid #37bc9b;
background: #deedee;
border-left: 3px solid #37bc9b;
.alert-icon {
background: #c5e6e2;
.alert-icon {
background: #c5e6e2;
> i {
color: #37bc9b;
}
}
> i {
color: #37bc9b;
}
}
}
.alert-danger {
background: #f2e8ee;
border-left: 3px solid #ff3366;
background: #f2e8ee;
border-left: 3px solid #ff3366;
.alert-icon {
background: #f4ced5;
.alert-icon {
background: #f4ced5;
> i {
color: #ff3366;
}
}
}
.alert-dismissible {
padding-right: 20px;
> i {
color: #ff3366;
}
}
}

View File

@@ -1,19 +1,17 @@
/* buttons */
.btn {
font-size: 14px;
font-weight: 500;
font-size: 15px;
border: none;
border-radius: 3px;
padding: 8px 16px;
transition: 150ms ease-in-out;
padding: 10px 20px;
transition: 200ms ease-in-out;
}
.btn-default {
background: #f1f1f1;
outline: 0;
border-color: #dddddd;
&.focus,
&:focus,
&.active {
@@ -67,10 +65,6 @@
}
}
.btn-delete {
margin-left: 10px;
}
fieldset[disabled] .btn-default {
&:focus,
&.focus,
@@ -316,9 +310,9 @@ fieldset[disabled] .btn-info {
right: 0;
bottom: 0;
margin: auto;
height: 14px;
width: 14px;
border: 1px solid #ffffff;
height: 16px;
width: 16px;
border: 2px solid #ffffff;
border-radius: 100%;
border-right-color: transparent;
border-top-color: transparent;
@@ -326,7 +320,7 @@ fieldset[disabled] .btn-info {
}
&.btn-default:after {
border: 1px solid #0068e1;
border: 2px solid #0068e1;
border-right-color: transparent;
border-top-color: transparent;
}
@@ -386,35 +380,26 @@ fieldset[disabled] .btn-info {
/* label */
.label {
font-weight: 500;
display: inline-block;
font-weight: normal;
padding: 5px 10px;
}
.label-default {
color: #4b5563;
background: #e5e7eb;
background: #d2d6de;
}
.label-primary {
color: #3b82f6;
background: #dbeafe;
background: #0068e1;
}
.label-success {
color: #16a34a;
background: #bbf7d0;
background: #37bc9b;
}
.label-danger {
color: #ef4444;
background: #fee2e2;
background: #fc4b4b;
}
.label-warning {
color: #c08304;
background: #fdeba3;
}
/* form error */
@@ -444,8 +429,8 @@ fieldset[disabled] .btn-info {
.input-group-addon {
color: #ff3366;
background: #f6f6f7;
border-color: #d9d9d9;
background-color: #f2dede;
border-color: #ff3366;
}
.form-control-feedback {
@@ -453,15 +438,13 @@ fieldset[disabled] .btn-info {
}
}
.input-group-addon {
background: #f6f6f7;
border-color: #d9d9d9;
.help-block {
margin-bottom: 0;
}
.checkbox {
label {
font-size: 14px;
font-weight: 400 !important;
font-size: 15px;
color: #333333;
margin-bottom: 0 !important;
}
@@ -475,7 +458,7 @@ fieldset[disabled] .btn-info {
&:checked + label,
&:not(:checked) + label {
font-family: "Inter", sans-serif;
font-family: "Roboto", sans-serif;
position: relative;
padding-left: 28px;
cursor: pointer;
@@ -493,7 +476,7 @@ fieldset[disabled] .btn-info {
height: 17px;
border-radius: 3px;
background: #e9e9e9;
transition: 150ms ease-in-out;
transition: 200ms ease-in-out;
}
&:checked + label:after,
@@ -506,7 +489,7 @@ fieldset[disabled] .btn-info {
left: 2px;
color: #ffffff;
-webkit-text-stroke: 1px #0068e1;
transition: 150ms ease-in-out;
transition: 200ms ease-in-out;
}
&:checked + label:before {
@@ -526,6 +509,8 @@ fieldset[disabled] .btn-info {
}
}
/* radio button */
.radio {
text-align: left;
@@ -542,7 +527,7 @@ fieldset[disabled] .btn-info {
&:checked + label,
&:not(:checked) + label {
font-family: "Inter", sans-serif;
font-family: "Roboto", sans-serif;
position: relative;
padding-left: 28px;
cursor: pointer;
@@ -572,7 +557,7 @@ fieldset[disabled] .btn-info {
top: 4px;
left: 3px;
border-radius: 100%;
transition: 150ms ease-in-out;
transition: 200ms ease-in-out;
}
&:not(:checked) + label:after {
@@ -584,7 +569,7 @@ fieldset[disabled] .btn-info {
top: 3px;
left: 3px;
border-radius: 100%;
transition: 150ms ease-in-out;
transition: 200ms ease-in-out;
opacity: 0;
transform: scale(0);
}
@@ -604,60 +589,11 @@ fieldset[disabled] .btn-info {
margin-top: 0;
}
.switch {
[type="checkbox"] {
display: none;
&:checked + label::before {
background: #0068e1;
}
&:checked + label::after {
left: 16px;
}
}
label {
font-weight: 400 !important;
position: relative;
min-height: 20px;
margin-bottom: 0 !important;
padding-left: 46px;
cursor: pointer;
&:before {
content: "";
position: absolute;
left: 0;
top: 4px;
height: 12px;
width: 30px;
background: #e9e9e9;
border-radius: 8px;
transition: 150ms ease-in-out;
}
&:after {
content: "";
position: absolute;
left: -4px;
top: 1px;
height: 18px;
width: 18px;
background: #ffffff;
border-radius: 16px;
box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.3);
transition: 150ms ease-in-out;
}
}
}
/* select option */
.custom-select-white {
appearance: none;
background: #f9f9f9 url("../images/arrow-white.png") no-repeat right 8px
center;
background: #f9f9f9 url('../images/arrow-white.png') no-repeat right 8px center;
background-size: 10px;
line-height: normal !important;
height: 40px;
@@ -668,9 +604,9 @@ fieldset[disabled] .btn-info {
.custom-select-black {
appearance: none;
background: #ffffff url("../images/arrow-black.png") no-repeat right 8px
center;
background: #ffffff url('../images/arrow-black.png') no-repeat right 8px center;
background-size: 10px;
line-height: normal !important;
height: 40px;
padding: 0 30px 0 10px;
border-radius: 3px;

View File

@@ -1,125 +1,99 @@
.content {
> .row {
> div {
padding-right: 10px;
padding-left: 10px;
&:first-child {
padding-left: 15px;
}
&:last-child {
padding-right: 15px;
}
}
}
}
.grid {
> .row {
> div {
margin-bottom: 20px;
padding-left: 10px;
padding-right: 10px;
.single-grid {
position: relative;
overflow: hidden;
height: 130px;
padding: 0 15px;
background: #ffffff;
border-radius: 3px;
box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.1);
&:first-child {
padding-left: 15px;
}
&:last-child {
padding-right: 15px;
}
}
}
.single-grid {
position: relative;
display: flex;
flex-direction: column;
justify-content: space-between;
height: 130px;
padding: 25px;
border-radius: 3px;
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.05);
.title {
font-weight: 600;
display: block;
color: #ffffff;
text-transform: uppercase;
h4 {
margin: 22px 0;
}
.count {
span {
font-size: 26px;
font-weight: 500;
color: #ffffff;
margin-top: 3px;
}
i {
font-size: 96px;
font-size: 36px;
position: absolute;
top: 10px;
right: -10px;
color: rgba(255, 255, 255, 0.25);
}
&.total-sales {
background: #f24a78;
left: 15px;
bottom: 22px;
margin: 0;
}
&.total-orders {
background: #2faedf;
i {
color: rgba(0, 104, 225, 0.7);
}
}
&.total-products {
background: #3e8ce8;
&.total-sales {
i {
color: rgba(112, 124, 210, 0.7);
}
}
&.total-customers {
background: #3abcbf;
i {
color: rgba(255, 51, 102, 0.7);
}
}
}
}
.grid-header {
overflow: auto;
h5 {
margin: 12px 0;
float: left;
}
&.total-products {
i {
color: rgba(55, 188, 155, 0.7);
}
}
}
}
.dashboard-panel {
margin-bottom: 20px;
margin-top: 30px;
padding: 0 15px;
background: #ffffff;
overflow: hidden;
border-radius: 3px;
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.05);
&:last-child {
margin-bottom: 0;
}
&.sales-analytics {
padding: 0 15px 15px;
.chart {
height: 282px !important;
}
}
box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.1);
.table-responsive {
margin-bottom: 10px;
}
}
.grid-header {
overflow: auto;
h4 {
margin: 15px 0;
float: left;
i {
margin-right: 8px;
}
}
}
.sales-analytics {
background: #ffffff;
margin-top: 30px;
padding: 0 15px 15px;
border-radius: 3px;
box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.1);
.chart {
height: 282px !important;
}
}
.anchor-table {
.table {
> tbody {
> tr {
> td {
.table {
> tbody {
> tr {
> td {
padding: 0;
a {
@@ -140,7 +114,7 @@
color: #0059bd;
}
}
}
}
}
}
}
@@ -158,42 +132,21 @@
}
@media screen and (max-width: 1199px) {
.content {
> .row {
> div {
padding-left: 15px;
padding-right: 15px;
&:first-child {
margin-bottom: 20px;
}
}
.single-grid {
&.total-customers {
margin-top: 15px;
}
}
.grid {
> .row {
> div {
&:nth-child(2) {
margin-bottom: 20px;
padding-right: 15px;
}
&:nth-child(3) {
padding-left: 15px;
}
}
&.total-products {
margin-top: 15px;
}
}
}
@media screen and (max-width: 767px) {
.grid {
> .row {
> div {
padding-left: 15px;
padding-right: 15px;
}
.single-grid {
&.total-orders {
margin-top: 15px;
}
}
}

View File

@@ -1,4 +1,14 @@
@import "datatables.net-bs/css/dataTables.bootstrap";
.index-table {
.label {
padding: 6px 10px;
}
> .loading-spinner {
display: table;
margin: 0 auto;
margin-bottom: 10px;
}
}
.dataTable.table {
border-bottom: 1px solid #e9e9e9;
@@ -6,20 +16,24 @@
> {
thead > tr > th,
tfoot > tr > th {
font-family: "Inter", sans-serif;
font-family: 'Open Sans', sans-serif;
color: #4a4a4a;
padding: 10px 15px;
border-color: #e9e9e9;
&:after {
padding: 5px;
}
}
tbody > tr {
transition: 150ms ease-in-out;
transition: 200ms ease-in-out;
&:first-child > td {
border: none;
}
&:nth-of-type(2n + 1) {
&:nth-of-type(2n+1) {
background: #ffffff;
&:hover {
@@ -27,12 +41,6 @@
}
}
&.clickable-row {
> td:not(:first-child) {
cursor: pointer;
}
}
> td {
padding: 15px;
vertical-align: middle;
@@ -79,32 +87,6 @@
table.dataTable {
margin: 5px 0 !important;
&.table-hover {
> tbody {
> tr:hover {
> * {
box-shadow: none;
}
}
}
}
&.table-striped {
> tbody {
> tr {
&.odd > * {
box-shadow: none;
}
&:hover {
> * {
box-shadow: none;
}
}
}
}
}
thead {
.sorting,
.sorting_asc,
@@ -139,24 +121,23 @@ div.dataTables_wrapper {
}
div.dataTables_processing {
position: absolute;
top: 0;
left: 0;
margin: 0;
width: 100%;
height: 100%;
margin: 0 !important;
background: rgba(255, 255, 255, 0.7);
border: 0;
text-align: center;
background: rgba(255, 255, 255, 0.7);
z-index: 999;
transform: translate(-50%, -50%);
> div {
.fa-spin {
position: absolute;
left: 50%;
top: 50%;
margin: 0;
transform: translate(-50%, -50%);
> div {
background: #0068e1;
}
left: 50%;
color: #000;
font-size: 30px;
}
}
@@ -169,10 +150,8 @@ div.dataTables_wrapper {
}
}
div.dataTables_processing {
&.panel {
box-shadow: none;
}
.btn-delete {
margin-left: 10px;
}
.dataTables_empty {
@@ -212,9 +191,30 @@ div.dataTables_processing {
}
div.dataTables_wrapper {
> .row > .col-sm-6 {
width: 100%;
}
div.dataTables_filter {
text-align: left;
margin-top: 15px;
}
}
}
@media screen and (max-width: 340px) {
div.dataTables_length {
display: block;
.btn-delete {
display: table;
margin: 15px 0 0 0;
}
}
div.dataTables_wrapper {
div.dataTables_length {
text-align: left;
}
}
}

View File

@@ -1,33 +1,43 @@
@import "flatpickr/dist/flatpickr";
@import '~flatpickr/dist/flatpickr';
.flatpickr-calendar {
box-shadow: 0 1px 8px rgba(0, 0, 0, 0.15);
}
.flatpickr-months {
.flatpickr-month {
height: 50px;
}
.flatpickr-prev-month,
.flatpickr-next-month {
top: 8px;
}
box-shadow: 0 1px 8px rgba(0, 0, 0, 0.15);
}
.rtl {
.flatpickr-prev-month,
.flatpickr-next-month {
transform: rotateY(180deg);
}
.flatpickr-calendar {
&:before,
&:after {
right: 22px;
left: auto;
}
}
}
.flatpickr-current-month {
height: auto;
.flatpickr-month {
height: 50px;
}
.flatpickr-prev-month,
.flatpickr-next-month {
top: 10px;
}
.rtl {
.flatpickr-prev-month,
.flatpickr-next-month {
transform: rotateY(180deg);
}
}
span.flatpickr-current-month {
.cur-month {
font-family: "Open Sans", sans-serif !important;
}
}
.flatpickr-weekdays {
span {
font-family: "Inter", sans-serif;
}
span {
font-family: "Open Sans", sans-serif;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,20 +1,21 @@
@import "bootstrap/dist/css/bootstrap";
@import "font-awesome/css/font-awesome";
@import "nprogress/nprogress";
@import "flatpickr/dist/ie";
@import "./datatables";
@import "./selectize";
@import "./toaster.scss";
@import "./ohsnap";
@import "./flatpickr";
@import "./classes";
@import "./tab";
@import "./alert";
@import "./modal";
@import "./utilities";
@import "./accordion";
@import "./fleetcart";
@import "./panel";
@import '~bootstrap/dist/css/bootstrap';
@import '~font-awesome/css/font-awesome';
@import '~nprogress/nprogress';
@import '~datatables.net-bs/css/dataTables.bootstrap';
@import '~flatpickr/dist/ie';
@import './datatables';
@import './selectize';
@import './wysiwyg';
@import './ohsnap';
@import './flatpickr';
@import './classes';
@import './tab';
@import './alert';
@import './modal';
@import './utilities';
@import './accordion';
@import './fleetcart';
@import './panel';
html {
direction: ltr;
@@ -25,11 +26,11 @@ html {
> i {
margin-right: 5px;
}
}
}
.overflow-hidden {
overflow: hidden;
overflow: hidden;
}
#nprogress {
@@ -48,13 +49,13 @@ html {
}
.sortable-ghost {
opacity: 0.8;
opacity: .2;
}
.btn-group.open .dropdown-toggle,
.btn-group .dropdown-toggle:active {
-webkit-box-shadow: none;
box-shadow: none;
-webkit-box-shadow: none;
box-shadow: none;
}
.dot {
@@ -73,33 +74,23 @@ html {
}
.options {
&.table > {
thead > {
tr > {
th {
padding: 6px 8px;
}
}
}
}
tr td:first-child {
width: 35px;
min-width: 35px;
width: 34px;
min-width: 34px;
}
tr td:last-child {
width: 60px;
}
.drag-handle {
.drag-icon {
font-size: 16px;
color: #737881;
cursor: move;
vertical-align: top;
margin-top: 11px;
margin-top: 12px;
white-space: nowrap;
display: inline-block;
cursor: move;
i {
float: left;
@@ -112,11 +103,7 @@ html {
.choose-file,
.delete-row {
padding: 8px 14px;
}
.delete-row {
color: #4d4d4d;
padding: 10px 15px;
}
}
@@ -128,7 +115,7 @@ html {
margin-left: -1px;
padding: 0;
border: 1px solid #e9e9e9;
transition: 150ms ease-in-out;
transition: 200ms ease-in-out;
&:hover {
cursor: pointer;
@@ -172,8 +159,7 @@ html {
background: transparent;
}
&:hover,
&:focus {
&:hover, &:focus {
color: #0068e1;
}
}
@@ -207,7 +193,3 @@ html {
.empty {
color: #626060;
}
[v-cloak] {
display: none;
}

View File

@@ -23,7 +23,7 @@
outline: 0;
margin-top: 2px;
-webkit-text-stroke: 1px #ffffff;
transition: 150ms ease-in-out;
transition: 200ms ease-in-out;
}
}

View File

@@ -1,21 +1,19 @@
.panel-wrap {
.panel {
margin-bottom: 15px;
box-shadow: none;
box-shadow: none;
border: 1px solid #e9e9e9;
border-radius: 3px;
.panel-header {
font-size: 16px;
font-weight: 500;
padding: 15px;
background: #f6f6f7;
border-bottom: 1px solid #e9e9e9;
.drag-handle {
.drag-icon {
font-size: 16px;
display: inline-block;
margin: 3px 10px 0 0;
margin: 2px 10px 0 0;
color: #737881;
cursor: move;
white-space: nowrap;
@@ -23,24 +21,19 @@
> i {
float: left;
&:last-child {
&:last-child {
margin-left: 1px;
}
}
}
.btn {
color: #737881;
padding: 0;
background: transparent;
i {
-webkit-text-stroke: 1px #f6f6f7;
}
&:hover {
color: #333333;
}
}
}
@@ -48,7 +41,7 @@
position: relative;
padding: 15px;
}
.panel-image {
position: absolute;
left: 15px;
@@ -108,4 +101,4 @@
}
}
}
}
}

View File

@@ -1,4 +1,4 @@
@import "selectize/dist/css/selectize";
@import '~selectize/dist/css/selectize';
.selectize-control:not(.multi) {
.selectize-input .item,
@@ -10,34 +10,21 @@
padding-right: 0 !important;
}
}
.selectize-control {
&.multi {
.selectize-input {
&.has-items {
padding: 4px 4px 1px;
}
> div {
padding: 3px 8px;
}
}
&.multi .selectize-input > div {
padding: 4px 8px;
}
&.single .selectize-input {
cursor: text;
&.full {
padding: 8px 12px;
}
&:after {
content: none;
}
> span {
display: flex;
padding: 2px 0;
padding: 2px 0
}
input {
@@ -52,7 +39,6 @@
display: flex;
align-items: center;
justify-content: center;
padding: 0;
border-left-color: #e9e9e9;
}
@@ -65,19 +51,22 @@
}
.selectize-input {
padding: 4px 12px;
border-radius: 3px;
border-color: #d9d9d9;
min-height: 36px;
min-height: 40px;
vertical-align: bottom;
box-shadow: none !important;
transition: border-color ease-in-out 0.15s;
transition: border-color ease-in-out .15s;
.dropdown-active {
-webkit-border-radius: 0;
border-radius: 0;
}
> input {
margin-top: 2px !important;
}
input {
font-size: 15px;
transition: 0ms !important;
@@ -98,7 +87,7 @@
&.focus {
border-color: #6f8dfd;
box-shadow: 0 0 2px rgba(30, 140, 190, 0.8);
box-shadow: 0 0 2px rgba(30, 140, 190, .8);
}
.item {
@@ -109,14 +98,6 @@
}
}
.ltr {
.selectize-input {
> input {
margin-top: 4px !important;
}
}
}
.selectize-dropdown {
[data-selectable] {
cursor: pointer;
@@ -127,7 +108,7 @@
}
.selectize-dropdown-content .create strong {
font-family: "Inter", sans-serif;
font-family: "Open Sans", sans-serif;
font-weight: 600;
}
}

View File

@@ -50,7 +50,7 @@
width: 0;
background: #0068e1;
height: 1px;
transition: 150ms ease-in-out;
transition: 200ms ease-in-out;
}
}

View File

@@ -1,23 +0,0 @@
@import "vue-toast-notification/dist/theme-sugar";
.v-toast {
padding: 20px 20px 15px;
}
.v-toast__item {
min-height: 3em;
.v-toast__icon {
display: none;
}
.v-toast__text {
padding: 0.5em 1em;
}
}
@media screen and (max-width: 991px) {
.v-toast {
padding: 15px 15px 10px !important;
}
}

View File

@@ -1,219 +1,191 @@
.p-tb-0 {
padding-top: 0;
padding-bottom: 0;
padding-top: 0;
padding-bottom: 0;
}
.p-tb-5 {
padding-top: 5px;
padding-bottom: 5px;
padding-top: 5px;
padding-bottom: 5px;
}
.p-tb-10 {
padding-top: 10px;
padding-bottom: 10px;
padding-top: 10px;
padding-bottom: 10px;
}
.p-tb-15 {
padding-top: 15px;
padding-bottom: 15px;
padding-top: 15px;
padding-bottom: 15px;
}
.p-t-0 {
padding-top: 0;
padding-top: 0;
}
.p-t-5 {
padding-top: 5px;
padding-top: 5px;
}
.p-t-10 {
padding-top: 10px;
padding-top: 10px;
}
.p-t-15 {
padding-top: 15px;
padding-top: 15px;
}
.p-b-0 {
padding-bottom: 0;
padding-bottom: 0;
}
.p-b-5 {
padding-bottom: 5px;
padding-bottom: 5px;
}
.p-b-10 {
padding-bottom: 10px;
padding-bottom: 10px;
}
.p-b-15 {
padding-bottom: 15px;
padding-bottom: 15px;
}
.p-l-0 {
padding-left: 0;
padding-left: 0;
}
.p-l-5 {
padding-left: 5px;
padding-left: 5px;
}
.p-l-10 {
padding-left: 10px;
padding-left: 10px;
}
.p-l-15 {
padding-left: 15px;
padding-left: 15px;
}
.p-r-0 {
padding-right: 0;
padding-right: 0;
}
.p-r-5 {
padding-right: 5px;
padding-right: 5px;
}
.p-r-10 {
padding-right: 10px;
padding-right: 10px;
}
.p-r-15 {
padding-right: 15px;
padding-right: 15px;
}
.m-tb-0 {
margin-top: 0;
margin-bottom: 0;
margin-top: 0;
margin-bottom: 0;
}
.m-tb-5 {
margin-top: 5px;
margin-bottom: 5px;
margin-top: 5px;
margin-bottom: 5px;
}
.m-tb-10 {
margin-top: 10px;
margin-bottom: 10px;
margin-top: 10px;
margin-bottom: 10px;
}
.m-tb-15 {
margin-top: 15px;
margin-bottom: 15px;
margin-top: 15px;
margin-bottom: 15px;
}
.m-t-0 {
margin-top: 0;
margin-top: 0;
}
.m-t-5 {
margin-top: 5px;
margin-top: 5px;
}
.m-t-10 {
margin-top: 10px !important;
margin-top: 10px;
}
.m-t-15 {
margin-top: 15px;
margin-top: 15px;
}
.m-b-0 {
margin-bottom: 0 !important;
margin-bottom: 0;
}
.m-b-5 {
margin-bottom: 5px;
margin-bottom: 5px;
}
.m-b-10 {
margin-bottom: 10px !important;
margin-bottom: 10px;
}
.m-b-15 {
margin-bottom: 15px;
}
.m-b-20 {
margin-bottom: 20px !important;
margin-bottom: 15px;
}
.m-l-0 {
margin-left: 0;
margin-left: 0;
}
.m-l-5 {
margin-left: 5px;
margin-left: 5px;
}
.m-l-10 {
margin-left: 10px;
margin-left: 10px;
}
.m-l-15 {
margin-left: 15px;
margin-left: 15px;
}
.m-r-0 {
margin-right: 0;
margin-right: 0;
}
.m-r-5 {
margin-right: 5px;
margin-right: 5px;
}
.m-r-10 {
margin-right: 10px;
margin-right: 10px;
}
.m-r-15 {
margin-right: 15px;
margin-right: 15px;
}
.no-border {
border: 0 !important;
border: 0 !important;
}
.no-padding {
padding: 0 !important;
padding: 0 !important;
}
.no-margin {
margin: 0 !important;
margin: 0 !important;
}
.no-shadow {
box-shadow: none !important;
box-shadow: none !important;
}
.cursor-auto {
cursor: auto !important;
}
.cursor-pointer {
cursor: pointer;
}
.cursor-move {
cursor: move;
cursor: auto !important;
}
.text-left {
text-align: left;
}
.d-block {
display: block;
}
.d-flex {
display: flex;
}
.align-items-center {
align-items: center;
}
.justify-content-between {
justify-content: space-between;
text-align: left;
}

View File

@@ -13,7 +13,3 @@
}
}
}
.tox-tinymce {
border-radius: 3px !important;
}

View File

@@ -1,49 +1,40 @@
<?php
return [
'admin' => 'Admin',
'visit_store' => 'Visit Store',
'form' => [
'please_select' => 'Please Select',
],
'buttons' => [
'save' => 'Save',
'delete' => 'Delete',
'cancel' => 'Cancel',
],
'table' => [
'id' => 'ID',
'status' => 'Status',
'created' => 'Created',
'updated' => 'Updated',
'date' => 'Date',
],
'pagination' => [
'previous' => 'Previous',
'next' => 'Next',
],
'delete' => [
'confirmation' => 'Confirmation',
'confirmation_message' => 'Are you sure you want to delete?',
],
'shortcuts' => [
'available_shortcuts' => 'Available keyboard shortcuts on this page',
'this_menu' => 'This Menu',
'back_to_index' => 'Back to :name Index',
],
'errors' => [
'404' => '404',
'404_title' => 'Oops! This page was not found',
'404_description' => 'The page you are looking for was not found',
'404_description' => 'The page you are looking for was not found.',
'500' => '500',
'500_title' => 'Oops! Something went wrong',
'500_description' => 'An administrator was notified',
'500_description' => 'An administrator was notified.',
],
];

View File

@@ -1,8 +1,7 @@
<?php
return [
'resource_created' => ':resource created',
'resource_updated' => ':resource updated',
'resource_deleted' => ':resource deleted',
'permission_denied' => 'Permission Denied (required permission: ":permission")',
'resource_saved' => ':resource has been saved.',
'resource_deleted' => ':resource has been deleted.',
'permission_denied' => 'Permission Denied (required permission: ":permission").',
];

View File

@@ -40,7 +40,7 @@
@endpush
@push('scripts')
<script type="module">
<script>
@if (isset($buttons) && in_array('create', $buttons))
keypressAction([
{ key: 'c', route: '{{ route("admin.{$resource}.create") }}'}
@@ -54,10 +54,10 @@
Mousetrap.bind('backspace', function () {
$('.btn-delete').trigger('click');
});
@isset($resource)
DataTable.setRoutes('#{{ $resource }}-table .table', {
table: '{{ "admin.{$resource}.table" }}',
index: '{{ "admin.{$resource}.index" }}',
edit: '{{ "admin.{$resource}.edit" }}',
destroy: '{{ "admin.{$resource}.destroy" }}',
});

View File

@@ -1,9 +1,8 @@
<div class="col-lg-3 col-md-6 col-sm-6">
<div class="single-grid total-customers">
<i class="fa fa-users" aria-hidden="true"></i>
<h4>{{ trans('admin::dashboard.total_customers') }}</h4>
<span class="title">{{ trans('admin::dashboard.total_customers') }}</span>
<span class="count">{{ $totalCustomers }}</span>
<i class="fa fa-users pull-left" aria-hidden="true"></i>
<span class="pull-right">{{ $totalCustomers }}</span>
</div>
</div>

View File

@@ -1,9 +1,8 @@
<div class="col-lg-3 col-md-6 col-sm-6">
<div class="single-grid total-orders">
<i class="fa fa-shopping-cart" aria-hidden="true"></i>
<h4>{{ trans('admin::dashboard.total_orders') }}</h4>
<span class="title">{{ trans('admin::dashboard.total_orders') }}</span>
<span class="count">{{ $totalOrders }}</span>
<i class="fa fa-shopping-cart pull-left" aria-hidden="true"></i>
<span class="pull-right">{{ $totalOrders }}</span>
</div>
</div>

View File

@@ -1,9 +1,8 @@
<div class="col-lg-3 col-md-6 col-sm-6">
<div class="single-grid total-products">
<h4>{{ trans('admin::dashboard.total_products') }}</h4>
<i class="fa fa-cubes" aria-hidden="true"></i>
<span class="title">{{ trans('admin::dashboard.total_products') }}</span>
<span class="count">{{ $totalProducts }}</span>
<span class="pull-right">{{ $totalProducts }}</span>
</div>
</div>

View File

@@ -1,9 +1,8 @@
<div class="col-lg-3 col-md-6 col-sm-6">
<div class="single-grid total-sales">
<i class="fa fa-money" aria-hidden="true"></i>
<h4>{{ trans('admin::dashboard.total_sales') }}</h4>
<span class="title">{{ trans('admin::dashboard.total_sales') }}</span>
<span class="count">{{ $totalSales->format() }}</span>
<i class="fa fa-money pull-left" aria-hidden="true"></i>
<span class="pull-right">{{ $totalSales->format() }}</span>
</div>
</div>

View File

@@ -3,7 +3,7 @@
@section('title', trans('admin::dashboard.dashboard'))
@section('content_header')
<h3 class="pull-left">{{ trans('admin::dashboard.dashboard') }}</h3>
<h2 class="pull-left">{{ trans('admin::dashboard.dashboard') }}</h2>
@endsection
@section('content')
@@ -44,10 +44,3 @@
</div>
</div>
@endsection
@push('globals')
@vite([
"Modules/Admin/Resources/assets/sass/dashboard.scss",
"Modules/Admin/Resources/assets/js/dashboard.js",
])
@endpush

View File

@@ -1,6 +1,6 @@
<div class="dashboard-panel">
<div class="grid-header">
<h5>{{ trans('admin::dashboard.latest_orders') }}</h5>
<h4><i class="fa fa-shopping-cart" aria-hidden="true"></i>{{ trans('admin::dashboard.latest_orders') }}</h4>
</div>
<div class="clearfix"></div>

View File

@@ -1,6 +1,6 @@
<div class="dashboard-panel">
<div class="grid-header">
<h5>{{ trans('admin::dashboard.latest_reviews') }}</h5>
<h4><i class="fa fa-comments-o" aria-hidden="true"></i>{{ trans('admin::dashboard.latest_reviews') }}</h4>
</div>
<div class="clearfix"></div>

View File

@@ -1,6 +1,6 @@
<div class="dashboard-panel">
<div class="grid-header">
<h5>{{ trans('admin::dashboard.latest_search_terms') }}</h5>
<h4><i class="fa fa-search" aria-hidden="true"></i>{{ trans('admin::dashboard.latest_search_terms') }}</h4>
</div>
<div class="clearfix"></div>

View File

@@ -1,6 +1,8 @@
<div class="dashboard-panel sales-analytics">
<div class="sales-analytics">
<div class="grid-header clearfix">
<h5>{{ trans('admin::dashboard.sales_analytics_title') }}</h5>
<h4>
<i class="fa fa-bar-chart" aria-hidden="true"></i>{{ trans('admin::dashboard.sales_analytics_title') }}
</h4>
</div>
<div class="canvas">

View File

@@ -1,57 +1,60 @@
<!DOCTYPE html>
<html lang="{{ locale() }}">
<head>
<base href="{{ url('/') }}">
<meta charset="UTF-8">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>
@yield('title') - {{ setting('store_name') }} {{trans('admin::admin.admin')}}
</title>
<head>
<base href="{{ url('/') }}">
<meta charset="UTF-8">
<meta name="csrf-token" content="{{ csrf_token() }}">
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
<title>
@yield('title') - {{ setting('store_name') }} Admin
</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
@vite([
'Modules/Admin/Resources/assets/sass/main.scss',
'Modules/Admin/Resources/assets/js/main.js',
'Modules/Admin/Resources/assets/js/app.js'
])
<link href="https://fonts.googleapis.com/css?family=Open+Sans:600|Roboto:400,500" rel="stylesheet">
@stack('styles')
@foreach ($assets->allCss() as $css)
<link media="all" type="text/css" rel="stylesheet" href="{{ v($css) }}">
@endforeach
@include('admin::partials.globals')
</head>
@stack('styles')
<body class="skin-blue sidebar-mini offcanvas clearfix {{ is_rtl() ? 'rtl' : 'ltr' }}" dir>
<div class="left-side"></div>
@include('admin::partials.globals')
</head>
@include('admin::partials.sidebar')
<body class="skin-blue sidebar-mini offcanvas clearfix {{ is_rtl() ? 'rtl' : 'ltr' }}">
<div class="left-side"></div>
<div class="wrapper">
<div class="content-wrapper">
@include('admin::partials.top_nav')
@include('admin::partials.sidebar')
<section class="content-header clearfix">
@yield('content_header')
</section>
<div class="wrapper">
<div class="content-wrapper">
@include('admin::partials.top_nav')
<section class="content">
@include('admin::partials.notification')
<section class="content-header clearfix">
@yield('content_header')
</section>
@yield('content')
</section>
<section class="content">
@include('admin::partials.notification')
<div id="notification-toast"></div>
</div>
@yield('content')
</section>
<div id="notification-toast"></div>
</div>
</div>
@include('admin::partials.footer')
@include('admin::partials.confirmation_modal')
@include('admin::partials.footer')
@include('admin::partials.confirmation_modal')
@foreach ($assets->allJs() as $js)
<script src="{{ v($js) }}"></script>
@endforeach
@stack('scripts')
</body>
@stack('scripts')
</body>
</html>

View File

@@ -6,7 +6,7 @@
<i class="fa fa-times" aria-hidden="true"></i>
</button>
<h4 class="modal-title">{{ trans('admin::admin.delete.confirmation') }}</h4>
<h3 class="modal-title">{{ trans('admin::admin.delete.confirmation') }}</h3>
</div>
<div class="modal-body">

View File

@@ -4,7 +4,7 @@
<div class="modal-content">
<div class="modal-header">
<a type="button" class="close" data-dismiss="modal" aria-label="Close">&times;</a>
<h5 class="modal-title">{{ trans('admin::admin.shortcuts.available_shortcuts') }}</h5>
<h4 class="modal-title">{{ trans('admin::admin.shortcuts.available_shortcuts') }}</h4>
</div>
<div class="modal-body">
@@ -21,11 +21,7 @@
<footer class="main-footer">
<div class="pull-right hidden-xs">
<span>
<strong>
v{{ fleetcart_version() }}
</strong>
</span>
<span>v{{ fleetcart_version() }}</span>
</div>
<a href="#" data-toggle="modal" data-target="#keyboard-shortcuts-modal">
@@ -34,6 +30,6 @@
<span>
Copyright &copy; {{ date('Y') }} <a href="{{ route('home') }}"
target="_blank"><strong>{{ setting('store_name') }}</strong></a>
target="_blank">{{ setting('store_name') }}</a>
</span>
</footer>

View File

@@ -12,6 +12,7 @@
FleetCart.langs['admin::admin.buttons.delete'] = '{{ trans('admin::admin.buttons.delete') }}';
FleetCart.langs['media::media.file_manager.title'] = '{{ trans('media::media.file_manager.title') }}';
FleetCart.langs['media::messages.image_has_been_added'] = '{{ trans('media::messages.image_has_been_added') }}';
</script>
@stack('globals')

View File

@@ -1,18 +1,23 @@
@if (session()->has('success'))
<div class="alert alert-success fade in alert-dismissible clearfix">
<div class="alert alert-success fade in alert-dismissable clearfix">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
<div class="alert-icon">
<i class="fa fa-check" aria-hidden="true"></i>
</div>
<span class="alert-text">{{ session('success') }}</span>
</div>
@endif
@if (session()->has('error'))
<div class="alert alert-danger fade in alert-dismissible clearfix">
<div class="alert alert-danger fade in alert-dismissable clearfix">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
<div class="alert-icon">
<i class="fa fa-exclamation" aria-hidden="true"></i>
</div>
<span class="alert-text">{{ session('error') }}</span>
</div>
@endif
@stack('notifications')

View File

@@ -1,17 +1,13 @@
<nav class="navbar navbar-static-top clearfix">
<ul class="nav navbar-nav clearfix">
<li class="visit-store hidden-sm hidden-xs">
<a href="{{ route('home') }}" target="_blank">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<title>storefront-outline</title>
<path d="M5.06 3C4.63 3 4.22 3.14 3.84 3.42S3.24 4.06 3.14 4.5L2.11 8.91C1.86 10 2.06 10.95 2.72 11.77L3 12.05V19C3 19.5 3.2 20 3.61 20.39S4.5 21 5 21H19C19.5 21 20 20.8 20.39 20.39S21 19.5 21 19V12.05L21.28 11.77C21.94 10.95 22.14 10 21.89 8.91L20.86 4.5C20.73 4.06 20.5 3.7 20.13 3.42C19.77 3.14 19.38 3 18.94 3H5.06M18.89 4.97L19.97 9.38C20.06 9.81 19.97 10.2 19.69 10.55C19.44 10.86 19.13 11 18.75 11C18.44 11 18.17 10.9 17.95 10.66C17.73 10.43 17.61 10.16 17.58 9.84L16.97 5L18.89 4.97M5.06 5H7.03L6.42 9.84C6.3 10.63 5.91 11 5.25 11C4.84 11 4.53 10.86 4.31 10.55C4.03 10.2 3.94 9.81 4.03 9.38L5.06 5M9.05 5H11V9.7C11 10.05 10.89 10.35 10.64 10.62C10.39 10.88 10.08 11 9.7 11C9.36 11 9.07 10.88 8.84 10.59S8.5 10 8.5 9.66V9.5L9.05 5M13 5H14.95L15.5 9.5C15.58 9.92 15.5 10.27 15.21 10.57C14.95 10.87 14.61 11 14.2 11C13.89 11 13.61 10.88 13.36 10.62C13.11 10.35 13 10.05 13 9.7V5M7.45 12.05C8.08 12.67 8.86 13 9.8 13C10.64 13 11.38 12.67 12 12.05C12.69 12.67 13.45 13 14.3 13C15.17 13 15.92 12.67 16.55 12.05C17.11 12.67 17.86 13 18.8 13H19.03V19H5V13H5.25C6.16 13 6.89 12.67 7.45 12.05Z" />
</svg>
<a href="{{ route('home') }}">
<i class="fa fa-desktop"></i>
{{ trans('admin::admin.visit_store') }}
</a>
</li>
<li class="user dropdown top-nav-menu pull-right">
<li class="dropdown top-nav-menu pull-right">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-user-circle-o"></i><span>{{ $currentUser->first_name }}</span>
</a>

View File

@@ -1,8 +0,0 @@
<?php
namespace Modules\Admin\Services;
class CRUDService implements CRUDAactions
{
}

View File

@@ -5,22 +5,21 @@ namespace Modules\Admin\Sidebar;
use Maatwebsite\Sidebar\Menu;
use Maatwebsite\Sidebar\Sidebar;
use Nwidart\Modules\Facades\Module;
use Nwidart\Modules\Contracts\RepositoryInterface as Modules;
class AdminSidebar implements Sidebar
{
/**
* The menu instance.
*
* @var Menu
* @var \Maatwebsite\Sidebar\Menu
*/
protected $menu;
/**
* Create a new sidebar instance.
*
* @param Menu $menu
*
* @param \Maatwebsite\Sidebar\Menu $menu
* @return void
*/
public function __construct(Menu $menu)
@@ -28,11 +27,10 @@ class AdminSidebar implements Sidebar
$this->menu = $menu;
}
/**
* Get the built menu.
*
* @return Menu
* @return \Maatwebsite\Sidebar\Menu
*/
public function getMenu()
{
@@ -41,7 +39,6 @@ class AdminSidebar implements Sidebar
return $this->menu;
}
/**
* Build the sidebar menu.
*
@@ -53,7 +50,6 @@ class AdminSidebar implements Sidebar
$this->addModuleExtenders();
}
/**
* Add active theme's sidebar extender.
*
@@ -66,24 +62,6 @@ class AdminSidebar implements Sidebar
$this->add("Themes\\{$theme}\\Sidebar\\SidebarExtender");
}
/**
* Add sidebar extender to the menu.
*
* @param string $extender
*
* @return void
*/
private function add($extender)
{
if (class_exists($extender)) {
resolve($extender)->extend($this->menu);
}
$this->menu->add($this->menu);
}
/**
* Add all enabled modules sidebar extender.
*
@@ -95,4 +73,19 @@ class AdminSidebar implements Sidebar
$this->add("Modules\\{$module->getName()}\\Sidebar\\SidebarExtender");
}
}
/**
* Add sidebar extender to the menu.
*
* @param string $extender
* @return void
*/
private function add($extender)
{
if (class_exists($extender)) {
resolve($extender)->extend($this->menu);
}
$this->menu->add($this->menu);
}
}

View File

@@ -8,7 +8,6 @@ class BaseSidebarExtender
{
protected $auth;
public function __construct(Authentication $auth)
{
$this->auth = $auth;

View File

@@ -3,21 +3,16 @@
namespace Modules\Admin\Traits;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Modules\Admin\Ui\AdminTable;
use Modules\Support\Eloquent\Model;
use Modules\Support\Search\Searchable;
use Modules\Admin\Ui\Facades\TabManager;
use Illuminate\Database\Eloquent\Model as EloquentModel;
trait HasCrudActions
{
/**
* Display a listing of the resource.
*
* @param Request $request
*
* @return Response
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function index(Request $request)
{
@@ -29,14 +24,32 @@ trait HasCrudActions
->get();
}
if ($request->has('table')) {
return $this->getModel()->table($request);
}
return view("{$this->viewPath}.index");
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
$data = array_merge([
'tabs' => TabManager::get($this->getModel()->getTable()),
$this->getResourceName() => $this->getModel(),
], $this->getFormData('create'));
return view("{$this->viewPath}.create", $data);
}
/**
* Store a newly created resource in storage.
*
* @return Response
* @return \Illuminate\Http\Response
*/
public function store()
{
@@ -52,42 +65,15 @@ trait HasCrudActions
return $this->redirectTo($entity);
}
if (request()->wantsJson()) {
return response()->json(
[
'success' => true,
'message' => trans('admin::messages.resource_created', ['resource' => $this->getLabel()]),
], 200
);
}
return redirect()->route("{$this->getRoutePrefix()}.index")
->withSuccess(trans('admin::messages.resource_created', ['resource' => $this->getLabel()]));
->withSuccess(trans('admin::messages.resource_saved', ['resource' => $this->getLabel()]));
}
/**
* Show the form for creating a new resource.
*
* @return Response
*/
public function create()
{
$data = array_merge([
'tabs' => TabManager::get($this->getModel()->getTable()),
$this->getResourceName() => $this->getModel(),
], $this->getFormData('create'));
return view("{$this->viewPath}.create", $data);
}
/**
* Display the specified resource.
*
* @param int $id
*
* @return Response
* @return \Illuminate\Http\Response
*/
public function show($id)
{
@@ -100,13 +86,11 @@ trait HasCrudActions
return view("{$this->viewPath}.show")->with($this->getResourceName(), $entity);
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
*
* @return Response
* @return \Illuminate\Http\Response
*/
public function edit($id)
{
@@ -115,17 +99,14 @@ trait HasCrudActions
$this->getResourceName() => $this->getEntity($id),
], $this->getFormData('edit', $id));
return view("{$this->viewPath}.edit", $data);
}
/**
* Update the specified resource in storage.
*
* @param int $id
*
* @return Response
* @return \Illuminate\Http\Response
*/
public function update($id)
{
@@ -137,39 +118,24 @@ trait HasCrudActions
$this->getRequest('update')->all()
);
$entity->withoutEvents(function () use ($entity) {
$entity->touch();
});
$this->searchable($entity);
if (method_exists($this, 'redirectTo')) {
return $this->redirectTo($entity)
->withSuccess(trans('admin::messages.resource_updated', ['resource' => $this->getLabel()]));
}
if (request()->wantsJson()) {
return response()->json(
[
'success' => true,
'message' => trans('admin::messages.resource_updated', ['resource' => $this->getLabel()]),
], 200
);
->withSuccess(trans('admin::messages.resource_saved', ['resource' => $this->getLabel()]));
}
return redirect()->route("{$this->getRoutePrefix()}.index")
->withSuccess(trans('admin::messages.resource_updated', ['resource' => $this->getLabel()]));
->withSuccess(trans('admin::messages.resource_saved', ['resource' => $this->getLabel()]));
}
/**
* Destroy resources by given ids.
*
* @param string $ids
*
* @return void
*/
public function destroy(string $ids): void
public function destroy($ids)
{
$this->getModel()
->withoutGlobalScope('active')
@@ -177,153 +143,13 @@ trait HasCrudActions
->delete();
}
/**
* Prepare the table response for the resource.
*
* @param Request $request
*
* @return AdminTable
*/
public function table(Request $request): AdminTable
{
return $this->getModel()->table($request);
}
/**
* Get a new instance of the model.
*
* @return Model
*/
protected function getModel()
{
return new $this->model;
}
/**
* Disable search syncing for the entity.
*
* @return void
*/
protected function disableSearchSyncing(): void
{
if ($this->isSearchable()) {
$this->getModel()->disableSearchSyncing();
}
}
/**
* Determine if the entity is searchable.
*
* @return bool
*/
protected function isSearchable(): bool
{
return in_array(Searchable::class, class_uses_recursive($this->getModel()));
}
/**
* Get name of the resource.
*
* @return string
*/
protected function getResourceName(): string
{
return match (true) {
isset($this->resourceName) => $this->resourceName,
default => lcfirst(class_basename($this->model))
};
}
/**
* Get form data for the given action.
*
* @param string $action
* @param mixed ...$args
*
* @return array
*/
protected function getFormData(string $action, ...$args): array
{
return match (true) {
method_exists($this, 'formData') => $this->formData(...$args),
($action === 'create' && method_exists($this, 'createFormData')) => $this->createFormData(),
($action === 'edit' && method_exists($this, 'editFormData')) => $this->editFormData(...$args),
default => []
};
}
/**
* Get request object
*
* @param string $action
*
* @return Request
*/
protected function getRequest(string $action): Request
{
return match (true) {
!isset($this->validation) => request(),
isset($this->validation[$action]) => resolve($this->validation[$action]),
default => resolve($this->validation),
};
}
/**
* Make the given model instance searchable.
*
* @param $entity
*
* @return void
*/
protected function searchable($entity): void
{
if ($this->isSearchable()) {
$entity->searchable();
}
}
/**
* Get label of the resource.
*
* @return void
*/
protected function getLabel(): string
{
return trans($this->label);
}
/**
* Get route prefix of the resource.
*
* @return string
*/
protected function getRoutePrefix(): string
{
return match (true) {
isset($this->routePrefix) => $this->routePrefix,
default => "admin.{$this->getModel()->getTable()}"
};
}
/**
* Get an entity by the given id.
*
* @param int $id
*
* @return EloquentModel
* @return \Illuminate\Database\Eloquent\Model
*/
protected function getEntity(int|string $id): EloquentModel
protected function getEntity($id)
{
return $this->getModel()
->with($this->relations())
@@ -331,20 +157,142 @@ trait HasCrudActions
->findOrFail($id);
}
/**
* Get the relations that should be eager loaded.
*
* @return array
*/
private function relations(): array
private function relations()
{
return collect($this->with ?? [])->mapWithKeys(
function ($relation) {
return [$relation => function ($query) {
return $query->withoutGlobalScope('active');
}];
}
)->all();
return collect($this->with ?? [])->mapWithKeys(function ($relation) {
return [$relation => function ($query) {
return $query->withoutGlobalScope('active');
}];
})->all();
}
/**
* Get form data for the given action.
*
* @param string $action
* @param mixed ...$args
* @return array
*/
protected function getFormData($action, ...$args)
{
if (method_exists($this, 'formData')) {
return $this->formData(...$args);
}
if ($action === 'create' && method_exists($this, 'createFormData')) {
return $this->createFormData();
}
if ($action === 'edit' && method_exists($this, 'editFormData')) {
return $this->editFormData(...$args);
}
return [];
}
/**
* Get name of the resource.
*
* @return string
*/
protected function getResourceName()
{
if (isset($this->resourceName)) {
return $this->resourceName;
}
return lcfirst(class_basename($this->model));
}
/**
* Get label of the resource.
*
* @return void
*/
protected function getLabel()
{
return trans($this->label);
}
/**
* Get route prefix of the resource.
*
* @return string
*/
protected function getRoutePrefix()
{
if (isset($this->routePrefix)) {
return $this->routePrefix;
}
return "admin.{$this->getModel()->getTable()}";
}
/**
* Get a new instance of the model.
*
* @return \Modules\Support\Eloquent\Model
*/
protected function getModel()
{
return new $this->model;
}
/**
* Get request object
*
* @param string $action
* @return \Illuminate\Http\Request
*/
protected function getRequest($action)
{
if (! isset($this->validation)) {
return request();
}
if (isset($this->validation[$action])) {
return resolve($this->validation[$action]);
}
return resolve($this->validation);
}
/**
* Disable search syncing for the entity.
*
* @return void
*/
protected function disableSearchSyncing()
{
if ($this->isSearchable()) {
$this->getModel()->disableSearchSyncing();
}
}
/**
* Determine if the entity is searchable.
*
* @return bool
*/
protected function isSearchable()
{
return in_array(Searchable::class, class_uses_recursive($this->getModel()));
}
/**
* Make the given model instance searchable.
*
* @return void
*/
protected function searchable($entity)
{
if ($this->isSearchable($entity)) {
$entity->searchable();
}
}
}

View File

@@ -2,12 +2,6 @@
namespace Modules\Admin\Ui;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Yajra\DataTables\DataTables;
use Illuminate\Http\JsonResponse;
use Illuminate\Database\Eloquent\Builder;
use Yajra\DataTables\Exceptions\Exception;
use Illuminate\Contracts\Support\Responsable;
class AdminTable implements Responsable
@@ -17,30 +11,28 @@ class AdminTable implements Responsable
*
* @var array
*/
protected array $rawColumns = [];
protected $rawColumns = [];
/**
* Raw columns that will not be escaped.
*
* @var array
*/
protected array $defaultRawColumns = [
'checkbox', 'thumbnail', 'status', 'created', 'updated',
protected $defaultRawColumns = [
'checkbox', 'thumbnail', 'status', 'created',
];
/**
* Source of the table.
*
* @var Builder
* @var \Illuminate\Database\Eloquent\Builder
*/
protected $source;
/**
* Create a new table instance.
*
* @param Builder $source
*
* @param \Illuminate\Database\Eloquent\Builder $source
* @return void
*/
public function __construct($source = null)
@@ -48,38 +40,22 @@ class AdminTable implements Responsable
$this->source = $source;
}
/**
* Create an HTTP response that represents the object.
*
* @param Request $request
*
* @return Response
*/
public function toResponse($request)
{
return $this->make()->toJson();
}
/**
* Make table response for the resource.
*
* @param mixed $source
*
* @return JsonResponse
* @return \Illuminate\Http\JsonResponse
*/
public function make()
{
return $this->newTable();
}
/**
* Create a new datatable instance;
*
* @return DataTables
* @throws Exception
* @param mixed $source
* @return \Yajra\DataTables\DataTables
*/
public function newTable()
{
@@ -95,10 +71,18 @@ class AdminTable implements Responsable
->editColumn('created', function ($entity) {
return view('admin::partials.table.date')->with('date', $entity->created_at);
})
->editColumn('updated', function ($entity) {
return view('admin::partials.table.date')->with('date', $entity->updated_at);
})
->rawColumns(array_merge($this->defaultRawColumns, $this->rawColumns))
->removeColumn('translations');
}
/**
* Create an HTTP response that represents the object.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function toResponse($request)
{
return $this->make()->toJson();
}
}

View File

@@ -23,10 +23,9 @@ trait InputFields
{$attributes}"
. ($disabled ? 'disabled' : '')
. ($readonly ? 'readonly ' : '') .
'>';
'>';
}
protected function textareaField($name, $value, $class, $attributes, $options)
{
$readonly = array_pull($options, 'readonly', false);
@@ -39,22 +38,21 @@ trait InputFields
{$attributes}"
. ($disabled ? 'disabled' : '')
. ($readonly ? 'readonly ' : '') .
">{$value}</textarea>";
">{$value}</textarea>";
}
protected function checkboxField($name, $value, $class, $attributes, $options, $label)
{
$checked = array_pull($options, 'checked', false);
$disabled = array_get($options, 'disabled', false);
if (!is_null($value)) {
if (! is_null($value)) {
$checked = $value;
}
$html = '<div class="checkbox">';
if (!$disabled) {
if (! $disabled) {
$html .= "<input type='hidden' value='0' name='{$name}'>";
}
@@ -65,9 +63,9 @@ trait InputFields
id='{$name}'
{$attributes}
value='1'"
. ($checked ? 'checked ' : '')
. ($disabled ? 'disabled' : '') .
'>';
. ($checked ? 'checked ' : '')
. ($disabled ? 'disabled' : '') .
'>';
$html .= "<label for='{$name}'>{$label}</label>";
$html .= '</div>';
@@ -75,7 +73,6 @@ trait InputFields
return $html;
}
protected function selectField($name, $value, $class, $attributes, $options, $list)
{
$multiple = array_get($options, 'multiple', false);
@@ -89,7 +86,7 @@ trait InputFields
{$attributes}"
. ($disabled ? 'disabled' : '')
. ($readonly ? 'readonly ' : '') .
'>';
'>';
foreach ($list as $listValue => $listName) {
$listValue = e($listValue);
@@ -97,10 +94,10 @@ trait InputFields
if ($multiple && $value instanceof Collection) {
$selected = $value->where('id', $listValue)->isNotEmpty() ? 'selected' : '';
} else if ($multiple && is_array($value)) {
} elseif ($multiple && is_array($value)) {
$selected = in_array($listValue, $value) ? 'selected' : '';
} else {
$selected = (!is_null($value) && $value == $listValue) ? 'selected' : '';
$selected = (! is_null($value) && $value == $listValue) ? 'selected' : '';
}
$html .= "<option value='{$listValue}' {$selected}>{$listName}</option>";
@@ -111,7 +108,6 @@ trait InputFields
return $html;
}
protected function field($name, $title, $errors, $entity, $options, callable $fieldCallback, ...$args)
{
$value = $this->getValue($entity, $name);
@@ -143,7 +139,7 @@ trait InputFields
$html .= "<div class='col-md-{$fieldCol}'>";
$html .= call_user_func_array($fieldCallback, $params);
if ($help && !$errors->has($normalizedName)) {
if ($help && ! $errors->has($normalizedName)) {
$html .= "<span class='help-block'>{$help}</span>";
}
@@ -155,31 +151,15 @@ trait InputFields
return new HtmlString($html);
}
protected function generateHtmlAttributes($options = [])
private function normalizeTranslatableFieldName($name)
{
$this->unsetUnnecessaryAttributes($options);
$attributes = '';
foreach ($options as $attr => $value) {
$attributes .= "{$attr}='{$value}' ";
if (starts_with($name, 'translatable[')) {
return 'translatable.' . str_between($name, 'translatable[', ']');
}
return $attributes;
return $name;
}
protected function unsetUnnecessaryAttributes(&$options = [])
{
foreach ($this->unnecessaryAttributes as $attribute) {
if (array_key_exists($attribute, $options)) {
unset($options[$attribute]);
}
}
}
protected function label($name, $title, $labelCol = 3, $required = false)
{
$html = "<label for='{$name}' class='col-md-{$labelCol} control-label text-left'>{$title}";
@@ -191,7 +171,6 @@ trait InputFields
return $html .= '</label>';
}
private function getValue($entity, $name)
{
if (is_object($entity) && method_exists($entity, 'translate') && $entity->isTranslationAttribute($name)) {
@@ -222,13 +201,25 @@ trait InputFields
return old($normalizedName, $value);
}
private function normalizeTranslatableFieldName($name)
protected function generateHtmlAttributes($options = [])
{
if (starts_with($name, 'translatable[')) {
return 'translatable.' . str_between($name, 'translatable[', ']');
$this->unsetUnnecessaryAttributes($options);
$attributes = '';
foreach ($options as $attr => $value) {
$attributes .= "{$attr}='{$value}' ";
}
return $name;
return $attributes;
}
protected function unsetUnnecessaryAttributes(&$options = [])
{
foreach ($this->unnecessaryAttributes as $attribute) {
if (array_key_exists($attribute, $options)) {
unset($options[$attribute]);
}
}
}
}

View File

@@ -10,57 +10,41 @@ class Form
protected $unnecessaryAttributes = ['disabled', 'readonly', 'checked'];
public function text($name, $title, $errors, $entity = null, $options = [])
{
return $this->input($name, $title, $errors, $entity, array_merge($options, ['type' => 'text']));
}
public function input($name, $title, $errors, $entity = null, $options = [])
{
return $this->field($name, $title, $errors, $entity, $options, [$this, 'inputField']);
}
public function password($name, $title, $errors, $entity = null, $options = [])
{
return $this->input($name, $title, $errors, $entity, array_merge($options, ['type' => 'password']));
}
public function number($name, $title, $errors, $entity = null, $options = [])
{
return $this->input($name, $title, $errors, $entity, array_merge($options, ['type' => 'number']));
}
public function email($name, $title, $errors, $entity = null, $options = [])
{
return $this->input($name, $title, $errors, $entity, array_merge($options, ['type' => 'email']));
}
public function file($name, $title, $errors, $entity = null, $options = [])
{
return $this->input($name, $title, $errors, $entity, array_merge($options, ['type' => 'file']));
}
public function color($name, $title, $errors, $entity = null, $options = [])
{
return $this->input($name, $title, $errors, $entity, array_merge($options, ['type' => 'color']));
}
public function wysiwyg($name, $title, $errors, $entity = null, $options = [])
public function input($name, $title, $errors, $entity = null, $options = [])
{
$options['class'] = array_get($options, 'class', '') . ' wysiwyg';
return $this->textarea($name, $title, $errors, $entity, $options);
return $this->field($name, $title, $errors, $entity, $options, [$this, 'inputField']);
}
public function textarea($name, $title, $errors, $entity = null, $options = [])
{
$options = array_merge(['rows' => 10, 'cols' => 10], $options);
@@ -68,13 +52,18 @@ class Form
return $this->field($name, $title, $errors, $entity, $options, [$this, 'textareaField']);
}
public function wysiwyg($name, $title, $errors, $entity = null, $options = [])
{
$options['class'] = array_get($options, 'class', '') . ' wysiwyg';
return $this->textarea($name, $title, $errors, $entity, $options);
}
public function checkbox($name, $title, $label, $errors, $entity = null, $options = [])
{
return $this->field($name, $title, $errors, $entity, $options, [$this, 'checkboxField'], $label);
}
public function select($name, $title, $errors, $list = [], $entity = null, $options = [])
{
return $this->field($name, $title, $errors, $entity, $options, [$this, 'selectField'], $list);

View File

@@ -2,7 +2,6 @@
namespace Modules\Admin\Ui;
use Closure;
use Illuminate\Support\ViewErrorBag;
class Tab
@@ -12,21 +11,21 @@ class Tab
*
* @var bool
*/
public $active = false;
private $active = false;
/**
* Name of the tab.
*
* @var string
*/
public $name;
private $name;
/**
* Label of the tab.
*
* @var string
*/
public $label;
private $label;
/**
* Weight of the tab.
@@ -45,25 +44,10 @@ class Tab
/**
* View of the tab.
*
* @var string|Closure
* @var string|\Closure
*/
private $view;
/**
* Prepend to the View of the tab.
*
* @var string|Closure
*/
private $prependView;
/**
* Append to the view of the tab.
*
* @var string|Closure
*/
private $appendView;
/**
* Data for the tab view.
*
@@ -74,17 +58,15 @@ class Tab
/**
* Error message bag.
*
* @var ViewErrorBag
* @var \Illuminate\Support\ViewErrorBag
*/
private $errors;
/**
* Create a new Tab instance.
*
* @param string $name
* @param string $label
*
* @return void
*/
public function __construct($name, $label)
@@ -94,7 +76,6 @@ class Tab
$this->errors = request()->session()->get('errors') ?: new ViewErrorBag;
}
/**
* Set tab as active.
*
@@ -107,12 +88,10 @@ class Tab
return $this;
}
/**
* Set weight of tab.
*
* @param int $weight
*
* @return self
*/
public function weight($weight)
@@ -122,7 +101,6 @@ class Tab
return $this;
}
/**
* Get weight of the tab.
*
@@ -133,12 +111,10 @@ class Tab
return $this->weight;
}
/**
* Get nav of the tab.
*
* @param array $data
*
* @return string
*/
public function getNav()
@@ -148,116 +124,6 @@ class Tab
</li>";
}
/**
* Set fields of the tab.
*
* @param array|string $fields
*
* @return self
*/
public function fields($fields)
{
$this->fields = is_array($fields) ? $fields : func_get_args();
return $this;
}
/**
* Get fields of the tab.
*
* @return array
*/
public function getFields()
{
return $this->fields;
}
/**
* Set view of the tab.
*
* @param Closure|string $view
* @param array $data
*
* @return self
*/
public function view($view, $data = [], $prependView = null, $appendView = null)
{
$this->view = $view;
$this->data = $data;
$this->prependView = $prependView;
$this->appendView = $appendView;
return $this;
}
/**
* Get view of the tab.
*
* @param array $data
*
* @return string
*/
public function getView($data = [])
{
return $this->getPrependView($data) . $this->getMainView($data) . $this->getAppendView($data);
}
public function getPrependView($data = [])
{
$html = '';
if (!is_null($this->prependView)) {
if (is_callable($this->prependView)) {
$html .= call_user_func($this->prependView, $this->name, $this->label, $this->activeClass());
} else {
$html .= $this->prependView;
}
} else {
$html .= "<div class='tab-pane fade in {$this->activeClass()}' id='{$this->name}'>";
$html .= "<h4 class='tab-content-title'>{$this->label}</h4>";
}
return $html;
}
public function getMainView($data = [])
{
$html = '';
if (is_callable($this->view)) {
$html .= call_user_func($this->view, array_merge($this->data, $data));
} else {
$html .= view($this->view)->with(array_merge($this->data, $data))->render();
}
return $html;
}
public function getAppendView($data = [])
{
$html = '';
if (!is_null($this->appendView)) {
if (is_callable($this->appendView)) {
$html .= call_user_func($this->appendView, $this->name, $this->label, $this->activeClass());
} else {
$html .= $this->appendView;
}
} else {
$html .= '</div>';
}
return $html;
}
/**
* Return active class if tab is active.
*
@@ -268,7 +134,6 @@ class Tab
return $this->active ? 'active' : '';
}
/**
* Return error class if tab fields has any error.
*
@@ -278,4 +143,62 @@ class Tab
{
return $this->errors->hasAny($this->fields) ? 'has-error' : '';
}
/**
* Set fields of the tab.
*
* @param array|string $fields
* @return self
*/
public function fields($fields)
{
$this->fields = is_array($fields) ? $fields : func_get_args();
return $this;
}
/**
* Get fields of the tab.
*
* @return array
*/
public function getFields()
{
return $this->fields;
}
/**
* Set view of the tab.
*
* @param \Closure|string $view
* @param array $data
* @return self
*/
public function view($view, $data = [])
{
$this->view = $view;
$this->data = $data;
return $this;
}
/**
* Get view of the tab.
*
* @param array $data
* @return string
*/
public function getView($data = [])
{
$html = "<div class='tab-pane fade in {$this->activeClass()}' id='{$this->name}'>";
$html .= "<h3 class='tab-content-title'>{$this->label}</h3>";
if (is_callable($this->view)) {
$html .= call_user_func($this->view, array_merge($this->data, $data));
} else {
$html .= view($this->view)->with(array_merge($this->data, $data))->render();
}
return $html .= '</div>';
}
}

View File

@@ -18,13 +18,11 @@ class TabManager
*/
private $extends = [];
/**
* Register a new Tabs.
*
* @param string $name
* @param string $tabs
*
* @return void
*/
public function register($name, $tabs)
@@ -32,18 +30,28 @@ class TabManager
$this->tabs[$name] = $tabs;
}
/**
* Add a new Tabs extender.
*
* @param string $name
* @param string $extender
* @return void
*/
public function extend($name, $extender)
{
$this->extends[$name][] = $extender;
}
/**
* Get tabs for the given name.
*
* @param string $name
*
* @return Tabs
* @return \Modules\Admin\Ui\Tabs
*/
public function get($name)
{
if (!array_key_exists($name, $this->tabs)) {
return null;
if (! array_key_exists($name, $this->tabs)) {
return;
}
return tap(resolve($this->tabs[$name]), function (Tabs $tabs) use ($name) {
@@ -53,27 +61,11 @@ class TabManager
});
}
/**
* Add a new Tabs extender.
*
* @param string $name
* @param string $extender
*
* @return void
*/
public function extend($name, $extender)
{
$this->extends[$name][] = $extender;
}
/**
* Extend the given tabs using the given extenders.
*
* @param Tabs $tabs
* @param \Modules\Admin\Ui\Tabs $tabs
* @param array $extenders
*
* @return void
*/
private function extendTabs(Tabs $tabs, array $extenders)

View File

@@ -36,7 +36,6 @@ abstract class Tabs
*/
protected $buttonOffset = true;
/**
* Make new tabs with groups.
*
@@ -44,6 +43,23 @@ abstract class Tabs
*/
abstract public function make();
/**
* Set group name.
*
* @param string $name
* @param string $title
* @return self
*/
public function group($name, $title = null)
{
$this->group = $name;
if (! is_null($title)) {
$this->groups[$name]['title'] = $title;
}
return $this;
}
/**
* Set current group as active group.
@@ -57,24 +73,21 @@ abstract class Tabs
return $this;
}
/**
* Add new tab.
*
* @param Tab|null $tab
*
* @param \Modules\Admin\Ui\Tab|null $tab
* @return void
*/
public function add($tab)
{
if (!is_null($tab)) {
if (! is_null($tab)) {
$this->tabs[$this->group][] = $tab;
}
return $this;
}
/**
* Determine if tabs fields has any error.
*
@@ -85,70 +98,16 @@ abstract class Tabs
return $this->getErrors()->hasAny($this->getTabFields());
}
/**
* Generate navs for the tabs.
*
* @param array $data
*
* @return HtmlString
*/
public function navs($data = [])
{
return new HtmlString($this->getTabsData('nav', $data));
}
/**
* Render the tabs,
*
* @param array $data
*
* @return void
*/
public function render($data = [])
{
return view('admin::components.accordion', [
'tabs' => $this,
'name' => class_basename($this),
'groups' => $this->groups(),
'contents' => $this->contents($data),
'buttonOffset' => $this->buttonOffset,
]);
}
/**
* Set group name.
*
* @param string $name
* @param string $title
*
* @return self
*/
public function group($name, $title = null)
{
$this->group = $name;
if (!is_null($title)) {
$this->groups[$name]['title'] = $title;
}
return $this;
}
/**
* Get error message bag.
*
* @return ViewErrorBag
* @return \Illuminate\Support\ViewErrorBag
*/
protected function getErrors()
{
return request()->session()->get('errors') ?: new ViewErrorBag;
}
/**
* Get all tabs fields.
*
@@ -161,49 +120,33 @@ abstract class Tabs
}, []);
}
/**
* Get sorted tabs.
* Generate navs for the tabs.
*
* @return array
*/
protected function getSortedTabs()
{
return collect($this->tabs[$this->group])->sortBy(function (Tab $tab) {
return $tab->getWeight();
})->all();
}
/**
* Get tabs data for the given type.
*
* @param string $type
* @param array $data
*
* @return string
*
* @throws InvalidArgumentException
* @return \Illuminate\Support\HtmlString
*/
protected function getTabsData($type, $data = [])
public function navs($data = [])
{
if (!array_key_exists($this->group, $this->tabs)) {
throw new InvalidArgumentException("Group [$this->group] is not registered.");
}
$html = '';
foreach ($this->getSortedTabs() as $tab) {
$method = 'get' . ucfirst($type);
if (method_exists($tab, $method)) {
$html .= call_user_func_array([$tab, $method], [$data]);
}
}
return $html;
return new HtmlString($this->getTabsData('nav', $data));
}
/**
* Render the tabs,
*
* @param array $data
* @return void
*/
public function render($data = [])
{
return view('admin::components.accordion', [
'tabs' => $this,
'name' => class_basename($this),
'groups' => $this->groups(),
'contents' => $this->contents($data),
'buttonOffset' => $this->buttonOffset,
]);
}
/**
* Get all groups with it's options.
@@ -221,13 +164,11 @@ abstract class Tabs
return $groups;
}
/**
* Generate contents for the tabs.
*
* @param array $data
*
* @return HtmlString
* @return \Illuminate\Support\HtmlString
*/
protected function contents($data = [])
{
@@ -239,4 +180,44 @@ abstract class Tabs
return new HtmlString($contents);
}
/**
* Get tabs data for the given type.
*
* @param string $type
* @param array $data
* @return string
*
* @throws \InvalidArgumentException
*/
protected function getTabsData($type, $data = [])
{
if (! array_key_exists($this->group, $this->tabs)) {
throw new InvalidArgumentException("Group [$this->group] is not registered.");
}
$html = '';
foreach ($this->getSortedTabs() as $tab) {
$method = 'get' . ucfirst($type);
if (method_exists($tab, $method)) {
$html .= call_user_func_array([$tab, $method], [$data]);
}
}
return $html;
}
/**
* Get sorted tabs.
*
* @return array
*/
protected function getSortedTabs()
{
return collect($this->tabs[$this->group])->sortBy(function (Tab $tab) {
return $tab->getWeight();
})->all();
}
}

View File

@@ -1,20 +1,20 @@
{
"name": "admin-module",
"private": true,
"dependencies": {
"devDependencies": {
"bootstrap": "^3.3.7",
"chart.js": "^4.4.0",
"datatables.net": "^1.13.8",
"datatables.net-bs": "^1.13.8",
"flatpickr": "^4.6.13",
"chart.js": "^2.7.3",
"datatables.net": "^1.10.15",
"datatables.net-bs": "^1.10.15",
"flatpickr": "^3.0.6",
"font-awesome": "^4.7.0",
"jquery": "^3.2.1",
"jquery-slimscroll": "^1.3.8",
"lodash": "^4.17.21",
"mousetrap": "^1.6.5",
"lodash": "^4.17.4",
"mousetrap": "^1.6.1",
"nprogress": "^0.2.0",
"selectize": "^0.12.6",
"sortablejs": "^1.15.0",
"tinymce": "5.10.7",
"vue-toast-notification": "0.6.3"
"sortablejs": "https://github.com/RubaXa/Sortable",
"tinymce": "^5.2.2"
}
}

View File

@@ -1,35 +1,34 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateCartsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('carts', function (Blueprint $table) {
$table->string('id')->index();
$table->longText('data');
$table->timestamps();
$table->primary('id');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('carts');
}
}
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('api_tokens', function (Blueprint $table) {
$table->id();
$table->integer('user_id')->nullable()->unsigned();
$table->string('token');
$table->foreign('user_id')->references('id')->on('users')->onDelete('set null');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('api_tokens');
}
};

View File

@@ -0,0 +1,13 @@
<?php
namespace Modules\Api\Entities;
use Carbon\Carbon;
use Modules\Support\Eloquent\Model;
use Illuminate\Support\Str;
class ApiToken extends Model{
protected $fillable = ['user_id', 'token'];
public $timestamps = false;
}

View File

@@ -0,0 +1,46 @@
<?php
namespace Modules\Api\Http\Controllers;
use Carbon\Carbon;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Str;
use Modules\Api\Entities\ApiToken;
use Modules\Api\Http\Requests\ApiAuthRequest;
use Modules\User\Entities\User;
class ApiAuthController extends Controller {
public function login(ApiAuthRequest $request)
{
$user = User::findByEmail($request->email);
if (Auth::guard()->attempt($request->validated()) && $user->hasRoleName('admin')) {
$apiToken = ApiToken::create([
'token' => 'apit_' . Str::random() . md5(Carbon::now()->toString()),
'user_id' => $user->id,
]);
return response([
'token' => $apiToken,
]);
}
return response()->json([
'status' => 'Unauthorized.'
], 401);
}
public function logout() {
$token = request()->header('authorization');
ApiToken::query()
->where('token', $token)
->delete();
return response()->json([
'status' => 'success'
]);
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace Modules\Api\Http\Controllers;
use Illuminate\Support\Facades\Response;
use Illuminate\Routing\Controller;
use Modules\Product\Entities\Product;
class ExportProductController extends Controller {
public function __invoke()
{
$products = Product::with('categories')->get();
return response()->json($products);
}
}

View File

@@ -0,0 +1,171 @@
<?php
namespace Modules\Api\Http\Controllers;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Modules\Api\Http\Requests\ImportProductRequest;
use Modules\Brand\Entities\Brand;
use Modules\Brand\Entities\BrandTranslation;
use Modules\Category\Entities\Category;
use Modules\Category\Entities\CategoryTranslation;
use Modules\Media\Entities\File;
use Modules\Meta\Entities\MetaData;
use Modules\Meta\Entities\MetaDataTranslation;
use Modules\Product\Entities\Product;
class ImportProductController extends Controller {
public function __invoke(ImportProductRequest $request)
{
foreach ($request->data as $data) {
$file = $this->getFilesFromUrl($data['image']);
$product = Product::create($data + [
'brand_id' => $this->getBrandId($data['brand']),
]) ;
$this->createMeta($data['seo'] ?? [], Product::class, $product->id);
$product->files()->attach([$file->id => ['zone' => 'base_image']]);
$product->categories()->attach($this->getCategories($data['categories']));
}
return response()->json([
'status' => 'success'
]);
}
private function getCategories($productCategories) {
$categories = [];
foreach ($productCategories as $productCategory) {
$parendId = null;
foreach ($productCategory as $category) {
$parendId = $this->getCategoryFromName($category, $parendId);
}
$categories[] = $parendId;
}
return $categories;
}
private function getCategoryFromName($data, $parendId) {
$category = CategoryTranslation::where([
'name' => $data['name'],
'locale' => 'en'
])->first();
if (!empty($category)) {
return $category->category_id;
}
$category = new Category();
$category->name = $data['name'];
$category->is_active = true;
$category->is_searchable = true;
$category->parent_id = $parendId;
$category->save();
if (isset($data['image']['logo']) && !empty($data['image']['logo'])) {
$this->createLogo($data['image']['logo'], $category, Category::class);
}
if (isset($data['image']['banner']) && !empty($data['image']['banner'])) {
$this->createBanner($data['image']['banner'], $category, Category::class);
}
return $category->id;
}
private function getBrandId($data) {
// dd($data);
$brand = BrandTranslation::where([
'name' => $data['name'],
'locale' => 'en'
])->first();
if (!empty($brand)) {
return $brand->brand_id;
}
$brand = new Brand();
$brand->name = $data['name'];
$brand->is_active = true;
$brand->save();
$this->createMeta($data['seo'] ?? [], Brand::class, $brand->id);
if (isset($data['image']['logo']) && !empty($data['image']['logo'])) {
$this->createLogo($data['image']['logo'], $brand, Brand::class);
}
if (isset($data['image']['banner']) && !empty($data['image']['banner'])) {
$this->createBanner($data['image']['banner'], $brand, Brand::class);
}
return $brand->id;
}
private function getFilesFromUrl($imageUrl) {
$headers = get_headers($imageUrl, 1);
$fileContents = file_get_contents($imageUrl);
$path = 'media/' . time() . Str::random(6) . '.' . pathinfo($imageUrl, PATHINFO_EXTENSION);
Storage::put($path, $fileContents);
return File::create([
'user_id' => 1,
'disk' => config('filesystems.default'),
'filename' => pathinfo($imageUrl, PATHINFO_BASENAME),
'path' => $path,
'extension' => pathinfo($imageUrl, PATHINFO_EXTENSION) ?? '',
'mime' => $headers['Content-Type'],
'size' => strlen($fileContents),
]);
}
private function createMeta($seo, $class, $id) {
$metaData = new MetaDataTranslation();
$metaData->meta_title = isset($seo['title']) ? $seo['title'] : '';
$metaData->meta_description = isset($seo['description']) ? $seo['description'] : '';
$metaData->locale = 'en';
$metaData->meta_data_id = $this->getMetaId($class, $id);
$metaData->save();
}
private function getMetaId($class, $id) {
$meta = MetaData::where([
'entity_type' => $class,
'entity_id' => $id,
])->first();
if (!empty($meta)) {
return $meta->id;
}
$meta = new MetaData();
$meta->entity_type = $class;
$meta->entity_id = $id;
$meta->save();
return $meta->id;
}
private function createLogo($url, $class, $className) {
$file = $this->getFilesFromUrl($url);
$class->files()->attach([$file->id => ['zone' => 'logo']]);
}
private function createBanner($url, $class, $className) {
$file = $this->getFilesFromUrl($url);
$class->files()->attach([$file->id => ['zone' => 'banner']]);
}
}

View File

@@ -0,0 +1,54 @@
<?php
namespace Modules\Api\Http\Middleware;
use Carbon\Carbon;
use Closure;
use Illuminate\Http\Request;
use Modules\Api\Entities\ApiToken;
class ApiAuthorization
{
/**
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string $permission
* @param string $to
* @return \Illuminate\Http\Response
*/
public function handle(Request $request, Closure $next)
{
if ($this->hasAccess($request)) {
return $next($request);
}
return $this->handleUnauthorizedRequest($request);
}
/**
* @param \Illuminate\Http\Request $request
* @param string $permission
* @return \Illuminate\Http\Response
*/
private function handleUnauthorizedRequest(Request $request)
{
return response()->json([
'status' => 'Unauthorized.'
], 401);
}
private function hasAccess(Request $request)
{
$token = $request->header('authorization');
$hasToken = ApiToken::query()
->where('token', $token)
->exists();
if (!empty($token) && $hasToken) {
return true;
}
return false;
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace Modules\Api\Http\Requests;
use Illuminate\Validation\Rule;
use Modules\Product\Entities\Product;
use Modules\Core\Http\Requests\Request;
class ApiAuthRequest extends Request
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'email' => ['required', 'email', Rule::exists('users', 'email')],
'password' => 'required',
];
}
}

View File

@@ -0,0 +1,69 @@
<?php
namespace Modules\Api\Http\Requests;
use Illuminate\Validation\Rule;
use Modules\Product\Entities\Product;
use Modules\Core\Http\Requests\Request;
class ImportProductRequest extends Request
{
/**
* Available attributes.
*
* @var string
*/
protected $availableAttributes = 'product::attributes';
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
// 'slug' => $this->getSlugRules(),
'data' => 'required|array|min:1',
'data.*.slug' => 'sometimes',
'data.*.name' => 'required',
'data.*.description' => 'required',
'data.*.brand' => 'required|array|min:1',
'data.*.brand.name' => 'required|string',
'data.*.brand.seo' => 'sometimes',
'data.*.brand.image' => 'sometimes',
'data.*.tax_class_id' => ['nullable', Rule::exists('tax_classes', 'id')],
'data.*.is_virtual' => 'required|boolean',
'data.*.is_active' => 'required|boolean',
'data.*.price' => 'required|numeric|min:0|max:99999999999999',
'data.*.special_price' => 'nullable|numeric|min:0|max:99999999999999',
'data.*.special_price_type' => ['nullable', Rule::in(['fixed', 'percent'])],
'data.*.special_price_start' => 'nullable|date',
'data.*.special_price_end' => 'nullable|date',
'data.*.manage_stock' => 'required|boolean',
'data.*.qty' => 'required_if:manage_stock,1|nullable|numeric',
'data.*.in_stock' => 'required|boolean',
'data.*.new_from' => 'nullable|date',
'data.*.new_to' => 'nullable|date',
'data.*.categories' => 'required|array|min:1',
'data.*.categories.*.*.name' => 'required|string',
'data.*.categories.*.*.seo' => 'sometimes',
'data.*.image' => 'required|string',
];
}
// private function getSlugRules()
// {
// $rules = $this->route()->getName() === 'admin.products.update' ? ['required'] : ['sometimes'];
// $slug = Product::withoutGlobalScope('active')
// ->where('id', $this->id)
// ->value('slug');
// $rules[] = Rule::unique('products', 'slug')->ignore($slug, 'slug');
// return $rules;
// }
}

View File

@@ -0,0 +1,18 @@
<?php
namespace Modules\Api\Providers;
use Illuminate\Support\ServiceProvider;
class ApiProvider extends ServiceProvider
{
public function register()
{
}
public function boot()
{
$this->loadRoutesFrom(__DIR__ . '/../Routes/admin.php');
$this->loadRoutesFrom(__DIR__ . '/../Routes/public.php');
}
}

View File

@@ -0,0 +1,9 @@
<?php
use Illuminate\Support\Facades\Route;
use Modules\Api\Http\Controllers\ApiAuthController;
Route::prefix('api')->group(function () {
Route::post('/login', [ApiAuthController::class, 'login'])->name('api.auth.login');
Route::post('/logout', [ApiAuthController::class, 'logout'])->middleware('api_auth')->name('api.auth.logout');
});

View File

@@ -0,0 +1,14 @@
<?php
use Illuminate\Support\Facades\Route;
use Modules\Api\Http\Controllers\ExportProductController;
use Modules\Api\Http\Controllers\ImportProductController;
Route::group(['prefix' => 'export', 'middleware' => 'api_auth'], function () {
Route::get('products', ExportProductController::class)->name('api.export.products');
});
Route::group(['prefix' => 'import', 'middleware' => 'api_auth'], function () {
Route::post('products', ImportProductController::class)->name('api.import.products');
});

View File

@@ -0,0 +1,3 @@
{
}

View File

@@ -14,7 +14,6 @@ class AttributeSetTabs extends Tabs
->add($this->general());
}
private function general()
{
return tap(new Tab('general', trans('attribute::attribute_sets.tabs.general')), function (Tab $tab) {

View File

@@ -3,14 +3,13 @@
namespace Modules\Attribute\Admin;
use Modules\Admin\Ui\AdminTable;
use Illuminate\Http\JsonResponse;
class AttributeTable extends AdminTable
{
/**
* Make table response for the resource.
*
* @return JsonResponse
* @return \Illuminate\Http\JsonResponse
*/
public function make()
{

View File

@@ -17,7 +17,6 @@ class AttributeTabs extends Tabs
->add($this->values());
}
private function general()
{
return tap(new Tab('general', trans('attribute::admin.tabs.general')), function (Tab $tab) {
@@ -31,14 +30,12 @@ class AttributeTabs extends Tabs
});
}
private function getAttributeSets()
{
return AttributeSet::all()->sortBy('name')->pluck('name', 'id')
->prepend(trans('admin::admin.form.please_select'), '');
}
private function values()
{
return tap(new Tab('values', trans('attribute::admin.tabs.values')), function (Tab $tab) {

View File

@@ -19,7 +19,6 @@ class CreateAttributeSetsTable extends Migration
});
}
/**
* Reverse the migrations.
*

View File

@@ -24,7 +24,6 @@ class CreateAttributeSetTranslationsTable extends Migration
});
}
/**
* Reverse the migrations.
*

View File

@@ -23,7 +23,6 @@ class CreateAttributesTable extends Migration
});
}
/**
* Reverse the migrations.
*

View File

@@ -24,7 +24,6 @@ class CreateAttributeTranslationsTable extends Migration
});
}
/**
* Reverse the migrations.
*

View File

@@ -23,7 +23,6 @@ class CreateProductAttributesTable extends Migration
});
}
/**
* Reverse the migrations.
*

View File

@@ -23,7 +23,6 @@ class CreateAttributeValuesTable extends Migration
});
}
/**
* Reverse the migrations.
*

View File

@@ -24,7 +24,6 @@ class CreateAttributeValueTranslationsTable extends Migration
});
}
/**
* Reverse the migrations.
*

View File

@@ -23,7 +23,6 @@ class CreateProductAttributeValuesTable extends Migration
});
}
/**
* Reverse the migrations.
*

View File

@@ -23,7 +23,6 @@ class CreateAttributeCategoriesTable extends Migration
});
}
/**
* Reverse the migrations.
*

Some files were not shown because too many files have changed in this diff Show More