added vue & vuex to client js

added vue-toast to client
added image seeder guide to readme
added fav toggle to client
master
A1Gard 2 months ago
parent ff0395f4a4
commit d7292f7457

@ -76,3 +76,7 @@ VITE_APP_NAME="${APP_NAME}"
XLANG_ACTIVE=false XLANG_ACTIVE=false
XLANG_MAIN=en XLANG_MAIN=en
XLANG_API_URL="http://5.255.98.77:3001" XLANG_API_URL="http://5.255.98.77:3001"
CURRENCY_SYMBOL="$"
CURRENCY_FACTOR=1
CURRENCY_CODE=USD

@ -39,7 +39,21 @@ php artisan serv
``` ```
> [!TIP] > [!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 ## Requirement

@ -45,7 +45,7 @@ class SeedingImage extends Command
if ($item->media()->count() == 0){ if ($item->media()->count() == 0){
$name = $images[0]->getFilename(); $name = $images[0]->getFilename();
$tempName = explode('.', $name); $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()) $item->addMedia($images[0]->getRealPath())
->preservingOriginal() //middle method ->preservingOriginal() //middle method

@ -870,7 +870,7 @@ function success($data = null, $message = null, $meta = [], $og = [], $twitter =
$defaultTwitter = [ $defaultTwitter = [
'card' => 'summary_large_image', 'card' => 'summary_large_image',
'site' => '@yourTwitterHandle', 'site' => getSetting('social.twitter'),
'title' => null, 'title' => null,
'description' => null, 'description' => null,
'image' => null, 'image' => null,
@ -904,6 +904,11 @@ function errors($errors, $status = 422, $message = null, $data = null)
], $status); ], $status);
} }
/**
* make human readable
* @param $text
* @return string
*/
function readable($text) function readable($text)
{ {
return ucfirst(trim(str_replace(['-','_'],' ',$text))); return ucfirst(trim(str_replace(['-','_'],' ',$text)));

@ -6,19 +6,24 @@ use App\Http\Controllers\Controller;
use App\Models\Area; use App\Models\Area;
use App\Models\Gfx; use App\Models\Gfx;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use mysql_xdevapi\Exception;
class GfxController extends Controller class GfxController extends Controller
{ {
// //
public function index() public function index()
{ {
$prviews = Area::whereNotNull('preview') $previews = Area::whereNotNull('preview')
->pluck('preview', 'name')->toArray(); ->pluck('preview', 'name')->toArray();
array_walk($prviews, function ($value, $key) use (&$prviews) { array_walk($previews, function ($value, $key) use (&$previews) {
$prviews[$key] = route($value); 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) public function update(Request $request)

@ -118,8 +118,10 @@ class ProductController extends XController
} }
$product->quantities()->whereIn('id',$toRemoveQ)->delete(); $product->quantities()->whereIn('id',$toRemoveQ)->delete();
$product->stock_quantity = $product->quantities()->sum('count'); if ($product->quantities()->count() > 0){
$product->price = $product->quantities()->min('price'); $product->stock_quantity = $product->quantities()->sum('count');
$product->price = $product->quantities()->min('price');
}
$product->save(); $product->save();
} }

@ -1,20 +1,17 @@
<?php <?php
namespace App\Http\Controllers\Api\Web; namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Resources\AdvResource; use App\Http\Resources\AdvResource;
use App\Http\Resources\CategoryResource; use App\Http\Resources\CategoryResource;
use App\Http\Resources\PostResource; use App\Http\Resources\PostResource;
use App\Http\Resources\ProductResource;
use App\Http\Resources\SliderResource; use App\Http\Resources\SliderResource;
use App\Models\Adv; use App\Models\Adv;
use App\Models\Category; use App\Models\Category;
use App\Models\Menu; use App\Models\Menu;
use App\Models\Post; use App\Models\Post;
use App\Models\Product;
use App\Models\Slider; use App\Models\Slider;
use Illuminate\Http\Request;
class HomeController extends Controller class HomeController extends Controller
{ {

@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Controllers\Api\Web; namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Resources\ProductResource; use App\Http\Resources\ProductResource;

@ -148,4 +148,30 @@ class ClientController extends Controller
return response()->download($file); return response()->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]);
}
}
} }

@ -38,7 +38,7 @@ abstract class XController extends Controller
]; ];
public function save($user, $request) public function save($item, $request)
{ {
} }

@ -6,10 +6,11 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Sanctum\HasApiTokens;
class Customer extends Authenticatable class Customer extends Authenticatable
{ {
use HasFactory, SoftDeletes; use HasFactory, SoftDeletes,HasApiTokens ;
protected $hidden = [ protected $hidden = [
'password', 'remember_token', 'password', 'remember_token',
@ -44,4 +45,8 @@ class Customer extends Authenticatable
} }
public function favorites(){
return $this->belongsToMany(Product::class,'customer_product');
}
} }

@ -58,7 +58,7 @@ class Product extends Model implements HasMedia
{ {
$optimize = getSetting('optimize'); $optimize = getSetting('optimize');
if ($optimize == false){ if ($optimize == false) {
$optimize = 'webp'; $optimize = 'webp';
} }
$ti = imageSizeConvertValidate('product_image'); $ti = imageSizeConvertValidate('product_image');
@ -141,7 +141,8 @@ class Product extends Model implements HasMedia
public function activeDiscounts() 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')) $query->where('expire', '>=', date('Y-m-d'))
->orWhereNull('expire'); ->orWhereNull('expire');
}); });
@ -157,14 +158,6 @@ class Product extends Model implements HasMedia
return $this->discounts()->where('expire', '>', date('Y-m-d'))->count() > 0; 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() public function imgUrl()
{ {
@ -183,6 +176,7 @@ class Product extends Model implements HasMedia
return asset('assets/upload/logo.svg'); return asset('assets/upload/logo.svg');
} }
} }
public function originalOptimizedImageUrl() public function originalOptimizedImageUrl()
{ {
if ($this->getMedia()->count() > 0) { if ($this->getMedia()->count() > 0) {
@ -245,9 +239,9 @@ class Product extends Model implements HasMedia
} else { } else {
$result[$key]['human_value'] = ''; $result[$key]['human_value'] = '';
foreach ($value as $k => $v) { 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; break;
default: default:
@ -278,7 +272,54 @@ class Product extends Model implements HasMedia
} }
public function getPrice(){ public function getPrice()
return number_format($this->price); {
$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;
}
} }
} }

@ -7,11 +7,12 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
use Spatie\Permission\Traits\HasRoles; use Spatie\Permission\Traits\HasRoles;
class User extends Authenticatable class User extends Authenticatable
{ {
use HasFactory, Notifiable, HasRoles, SoftDeletes; use HasFactory, Notifiable, HasRoles, SoftDeletes, HasApiTokens;
static $roles = ['DEVELOPER', 'ADMIN', 'USER', 'SUSPENDED']; static $roles = ['DEVELOPER', 'ADMIN', 'USER', 'SUSPENDED'];
@ -58,6 +59,7 @@ class User extends Authenticatable
{ {
return $this->hasMany(Post::class); return $this->hasMany(Post::class);
} }
public function postsPercent() public function postsPercent()
{ {
if (Post::count() == 0) { if (Post::count() == 0) {
@ -65,10 +67,12 @@ class User extends Authenticatable
} }
return $this->posts()->count() * 100 / Post::count(); return $this->posts()->count() * 100 / Post::count();
} }
public function products() public function products()
{ {
return $this->hasMany(Product::class); return $this->hasMany(Product::class);
} }
public function productsPercent() public function productsPercent()
{ {
@ -77,10 +81,12 @@ class User extends Authenticatable
} }
return $this->products()->count() * 100 / Product::count(); return $this->products()->count() * 100 / Product::count();
} }
public function tickets() public function tickets()
{ {
return $this->hasMany(Ticket::class); return $this->hasMany(Ticket::class);
} }
public function ticketsPercent() public function ticketsPercent()
{ {
if (Ticket::count() == 0) { if (Ticket::count() == 0) {
@ -88,9 +94,10 @@ class User extends Authenticatable
} }
return $this->tickets()->count() * 100 / Ticket::count(); return $this->tickets()->count() * 100 / Ticket::count();
} }
public function comments() public function comments()
{ {
return $this->morphMany(Comment::class,'commentator'); return $this->morphMany(Comment::class, 'commentator');
} }
public function commentsPercent() public function commentsPercent()
@ -106,36 +113,42 @@ class User extends Authenticatable
return $this->hasMany(AdminLog::class, 'user_id', 'id'); return $this->hasMany(AdminLog::class, 'user_id', 'id');
} }
public function accesses(){ public function accesses()
{
return $this->hasMany(Access::class); return $this->hasMany(Access::class);
} }
public function hasAnyAccess($name){
if ($this->hasRole('SUSPENDED')){ public function hasAnyAccess($name)
return false; {
if ($this->hasRole('SUSPENDED')) {
return false;
} }
if ($this->hasRole('admin') || $this->hasRole('developer')) { 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')){ public function hasAnyAccesses($array)
return false; {
if ($this->hasRole('SUSPENDED')) {
return false;
} }
if ($this->hasRole('admin') || $this->hasRole('developer')) { 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){ public function hasAccess($route)
if ($this->hasRole('SUSPENDED')){ {
return false; if ($this->hasRole('SUSPENDED')) {
return false;
} }
return $this->accesses()->where('route',$route)->count() > 0; return $this->accesses()->where('route', $route)->count() > 0;
} }
} }

@ -137,6 +137,19 @@ return [
'prefix' => env('PANEL_PREFIX','dashboard'), 'prefix' => env('PANEL_PREFIX','dashboard'),
'page_count' => env('PANEL_PAGE_COUNT',30), '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 | Media

@ -1 +1,20 @@
import 'bootstrap'; 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;

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

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

@ -1,2 +1,3 @@
@import "remixicon/fonts/remixicon.css"; @import "remixicon/fonts/remixicon.css";
@import "tiny-slider/dist/tiny-slider.css"; @import "tiny-slider/dist/tiny-slider.css";
@import "vue-toast-notification/dist/theme-bootstrap.css";

@ -8,7 +8,7 @@
@include('components.err') @include('components.err')
<gfxer <gfxer
:items='@json(\App\Models\Gfx::all('key','label','value'))' :items='@json(\App\Models\Gfx::all('key','label','value'))'
:previews='@json($prviews)' :previews='@json($previews)'
></gfxer> ></gfxer>
<button <button
data-link="{{getRoute('update')}}" data-link="{{getRoute('update')}}"

@ -1,5 +1,8 @@
</div>
@yield('custom-foot') @yield('custom-foot')
<input type="hidden" id="api-display-url" value="{{route('v1.visitor.display')}}"> <input type="hidden" id="api-display-url" value="{{route('v1.visitor.display')}}">
<input type="hidden" id="api-fav-toggle" value="{{route('client.client.product-fav-toggle','')}}/">
{{--@if(auth()->check() && auth()->user()->hasRole('developer') && !request()->has('ediable'))--}} {{--@if(auth()->check() && auth()->user()->hasRole('developer') && !request()->has('ediable'))--}}
{{--<a id="do-edit" href="?ediable">--}} {{--<a id="do-edit" href="?ediable">--}}
{{-- <i class="ri-settings-2-line"></i>--}} {{-- <i class="ri-settings-2-line"></i>--}}

@ -29,3 +29,5 @@
@include($preloader->getBlade()) @include($preloader->getBlade())
@endif @endif
</div> </div>
<div id="app">

@ -39,6 +39,6 @@ Route::prefix('v1')->name('v1.')->group(
Route::post('morph/search', [\App\Http\Controllers\Api\MorphController::class, 'search'])->name('morph.search'); Route::post('morph/search', [\App\Http\Controllers\Api\MorphController::class, 'search'])->name('morph.search');
Route::post('visitor/display', [\App\Http\Controllers\Api\VisitorController::class, 'display'])->name('visitor.display'); Route::post('visitor/display', [\App\Http\Controllers\Api\VisitorController::class, 'display'])->name('visitor.display');
Route::apiResource('web', \App\Http\Controllers\Api\Web\HomeController::class)->only('index'); Route::apiResource('web', \App\Http\Controllers\Api\HomeController::class)->only('index');
Route::apiResource('products' , \App\Http\Controllers\Api\Web\ProductController::class)->only('index'); Route::apiResource('products' , \App\Http\Controllers\Api\ProductController::class)->only('index');
}); });

@ -366,8 +366,9 @@ Route::name('client.')->group(function (){
Route::get('/gallery/{gallery}', [\App\Http\Controllers\ClientController::class,'gallery'])->name('gallery'); Route::get('/gallery/{gallery}', [\App\Http\Controllers\ClientController::class,'gallery'])->name('gallery');
Route::get('/search', [\App\Http\Controllers\ClientController::class,'search'])->name('search'); Route::get('/search', [\App\Http\Controllers\ClientController::class,'search'])->name('search');
Route::get('attach/download/{attachment}', [\App\Http\Controllers\ClientController::class,'attachDl'])->name('attach-dl'); Route::get('attach/download/{attachment}', [\App\Http\Controllers\ClientController::class,'attachDl'])->name('attach-dl');
Route::get('/{post}', [\App\Http\Controllers\ClientController::class,'post'])->name('post'); Route::get('/post/{post}', [\App\Http\Controllers\ClientController::class,'post'])->name('post');
Route::get('product/fav/toggle/{product}', [\App\Http\Controllers\ClientController::class, 'ProductFavToggle'])->name('client.product-fav-toggle');
Route::post('/comment/submit', [\App\Http\Controllers\ClientController::class,'submitComment'])->name('comment.submit'); Route::post('/comment/submit', [\App\Http\Controllers\ClientController::class,'submitComment'])->name('comment.submit');
})->middleware([\App\Http\Middleware\VisitorCounter::class]); })->middleware([\App\Http\Middleware\VisitorCounter::class]);
@ -389,3 +390,9 @@ Route::get('test',function (){
})->name('test'); })->name('test');
Route::get('whoami',function (){
if (!auth('customer')->check()){
return 'You are nothing';
}
return \Auth::guard('customer')->user();
})->name('whoami');

Loading…
Cancel
Save