diff --git a/.env.example b/.env.example index 22ec876..68d7ca3 100644 --- a/.env.example +++ b/.env.example @@ -80,3 +80,6 @@ XLANG_API_URL="http://5.255.98.77:3001" CURRENCY_SYMBOL="$" CURRENCY_FACTOR=1 CURRENCY_CODE=USD + +SIGN_SMS=true +SIGN_DRIVER=Kavenegar diff --git a/app/Helpers/Helper.php b/app/Helpers/Helper.php index 286b89b..03c4eea 100644 --- a/app/Helpers/Helper.php +++ b/app/Helpers/Helper.php @@ -427,7 +427,7 @@ function showCatNested($cats, $parent = null) foreach ($cats as $cat) { if ($cat->parent_id == $parent) { $ret .= "
  • "; - $ret .= ""; + $ret .= ""; $ret .= $cat->name . ''; $ret .= showCatNested($cats, $cat->id); $ret .= "
  • "; @@ -849,6 +849,7 @@ function getCategoryProductBySetting($key, $limit = 10, $order = 'id', $dir = "D return Category::where('id', getSetting($key) ?? 1)->first() ->products()->where('status', 1)->orderBy($order, $dir)->limit($limit)->get(); } + /** * get group's posts by setting key * @param $key @@ -936,5 +937,41 @@ function errors($errors, $status = 422, $message = null, $data = null) */ function readable($text) { - return ucfirst(trim(str_replace(['-','_'],' ',$text))); + return ucfirst(trim(str_replace(['-', '_'], ' ', $text))); +} + + +/** + * register guest logs + * @param $action + * @param $type + * @param $id + * @return void + */ +function guestLog($action, $type = null, $id = null) +{ + $gl = new \App\Models\GuestLog(); + $gl->action = $action; + $gl->ip = request()->ip(); + $gl->loggable_type = $type; + $gl->loggable_id = $id; + $gl->save(); +} + +/** + * is user try more than allowed or not + * @param $action + * @param $max + * @param $minutes + * @return bool + */ +function isGuestMaxAttemptTry($action, $max = 5, $minutes = 60) +{ + if (\App\Models\GuestLog::where('ip', request()->ip()) + ->where('action', $action) + ->where('created_at', '<' ,time() - ($minutes * 60) )->count() >= $max) { + return true; + } else { + return false; + } } diff --git a/app/Http/Controllers/ClientController.php b/app/Http/Controllers/ClientController.php index fc7cffc..021aa8b 100644 --- a/app/Http/Controllers/ClientController.php +++ b/app/Http/Controllers/ClientController.php @@ -185,15 +185,15 @@ class ClientController extends Controller if ($request->has('meta')) { foreach ($category->props()->where('searchable', 1)->get() as $prop) { - if (isset($request->input('meta')[$prop->name]) && $request->input('meta')[$prop->name] != '' && $request->input('meta')[$prop->name] != '[]') { + if (isset($request->input('meta')[$prop->name]) && $request->input('meta')[$prop->name] != '' && $request->input('meta')[$prop->name] != '[]') { switch ($prop->type) { case 'checkbox': - if ($prop->priceable){ + if ($prop->priceable) { $id = Quantity::where('count', '>', 0) - ->where('data', 'LIKE', '%"'.$prop->name.'":%') + ->where('data', 'LIKE', '%"' . $prop->name . '":%') ->pluck('product_id')->toArray(); $query->whereIn('id', $id); - }else{ + } else { $query->whereHasMeta($prop->name); } @@ -201,15 +201,15 @@ class ClientController extends Controller case 'number': case 'select': case 'color': - if ($prop->priceable){ + if ($prop->priceable) { $id = Quantity::where('count', '>', 0) - ->where('data', 'LIKE', '%"'.$prop->name.'":"' . $request->meta[$prop->name] . '"%') + ->where('data', 'LIKE', '%"' . $prop->name . '":"' . $request->meta[$prop->name] . '"%') ->pluck('product_id')->toArray(); - $id = array_merge($id,$query->whereMeta($prop->name, $request->input('meta')[$prop->name])->pluck('id')->toArray()); + $id = array_merge($id, $query->whereMeta($prop->name, $request->input('meta')[$prop->name])->pluck('id')->toArray()); $id = array_unique($id); $query->whereIn('id', $id); - }else{ + } else { $query->whereMeta($prop->name, $request->input('meta')[$prop->name]); } break; @@ -218,26 +218,26 @@ class ClientController extends Controller break; case 'multi': case 'singlemulti': - if ($prop->priceable){ - $q = Quantity::where('count', '>', 0); - $metas = json_decode($request->meta[$prop->name], true); - $q->where(function ($query) use ($metas) { - foreach ($metas as $meta) { - $query->orWhere('data', 'LIKE', '%' . $meta . '%'); - } - }); - $query->whereIn('id',$q->pluck('product_id')->toArray()); - }else{ - $q = Meta::where('key',$prop->name)->where('metable_type',Product::class); - $metas = json_decode($request->meta[$prop->name], true); - $q->where(function ($query) use ($metas) { - foreach ($metas as $meta) { - $query->orWhere('value', 'LIKE', '%' . $meta . '%'); - } - }); - - $query->whereIn('id',$q->pluck('metable_id')->toArray()); - } + if ($prop->priceable) { + $q = Quantity::where('count', '>', 0); + $metas = json_decode($request->meta[$prop->name], true); + $q->where(function ($query) use ($metas) { + foreach ($metas as $meta) { + $query->orWhere('data', 'LIKE', '%' . $meta . '%'); + } + }); + $query->whereIn('id', $q->pluck('product_id')->toArray()); + } else { + $q = Meta::where('key', $prop->name)->where('metable_type', Product::class); + $metas = json_decode($request->meta[$prop->name], true); + $q->where(function ($query) use ($metas) { + foreach ($metas as $meta) { + $query->orWhere('value', 'LIKE', '%' . $meta . '%'); + } + }); + + $query->whereIn('id', $q->pluck('metable_id')->toArray()); + } } } @@ -351,15 +351,134 @@ class ClientController extends Controller } - public function compare() { $area = 'compare'; $title = __("Compare products"); $subtitle = ''; $ids = json_decode(\Cookie::get('compares'), true); - $products = Product::whereIn('id',$ids)->where('status',1)->get(); + $products = Product::whereIn('id', $ids)->where('status', 1)->get(); return view('client.default-list', compact('area', 'products', 'title', 'subtitle')); } + + public function signOut() + { + auth('customer')->logout(); + return redirect()->route('client.sign-in')->with(['message' => __("Signed out successfully")]); + } + + public function signIn() + { + $area = 'login'; + $title = __("sign in"); + $subtitle = 'Sign in as customer'; + return view('client.default-list', compact('area', 'title', 'subtitle')); + } + + public function signUp() + { + + } + + public function singInDo(Request $request) + { + $max = 3; + $request->validate([ + 'email' => 'required|string|email|max:255', + 'password' => 'required|string|min:6', + ]); + + if (isGuestMaxAttemptTry('login', $max)) { + return redirect()->back()->withErrors([__('You try more than :COUNT attempts, Try it later', ["COUNT" => $max])]); + } + + guestLog('login'); + $customer = Customer::where('email', $request->input('email')); + if ($customer->count() == 0) { + return redirect()->back()->withErrors([__('Email or password is incorrect')]); + } + + $customer = $customer->first(); + + if (\Hash::check($request->input('password'), $customer->password)) { + auth('customer')->login($customer); + return redirect()->route('client.profile')->with(['message' => __('Signed in successfully')]); + } else { + return redirect()->back()->withErrors([__('Email or password is incorrect'), __('If you forget your password call us')]); + } + } + + public function profile() + { + return auth('customer')->user(); + } + + public function sendSms(Request $request) + { + + if (isGuestMaxAttemptTry('sms', 1, 2)) { + return [ + 'OK' => false, + 'message' => __('You try attempts, Try it a few minutes'), + 'error' => __('You try attempts, Try it a few minutes'), + ]; + } + guestLog('sms'); + $customer = Customer::where('mobile', $request->input('tel')); + $code = rand(11111, 99999); + if ($customer->count() == 0) { + $customer = new Customer(); + $customer->mobile = $request->input('tel'); + $customer->code = $code; + $customer->save(); + } else { + $customer = $customer->first(); + $customer->code = $code; + $customer->save(); + } + // WIP send sms + + return [ + 'OK' => true, + 'message' => __('Auth code send successfully'), + ]; + } + + public function checkAuth(Request $request) + { + $max = 3; + $request->validate([ + 'tel' => 'required|string|min:6', + 'code' => 'required|string|min:5', + ]); + + if (isGuestMaxAttemptTry('login', $max)) { + return redirect()->back()->withErrors([__('You try more than :COUNT attempts, Try it later', ["COUNT" => $max])]); + } + + guestLog('login'); + + $customer = Customer::where('mobile', $request->input('tel')) + ->where('code', $request->input('code'))->first(); + + if ($customer == null) { + return [ + 'OK' => false, + 'message' => __('Auth code is invalid'), + 'error' => __('Auth code is invalid'), + ]; + } + $customer->code = null; + $customer->save(); + + auth('customer')->login($customer); + + return [ + 'OK' => true, + 'message' => __('You are logged in successfully'), + ]; + + } + } diff --git a/app/Models/GuestLog.php b/app/Models/GuestLog.php index 60938be..166ed30 100644 --- a/app/Models/GuestLog.php +++ b/app/Models/GuestLog.php @@ -8,4 +8,6 @@ use Illuminate\Database\Eloquent\Model; class GuestLog extends Model { use HasFactory; + + public static $actions = ['login','register','search','sms']; } diff --git a/config/app.php b/config/app.php index 7f02124..0610c85 100644 --- a/config/app.php +++ b/config/app.php @@ -150,6 +150,18 @@ return [ 'factor' => env('CURRENCY_FACTOR',1), 'code'=> env('CURRENCY_CODE','USD'), ], + + /* + |-------------------------------------------------------------------------- + | sign in/up config + |-------------------------------------------------------------------------- + | + */ + + 'sign' => [ + 'sms' => env('SIGN_SMS',false), + 'driver' => env('SIGN_DRIVER',''), + ], /* |-------------------------------------------------------------------------- | Media diff --git a/database/migrations/2024_05_07_124306_create_guest_logs_table.php b/database/migrations/2024_05_07_124306_create_guest_logs_table.php index ab8f352..30125b5 100644 --- a/database/migrations/2024_05_07_124306_create_guest_logs_table.php +++ b/database/migrations/2024_05_07_124306_create_guest_logs_table.php @@ -15,7 +15,7 @@ return new class extends Migration $table->id(); $table->ipAddress('ip'); $table->string('action'); - $table->morphs('loggable'); + $table->nullableMorphs('loggable'); $table->timestamps(); }); } diff --git a/resources/js/client-custom/login.js b/resources/js/client-custom/login.js new file mode 100644 index 0000000..4947c0a --- /dev/null +++ b/resources/js/client-custom/login.js @@ -0,0 +1,50 @@ +import axios from "axios"; + +function isValidMobile(p) { + const regex = /^(\+|[0-9])([0-9]{9,14})$/gm; + return regex.test(p); +} + +document.addEventListener('DOMContentLoaded', function () { + document.querySelector('#send-auth-code')?.addEventListener('click', async function () { + let url = this.getAttribute('data-route'); + let tel = document.querySelector('#tel').value; + if (tel.length < 11 || !isValidMobile(tel)){ + window.$toast.error('Invalid mobile'); + return; + } + + let resp = await axios.get(url+'?tel='+tel); + if (resp.data.OK){ + window.$toast.success(resp.data.message); + document.querySelector('#tel').setAttribute('readonly',''); + document.querySelector('.not-send').style.display = 'block'; + document.querySelector('.sent').style.display = 'none'; + }else{ + window.$toast.error(resp.data.message); + } + }); + document.querySelector('#send-auth-check')?.addEventListener('click', async function () { + let url = this.getAttribute('data-route'); + let tel = document.querySelector('#tel').value; + let code = document.querySelector('#auth').value; + if (tel.length < 11 || !isValidMobile(tel)){ + window.$toast.error('Invalid mobile'); + return; + } + if (code.length != 5 ){ + window.$toast.error('Invalid code'); + return; + } + + let resp = await axios.get(url+'?tel='+tel+'&code='+code); + if (resp.data.OK){ + window.$toast.success(resp.data.message); + setTimeout( () => { + window.location.href = this.getAttribute('data-profile'); + },5000); + }else{ + window.$toast.error(resp.data.message); + } + }); +}); diff --git a/resources/views/segments/login/LoginBigBg/LoginBigBg.blade.php b/resources/views/segments/login/LoginBigBg/LoginBigBg.blade.php new file mode 100644 index 0000000..f212b63 --- /dev/null +++ b/resources/views/segments/login/LoginBigBg/LoginBigBg.blade.php @@ -0,0 +1,54 @@ +
    +
    + @csrf +

    + {{$subtitle}} +

    +
    + @include('components.err') +
    +
    + @if(!config('app.sign.sms')) + + + + + + @else + + +
    + + + + + + + +
    +
    + +
    + @endif +
    +
    +
    diff --git a/resources/views/segments/login/LoginBigBg/LoginBigBg.js b/resources/views/segments/login/LoginBigBg/LoginBigBg.js new file mode 100644 index 0000000..e69de29 diff --git a/resources/views/segments/login/LoginBigBg/LoginBigBg.json b/resources/views/segments/login/LoginBigBg/LoginBigBg.json new file mode 100644 index 0000000..c39cab7 --- /dev/null +++ b/resources/views/segments/login/LoginBigBg/LoginBigBg.json @@ -0,0 +1,10 @@ +{ + "name": "LoginBigBg", + "version": "1.0", + "author": "xStack", + "email": "xshop@xstack.ir", + "license": "GPL-3.0-or-later", + "url": "https:\/\/xstack.ir", + "author_url": "https:\/\/4xmen.ir", + "packages": [] +} \ No newline at end of file diff --git a/resources/views/segments/login/LoginBigBg/LoginBigBg.php b/resources/views/segments/login/LoginBigBg/LoginBigBg.php new file mode 100644 index 0000000..aee8a6e --- /dev/null +++ b/resources/views/segments/login/LoginBigBg/LoginBigBg.php @@ -0,0 +1,32 @@ +section = 'theme'; + $setting->key = $part->area->name . '_' . $part->part.'_jpg'; + $setting->value = null; + $setting->type = 'FILE'; + $setting->size = 12; + $setting->title = $part->area->name . ' ' . $part->part.' background image'; + $setting->save(); + + File::copy(__DIR__.'/../../default-assets/bg-girl.jpg',public_path('upload/images/').$part->area->name . '.' . $part->part.'.jpg'); + } + public static function onRemove(Part $part = null) + { + + } + public static function onMount(Part $part = null) + { + return $part; + } +} diff --git a/resources/views/segments/login/LoginBigBg/LoginBigBg.scss b/resources/views/segments/login/LoginBigBg/LoginBigBg.scss new file mode 100644 index 0000000..ddcf122 --- /dev/null +++ b/resources/views/segments/login/LoginBigBg/LoginBigBg.scss @@ -0,0 +1,27 @@ +#LoginBigBg { + height: 90vh; + background-size: cover; + background-position: center; + display: flex; + align-items: center; + justify-content: center; + text-align: center; + + + #login-form{ + max-width: 90%; + width: 450px; + } + #login-content{ + + text-align: start; + padding: 1rem; + background: #ffffff99; + backdrop-filter: blur(4px); + border-radius: var(--xshop-border-radius); + } + + .not-send{ + display: none; + } +} diff --git a/resources/views/segments/login/LoginBigBg/screenshot.png b/resources/views/segments/login/LoginBigBg/screenshot.png new file mode 100644 index 0000000..b338fcc Binary files /dev/null and b/resources/views/segments/login/LoginBigBg/screenshot.png differ diff --git a/routes/web.php b/routes/web.php index 9ff45f4..783f38d 100644 --- a/routes/web.php +++ b/routes/web.php @@ -361,6 +361,13 @@ Route::middleware([\App\Http\Middleware\VisitorCounter::class]) // index Route::get('/', [\App\Http\Controllers\ClientController::class,'welcome'])->name('welcome'); Route::get('/posts', [\App\Http\Controllers\ClientController::class,'posts'])->name('posts'); + Route::get('/customer/sign-out', [\App\Http\Controllers\ClientController::class,'signOut'])->name('sign-out'); + Route::post('/customer/sign-in/do', [\App\Http\Controllers\ClientController::class,'singInDo'])->name('sign-in-do'); + Route::get('/customer/sign-in', [\App\Http\Controllers\ClientController::class,'signIn'])->name('sign-in'); + Route::get('/customer/sign-up', [\App\Http\Controllers\ClientController::class,'signUp'])->name('sign-up'); + Route::get('/customer/send/auth-code', [\App\Http\Controllers\ClientController::class,'sendSms'])->name('send-sms'); + Route::get('/customer/check/auth-code', [\App\Http\Controllers\ClientController::class,'checkAuth'])->name('check-auth'); + Route::get('/customer/profile', [\App\Http\Controllers\ClientController::class,'profile'])->name('profile'); Route::get('/compare', [\App\Http\Controllers\ClientController::class,'compare'])->name('compare'); Route::get('/galleries', [\App\Http\Controllers\ClientController::class,'galleries'])->name('galleries'); Route::get('/products', [\App\Http\Controllers\ClientController::class,'products'])->name('products');