added vue & vuex to client js

added vue-toast to client
added image seeder guide to readme
added fav toggle to client
pull/49/head
A1Gard 5 months ago
parent ff0395f4a4
commit d7292f7457

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

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

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

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

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

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

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

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

@ -148,4 +148,30 @@ class ClientController extends Controller
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\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');
}
}

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

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

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

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

@ -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 "tiny-slider/dist/tiny-slider.css";
@import "vue-toast-notification/dist/theme-bootstrap.css";

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

@ -1,5 +1,8 @@
</div>
@yield('custom-foot')
<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'))--}}
{{--<a id="do-edit" href="?ediable">--}}
{{-- <i class="ri-settings-2-line"></i>--}}

@ -29,3 +29,5 @@
@include($preloader->getBlade())
@endif
</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('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('products' , \App\Http\Controllers\Api\Web\ProductController::class)->only('index');
Route::apiResource('web', \App\Http\Controllers\Api\HomeController::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('/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('/{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');
})->middleware([\App\Http\Middleware\VisitorCounter::class]);
@ -389,3 +390,9 @@ Route::get('test',function (){
})->name('test');
Route::get('whoami',function (){
if (!auth('customer')->check()){
return 'You are nothing';
}
return \Auth::guard('customer')->user();
})->name('whoami');

Loading…
Cancel
Save