diff --git a/.env.example b/.env.example index 753a98e..22ec876 100644 --- a/.env.example +++ b/.env.example @@ -76,3 +76,7 @@ VITE_APP_NAME="${APP_NAME}" XLANG_ACTIVE=false XLANG_MAIN=en XLANG_API_URL="http://5.255.98.77:3001" + +CURRENCY_SYMBOL="$" +CURRENCY_FACTOR=1 +CURRENCY_CODE=USD diff --git a/README.md b/README.md index 67d32e9..db2db3b 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,21 @@ php artisan serv ``` > [!TIP] -> Default admin email is : `admin@example.com` and default password is: `password` +> Default admin email is : `developer@example.com` (developer) or `admin@example.com` (admin) and default password is: `password` + + +## image seeding + +- Download & prepare images +```bash +php artisan seeding:prepare + ``` +- Seeding image for models: [Group, Category, Post, Products] +```bash +pa seeding:image Product digital +``` +> First parameter is Model, Second is image seeder directory available [bag, clothe, digital, sport, posts, makeup] +> You can create your directory and put your image into new directory then use image seeder ## Requirement diff --git a/app/Console/Commands/SeedingImage.php b/app/Console/Commands/SeedingImage.php index 1c80fba..6371f37 100644 --- a/app/Console/Commands/SeedingImage.php +++ b/app/Console/Commands/SeedingImage.php @@ -45,7 +45,7 @@ class SeedingImage extends Command if ($item->media()->count() == 0){ $name = $images[0]->getFilename(); $tempName = explode('.', $name); - $item->name = ucfirst(str_replace('-', ' ', $tempName[0])) . ' model ' . $item->id; + $item->name = readable($tempName[0]) . ' model ' . $item->id; } $item->addMedia($images[0]->getRealPath()) ->preservingOriginal() //middle method diff --git a/app/Helpers/Helper.php b/app/Helpers/Helper.php index 41db4ef..dc1cd37 100644 --- a/app/Helpers/Helper.php +++ b/app/Helpers/Helper.php @@ -870,7 +870,7 @@ function success($data = null, $message = null, $meta = [], $og = [], $twitter = $defaultTwitter = [ 'card' => 'summary_large_image', - 'site' => '@yourTwitterHandle', + 'site' => getSetting('social.twitter'), 'title' => null, 'description' => null, 'image' => null, @@ -904,6 +904,11 @@ function errors($errors, $status = 422, $message = null, $data = null) ], $status); } +/** + * make human readable + * @param $text + * @return string + */ function readable($text) { return ucfirst(trim(str_replace(['-','_'],' ',$text))); diff --git a/app/Http/Controllers/Admin/GfxController.php b/app/Http/Controllers/Admin/GfxController.php index 4460d8c..956f8ce 100644 --- a/app/Http/Controllers/Admin/GfxController.php +++ b/app/Http/Controllers/Admin/GfxController.php @@ -6,19 +6,24 @@ use App\Http\Controllers\Controller; use App\Models\Area; use App\Models\Gfx; use Illuminate\Http\Request; +use mysql_xdevapi\Exception; class GfxController extends Controller { // public function index() { - $prviews = Area::whereNotNull('preview') + $previews = Area::whereNotNull('preview') ->pluck('preview', 'name')->toArray(); - array_walk($prviews, function ($value, $key) use (&$prviews) { - $prviews[$key] = route($value); + array_walk($previews, function ($value, $key) use (&$previews) { + try { + $previews[$key] = route($value); + }catch (Exception $exception){ + + } }); - return view('admin.commons.gfx',compact('prviews')); + return view('admin.commons.gfx',compact('previews')); } public function update(Request $request) diff --git a/app/Http/Controllers/Admin/ProductController.php b/app/Http/Controllers/Admin/ProductController.php index 94bc54b..6ee71e0 100644 --- a/app/Http/Controllers/Admin/ProductController.php +++ b/app/Http/Controllers/Admin/ProductController.php @@ -118,8 +118,10 @@ class ProductController extends XController } $product->quantities()->whereIn('id',$toRemoveQ)->delete(); - $product->stock_quantity = $product->quantities()->sum('count'); - $product->price = $product->quantities()->min('price'); + if ($product->quantities()->count() > 0){ + $product->stock_quantity = $product->quantities()->sum('count'); + $product->price = $product->quantities()->min('price'); + } $product->save(); } diff --git a/app/Http/Controllers/Api/Web/HomeController.php b/app/Http/Controllers/Api/HomeController.php similarity index 90% rename from app/Http/Controllers/Api/Web/HomeController.php rename to app/Http/Controllers/Api/HomeController.php index 1bfdada..cc3da56 100644 --- a/app/Http/Controllers/Api/Web/HomeController.php +++ b/app/Http/Controllers/Api/HomeController.php @@ -1,20 +1,17 @@ download($file); } } + + + public function ProductFavToggle(Product $product) + { + if (!auth('customer')->check()) { + return errors([ + __("You need to login first"), + ], 403, __("You need to login first")); + } + + if (auth('customer')->user()->favorites()->where('product_id', $product->id)->count() == 0) { + auth('customer')->user()->favorites()->attach($product->id); + $message = __('Product added to favorites'); + $fav = '1'; + } else { + auth('customer')->user()->favorites()->detach($product->id); + $message = __('Product removed from favorites'); + $fav = '0'; + } + + if (\request()->ajax()) { + return success($fav, $message); + } else { + return redirect()->back()->with(['message' => $message]); + } + } } diff --git a/app/Http/Controllers/XController.php b/app/Http/Controllers/XController.php index 238cce6..52e9824 100644 --- a/app/Http/Controllers/XController.php +++ b/app/Http/Controllers/XController.php @@ -38,7 +38,7 @@ abstract class XController extends Controller ]; - public function save($user, $request) + public function save($item, $request) { } diff --git a/app/Models/Customer.php b/app/Models/Customer.php index ad3bf9d..f4fe45f 100644 --- a/app/Models/Customer.php +++ b/app/Models/Customer.php @@ -6,10 +6,11 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Foundation\Auth\User as Authenticatable; +use Laravel\Sanctum\HasApiTokens; class Customer extends Authenticatable { - use HasFactory, SoftDeletes; + use HasFactory, SoftDeletes,HasApiTokens ; protected $hidden = [ 'password', 'remember_token', @@ -44,4 +45,8 @@ class Customer extends Authenticatable } + public function favorites(){ + return $this->belongsToMany(Product::class,'customer_product'); + } + } diff --git a/app/Models/Product.php b/app/Models/Product.php index b1539f9..aa976f4 100644 --- a/app/Models/Product.php +++ b/app/Models/Product.php @@ -58,7 +58,7 @@ class Product extends Model implements HasMedia { $optimize = getSetting('optimize'); - if ($optimize == false){ + if ($optimize == false) { $optimize = 'webp'; } $ti = imageSizeConvertValidate('product_image'); @@ -141,7 +141,8 @@ class Product extends Model implements HasMedia public function activeDiscounts() { - return $this->hasMany(Discount::class, 'product_id', 'id')->where(function ($query) { + return $this->hasMany(Discount::class, 'product_id', 'id') + ->where(function ($query) { $query->where('expire', '>=', date('Y-m-d')) ->orWhereNull('expire'); }); @@ -157,14 +158,6 @@ class Product extends Model implements HasMedia return $this->discounts()->where('expire', '>', date('Y-m-d'))->count() > 0; } - public function isFav() - { - if (auth('customer')->check()) { - return \auth('customer')->user()->products()->where('product_id', $this->id)->exists(); - } else { - return false; - } - } public function imgUrl() { @@ -183,6 +176,7 @@ class Product extends Model implements HasMedia return asset('assets/upload/logo.svg'); } } + public function originalOptimizedImageUrl() { if ($this->getMedia()->count() > 0) { @@ -245,9 +239,9 @@ class Product extends Model implements HasMedia } else { $result[$key]['human_value'] = ''; foreach ($value as $k => $v) { - $result[$key]['human_value'] = $result[$key]['data']->datas[$v].', '; + $result[$key]['human_value'] = $result[$key]['data']->datas[$v] . ', '; } - $result[$key]['human_value'] = trim($result[$key]['human_value'],' ,'); + $result[$key]['human_value'] = trim($result[$key]['human_value'], ' ,'); } break; default: @@ -278,7 +272,54 @@ class Product extends Model implements HasMedia } - public function getPrice(){ - return number_format($this->price); + public function getPrice() + { + $price = 0; + if ($this->quantities()->count() == 0) { + $price = $this->price; + } else { + $price = $this->quantities()->min('price'); + } + + if ($this->hasDiscount()) { + $d = $this->activeDiscounts()->first(); + if ($d->type == 'PRICE') { + $price -= $d->amount; + }else{ + $price = ( (100 - $d->amount) * $price ) / 100; + } + } + + if ($price == 0 || $price == '' || $price == null) { + return __("Call us!"); + } + + return number_format($price) . ' ' . config('app.currency.symbol'); + } + public function oldPrice() + { + $price = 0; + if ($this->quantities()->count() == 0) { + $price = $this->price; + } else { + $price = $this->quantities()->min('price'); + } + + if ($price == 0 || $price == '' || $price == null) { + return __("Call us!"); + } + + return number_format($price) . ' ' . config('app.currency.symbol'); + } + + public function isFav(){ + if (!auth('customer')->check()) { + return -1; + } + if (\auth('customer')->user()->products()->where('product_id', $this->id)->exists()) { + return 1; + }else{ + return 0; + } } } diff --git a/app/Models/User.php b/app/Models/User.php index c5585c2..854c514 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -7,11 +7,12 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; +use Laravel\Sanctum\HasApiTokens; use Spatie\Permission\Traits\HasRoles; class User extends Authenticatable { - use HasFactory, Notifiable, HasRoles, SoftDeletes; + use HasFactory, Notifiable, HasRoles, SoftDeletes, HasApiTokens; static $roles = ['DEVELOPER', 'ADMIN', 'USER', 'SUSPENDED']; @@ -58,6 +59,7 @@ class User extends Authenticatable { return $this->hasMany(Post::class); } + public function postsPercent() { if (Post::count() == 0) { @@ -65,10 +67,12 @@ class User extends Authenticatable } return $this->posts()->count() * 100 / Post::count(); } + public function products() { return $this->hasMany(Product::class); } + public function productsPercent() { @@ -77,10 +81,12 @@ class User extends Authenticatable } return $this->products()->count() * 100 / Product::count(); } + public function tickets() { return $this->hasMany(Ticket::class); } + public function ticketsPercent() { if (Ticket::count() == 0) { @@ -88,9 +94,10 @@ class User extends Authenticatable } return $this->tickets()->count() * 100 / Ticket::count(); } + public function comments() { - return $this->morphMany(Comment::class,'commentator'); + return $this->morphMany(Comment::class, 'commentator'); } public function commentsPercent() @@ -106,36 +113,42 @@ class User extends Authenticatable return $this->hasMany(AdminLog::class, 'user_id', 'id'); } - public function accesses(){ + public function accesses() + { return $this->hasMany(Access::class); } - public function hasAnyAccess($name){ - if ($this->hasRole('SUSPENDED')){ - return false; + + public function hasAnyAccess($name) + { + if ($this->hasRole('SUSPENDED')) { + return false; } if ($this->hasRole('admin') || $this->hasRole('developer')) { - return true; + return true; } - return $this->accesses()->where('route','LIKE','%.'.$name.'.%')->count() > 0; + return $this->accesses()->where('route', 'LIKE', '%.' . $name . '.%')->count() > 0; } - public function hasAnyAccesses($array){ - if ($this->hasRole('SUSPENDED')){ - return false; + + public function hasAnyAccesses($array) + { + if ($this->hasRole('SUSPENDED')) { + return false; } if ($this->hasRole('admin') || $this->hasRole('developer')) { - return true; + return true; + } + foreach ($array as $access) { + if ($this->hasAnyAccess($access)) { + return true; + } } - foreach ($array as $access){ - if ($this->hasAnyAccess($access)){ - return true; - } - } } - public function hasAccess($route){ - if ($this->hasRole('SUSPENDED')){ - return false; + public function hasAccess($route) + { + if ($this->hasRole('SUSPENDED')) { + return false; } - return $this->accesses()->where('route',$route)->count() > 0; + return $this->accesses()->where('route', $route)->count() > 0; } } diff --git a/config/app.php b/config/app.php index f125a56..7f02124 100644 --- a/config/app.php +++ b/config/app.php @@ -137,6 +137,19 @@ return [ 'prefix' => env('PANEL_PREFIX','dashboard'), 'page_count' => env('PANEL_PAGE_COUNT',30), ], + + /* + |-------------------------------------------------------------------------- + | currency + |-------------------------------------------------------------------------- + | default currency data + */ + + 'currency' => [ + 'symbol' => env('CURRENCY_SYMBOL','$'), + 'factor' => env('CURRENCY_FACTOR',1), + 'code'=> env('CURRENCY_CODE','USD'), + ], /* |-------------------------------------------------------------------------- | Media diff --git a/resources/js/client-custom/assetsNode.js b/resources/js/client-custom/assetsNode.js index abd76b5..a6231a4 100644 --- a/resources/js/client-custom/assetsNode.js +++ b/resources/js/client-custom/assetsNode.js @@ -1 +1,20 @@ import 'bootstrap'; +import { createApp } from 'vue'; +import ToastPlugin from 'vue-toast-notification'; +import {useToast} from 'vue-toast-notification'; +import store from "../client-vuex/client-store.js"; +import chartjs from 'chart.js/auto'; + + +const app = createApp({}); +const $toast = useToast({ + duration: 10000, +}); + +app.use(ToastPlugin); +app.use(store); +app.mount('#app'); + +window.app = app; +window.$toast = $toast; +window.store = store; diff --git a/resources/js/client-custom/customerActions.js b/resources/js/client-custom/customerActions.js new file mode 100644 index 0000000..6d4beb7 --- /dev/null +++ b/resources/js/client-custom/customerActions.js @@ -0,0 +1,12 @@ +window.addEventListener('load', function () { + const favUrl = document.querySelector('#api-fav-toggle').value; + document.querySelectorAll('.fav-btn')?.forEach(function (el) { + el.addEventListener('click', async function () { + let resp = await axios.get(favUrl+this.getAttribute('data-slug')); + if (resp.data.success){ + this.setAttribute('data-is-fav',resp.data.data); + window.$toast.success(resp.data.message); + } + }); + }); +}); diff --git a/resources/js/client-vuex/client-store.js b/resources/js/client-vuex/client-store.js new file mode 100644 index 0000000..2614fda --- /dev/null +++ b/resources/js/client-vuex/client-store.js @@ -0,0 +1,18 @@ +import Vuex from 'vuex'; + + +export default new Vuex.Store({ + state: { + test: '', + }, + mutations: { + UPDATE_TEST(state, payload) { + state.test = payload; + }, + }, + actions:{ + updateTest(context,val){ + context.commit('UPDATE_TEST',val); + }, + } +}); diff --git a/resources/sass/client-custom/_assetsNode.scss b/resources/sass/client-custom/_assetsNode.scss index fd849bb..b2ea7aa 100644 --- a/resources/sass/client-custom/_assetsNode.scss +++ b/resources/sass/client-custom/_assetsNode.scss @@ -1,2 +1,3 @@ @import "remixicon/fonts/remixicon.css"; @import "tiny-slider/dist/tiny-slider.css"; +@import "vue-toast-notification/dist/theme-bootstrap.css"; diff --git a/resources/views/admin/commons/gfx.blade.php b/resources/views/admin/commons/gfx.blade.php index 5f642f1..a2b8efd 100644 --- a/resources/views/admin/commons/gfx.blade.php +++ b/resources/views/admin/commons/gfx.blade.php @@ -8,7 +8,7 @@ @include('components.err')