customer login and profile page

dev
Aji Kamaludin 1 year ago
parent cabe8a2e71
commit c64cf07702
No known key found for this signature in database
GPG Key ID: 19058F67F0083AD3

@ -0,0 +1,106 @@
<?php
namespace App\Http\Controllers\Customer;
use App\Http\Controllers\Controller;
use App\Models\Customer;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Laravel\Socialite\Facades\Socialite;
use SocialiteProviders\Manager\Config;
class AuthController extends Controller
{
public function login()
{
return inertia('Home/Auth/Login');
}
public function update(Request $request)
{
$request->validate([
'username' => 'string|required|alpha_dash',
'password' => 'string|required',
]);
$isAuth = Auth::guard('customer')->attempt(['username' => $request->username, 'password' => $request->password]);
if ($isAuth) {
return redirect()->route('home.index');
}
return redirect()->route('customer.login')
->with('message', ['type' => 'error', 'message' => 'invalid credentials']);
}
public function signin_google()
{
$config = new Config(
env('GOOGLE_CLIENT_ID'),
env('GOOGLE_CLIENT_SECRET'),
route('customer.login.callback_google')
);
return Socialite::driver('google')
->setConfig($config)
->redirect();
}
public function callback_google()
{
$user = Socialite::driver('google')->user();
$customer = Customer::where('google_id', $user->id)->first();
if ($customer == null) {
$customer = Customer::create([
'fullname' => $user->name,
'name' => $user->nickname,
'email' => $user->email,
'username' => $user->id,
'google_id' => $user->id,
'image' => 'GOOGLE-' . $user->avatar,
'google_oauth_response' => json_encode($user),
]);
}
Auth::guard('customer')->loginUsingId($customer->id);
return redirect()->route('home.index');
}
public function register()
{
return inertia('Home/Auth/Register');
}
public function store(Request $request)
{
$request->validate([
'fullname' => 'string|required',
'name' => 'string|required',
'address' => 'string|required',
'phone' => 'string|required|numeric',
'username' => 'string|required|min:5|alpha_dash|unique:customers,username',
'password' => 'string|required|min:8|confirmed',
]);
$customer = Customer::create([
'fullname' => $request->fullname,
'name' => $request->name,
'address' => $request->address,
'phone' => $request->phone,
'username' => $request->username,
'password' => bcrypt($request->password),
]);
Auth::guard('customer')->loginUsingId($customer->id);
return redirect()->route('home.index');
}
public function destroy()
{
Auth::logout();
return redirect()->route('customer.login')
->with('message', ['type' => 'success', 'message' => 'you are logged out, see you next time']);
}
}

@ -0,0 +1,50 @@
<?php
namespace App\Http\Controllers\Customer;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class ProfileController extends Controller
{
public function index()
{
return inertia('Home/Profile/Index');
}
public function show()
{
return inertia('Home/Profile/Form');
}
public function update(Request $request)
{
$customer = auth()->user();
$request->validate([
'fullname' => 'string|required',
'name' => 'string|required',
'address' => 'string|required',
'phone' => 'string|required|numeric',
'username' => 'string|required|min:5|alpha_dash|unique:customers,username,' . $customer->id,
'password' => 'nullable|string|min:8|confirmed',
'image' => 'nullable|image',
]);
if ($request->hasFile('image')) {
$file = $request->file('image');
$file->store('uploads', 'public');
$customer->image = $file->hashName();
}
$customer->update([
'fullname' => $request->fullname,
'name' => $request->name,
'address' => $request->address,
'phone' => $request->phone,
'username' => $request->username,
'password' => bcrypt($request->password),
'image' => $customer->image,
]);
}
}

@ -68,5 +68,6 @@ class Kernel extends HttpKernel
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
'inertia.admin' => \App\Http\Middleware\HandleInertiaRequests::class,
'inertia.customer' => \App\Http\Middleware\HandleInertiaCustomerRequests::class,
'guard_should_customer' => \App\Http\Middleware\GuardCustomer::class,
];
}

@ -0,0 +1,23 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpFoundation\Response;
class GuardCustomer
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
Auth::shouldUse('customer');
return $next($request);
}
}

@ -31,10 +31,10 @@ class HandleInertiaCustomerRequests extends Middleware
{
return array_merge(parent::share($request), [
'auth' => [
'user' => $request->user(),
'user' => auth('customer')->user(),
],
'flash' => [
'message' => fn () => $request->session()->get('message'),
'message' => fn () => $request->session()->get('message') ?? ['type' => null, 'message' => null],
],
'app_name' => env('APP_NAME', 'App Name'),
]);

@ -3,6 +3,7 @@
namespace App\Models;
use App\Models\Traits\UserTrackable;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Concerns\HasUlids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
@ -13,6 +14,7 @@ class Customer extends Authenticatable
use HasFactory, HasUlids, UserTrackable, SoftDeletes;
protected $fillable = [
'username',
'email',
'password',
'name',
@ -27,5 +29,35 @@ class Customer extends Authenticatable
'identity_verified',
'identity_image',
'customer_level_id',
'google_oauth_response'
];
protected $hidden = [
'password',
'google_oauth_reponse'
];
protected $appends = [
'image_url',
];
public function imageUrl(): Attribute
{
return Attribute::make(
get: function () {
if ($this->google_id != null) {
$image = explode('-', $this->images);
if ($image[0] == "IMAGE") {
return $image[1];
}
}
if ($this->image != null) {
return $this->asset($this->image);
}
return asset('sample/avatar.svg');
}
);
}
}

@ -18,6 +18,9 @@ class EventServiceProvider extends ServiceProvider
Registered::class => [
SendEmailVerificationNotification::class,
],
\SocialiteProviders\Manager\SocialiteWasCalled::class => [
\SocialiteProviders\Google\GoogleExtendSocialite::class . '@handle',
],
];
/**

@ -11,8 +11,10 @@
"inertiajs/inertia-laravel": "^0.6.9",
"laravel/framework": "^10.12.0",
"laravel/sanctum": "^3.2.5",
"laravel/socialite": "^5.6",
"laravel/tinker": "^2.8.1",
"react/async": "^4",
"socialiteproviders/google": "^4.1",
"tightenco/ziggy": "^1.6.0"
},
"require-dev": {

738
composer.lock generated

File diff suppressed because it is too large Load Diff

@ -31,4 +31,9 @@ return [
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
],
'google' => [
'client_id' => env('GOOGLE_CLIENT_ID'),
'client_secret' => env('GOOGLE_CLIENT_SECRET'),
'redirect' => env('APP_URL') . '/login/callback_google',
],
];

@ -16,6 +16,7 @@ return new class extends Migration
$table->string('email')->nullable();
$table->string('password')->nullable();
$table->string('username')->nullable();
$table->string('name')->nullable();
$table->string('fullname')->nullable();
$table->text('address')->nullable();
@ -28,6 +29,7 @@ return new class extends Migration
$table->smallInteger('identity_verified')->nullable();
$table->string('identity_image')->nullable();
$table->ulid('customer_level_id')->nullable();
$table->text('google_oauth_response')->nullable();
$table->timestamps();
$table->softDeletes();

197
package-lock.json generated

@ -1,5 +1,5 @@
{
"name": "laravel-template",
"name": "voucher",
"lockfileVersion": 3,
"requires": true,
"packages": {
@ -70,30 +70,30 @@
}
},
"node_modules/@babel/compat-data": {
"version": "7.21.9",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.9.tgz",
"integrity": "sha512-FUGed8kfhyWvbYug/Un/VPJD41rDIgoVVcR+FuzhzOYyRz5uED+Gd3SLZml0Uw2l2aHFb7ZgdW5mGA3G2cCCnQ==",
"version": "7.22.3",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.3.tgz",
"integrity": "sha512-aNtko9OPOwVESUFp3MZfD8Uzxl7JzSeJpd7npIoxCasU37PFbAQRpKglkaKwlHOyeJdrREpo8TW8ldrkYWwvIQ==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/core": {
"version": "7.21.8",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.8.tgz",
"integrity": "sha512-YeM22Sondbo523Sz0+CirSPnbj9bG3P0CdHcBZdqUuaeOaYEFbOLoGU7lebvGP6P5J/WE9wOn7u7C4J9HvS1xQ==",
"version": "7.22.1",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.1.tgz",
"integrity": "sha512-Hkqu7J4ynysSXxmAahpN1jjRwVJ+NdpraFLIWflgjpVob3KNyK3/tIUc7Q7szed8WMp0JNa7Qtd1E9Oo22F9gA==",
"dev": true,
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.21.4",
"@babel/generator": "^7.21.5",
"@babel/helper-compilation-targets": "^7.21.5",
"@babel/helper-module-transforms": "^7.21.5",
"@babel/helpers": "^7.21.5",
"@babel/parser": "^7.21.8",
"@babel/template": "^7.20.7",
"@babel/traverse": "^7.21.5",
"@babel/types": "^7.21.5",
"@babel/generator": "^7.22.0",
"@babel/helper-compilation-targets": "^7.22.1",
"@babel/helper-module-transforms": "^7.22.1",
"@babel/helpers": "^7.22.0",
"@babel/parser": "^7.22.0",
"@babel/template": "^7.21.9",
"@babel/traverse": "^7.22.1",
"@babel/types": "^7.22.0",
"convert-source-map": "^1.7.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
@ -109,12 +109,12 @@
}
},
"node_modules/@babel/generator": {
"version": "7.21.9",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.9.tgz",
"integrity": "sha512-F3fZga2uv09wFdEjEQIJxXALXfz0+JaOb7SabvVMmjHxeVTuGW8wgE8Vp1Hd7O+zMTYtcfEISGRzPkeiaPPsvg==",
"version": "7.22.3",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.3.tgz",
"integrity": "sha512-C17MW4wlk//ES/CJDL51kPNwl+qiBQyN7b9SKyVp11BLGFeSPoVaHrv+MNt8jwQFhQWowW88z1eeBx3pFz9v8A==",
"dev": true,
"dependencies": {
"@babel/types": "^7.21.5",
"@babel/types": "^7.22.3",
"@jridgewell/gen-mapping": "^0.3.2",
"@jridgewell/trace-mapping": "^0.3.17",
"jsesc": "^2.5.1"
@ -124,12 +124,12 @@
}
},
"node_modules/@babel/helper-compilation-targets": {
"version": "7.21.5",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.5.tgz",
"integrity": "sha512-1RkbFGUKex4lvsB9yhIfWltJM5cZKUftB2eNajaDv3dCMEp49iBG0K14uH8NnX9IPux2+mK7JGEOB0jn48/J6w==",
"version": "7.22.1",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.1.tgz",
"integrity": "sha512-Rqx13UM3yVB5q0D/KwQ8+SPfX/+Rnsy1Lw1k/UwOC4KC6qrzIQoY3lYnBu5EHKBlEHHcj0M0W8ltPSkD8rqfsQ==",
"dev": true,
"dependencies": {
"@babel/compat-data": "^7.21.5",
"@babel/compat-data": "^7.22.0",
"@babel/helper-validator-option": "^7.21.0",
"browserslist": "^4.21.3",
"lru-cache": "^5.1.1",
@ -143,9 +143,9 @@
}
},
"node_modules/@babel/helper-environment-visitor": {
"version": "7.21.5",
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.21.5.tgz",
"integrity": "sha512-IYl4gZ3ETsWocUWgsFZLM5i1BYx9SoemminVEXadgLBa9TdeorzgLKm8wWLA6J1N/kT3Kch8XIk1laNzYoHKvQ==",
"version": "7.22.1",
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.1.tgz",
"integrity": "sha512-Z2tgopurB/kTbidvzeBrc2To3PUP/9i5MUe+fU6QJCQDyPwSH2oRapkLw3KGECDYSjhQZCNxEvNvZlLw8JjGwA==",
"dev": true,
"engines": {
"node": ">=6.9.0"
@ -189,19 +189,19 @@
}
},
"node_modules/@babel/helper-module-transforms": {
"version": "7.21.5",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.5.tgz",
"integrity": "sha512-bI2Z9zBGY2q5yMHoBvJ2a9iX3ZOAzJPm7Q8Yz6YeoUjU/Cvhmi2G4QyTNyPBqqXSgTjUxRg3L0xV45HvkNWWBw==",
"version": "7.22.1",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.1.tgz",
"integrity": "sha512-dxAe9E7ySDGbQdCVOY/4+UcD8M9ZFqZcZhSPsPacvCG4M+9lwtDDQfI2EoaSvmf7W/8yCBkGU0m7Pvt1ru3UZw==",
"dev": true,
"dependencies": {
"@babel/helper-environment-visitor": "^7.21.5",
"@babel/helper-environment-visitor": "^7.22.1",
"@babel/helper-module-imports": "^7.21.4",
"@babel/helper-simple-access": "^7.21.5",
"@babel/helper-split-export-declaration": "^7.18.6",
"@babel/helper-validator-identifier": "^7.19.1",
"@babel/template": "^7.20.7",
"@babel/traverse": "^7.21.5",
"@babel/types": "^7.21.5"
"@babel/template": "^7.21.9",
"@babel/traverse": "^7.22.1",
"@babel/types": "^7.22.0"
},
"engines": {
"node": ">=6.9.0"
@ -268,14 +268,14 @@
}
},
"node_modules/@babel/helpers": {
"version": "7.21.5",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.5.tgz",
"integrity": "sha512-BSY+JSlHxOmGsPTydUkPf1MdMQ3M81x5xGCOVgWM3G8XH77sJ292Y2oqcp0CbbgxhqBuI46iUz1tT7hqP7EfgA==",
"version": "7.22.3",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.3.tgz",
"integrity": "sha512-jBJ7jWblbgr7r6wYZHMdIqKc73ycaTcCaWRq4/2LpuPHcx7xMlZvpGQkOYc9HeSjn6rcx15CPlgVcBtZ4WZJ2w==",
"dev": true,
"dependencies": {
"@babel/template": "^7.20.7",
"@babel/traverse": "^7.21.5",
"@babel/types": "^7.21.5"
"@babel/template": "^7.21.9",
"@babel/traverse": "^7.22.1",
"@babel/types": "^7.22.3"
},
"engines": {
"node": ">=6.9.0"
@ -296,9 +296,9 @@
}
},
"node_modules/@babel/parser": {
"version": "7.21.9",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.9.tgz",
"integrity": "sha512-q5PNg/Bi1OpGgx5jYlvWZwAorZepEudDMCLtj967aeS7WMont7dUZI46M2XwcIQqvUlMxWfdLFu4S/qSxeUu5g==",
"version": "7.22.4",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.4.tgz",
"integrity": "sha512-VLLsx06XkEYqBtE5YGPwfSGwfrjnyPP5oiGty3S8pQLFDFLaS8VwWSIxkTXpcvr5zeYLE6+MBNl2npl/YnfofA==",
"dev": true,
"bin": {
"parser": "bin/babel-parser.js"
@ -338,9 +338,9 @@
}
},
"node_modules/@babel/runtime": {
"version": "7.21.5",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.5.tgz",
"integrity": "sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==",
"version": "7.22.3",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.3.tgz",
"integrity": "sha512-XsDuspWKLUsxwCp6r7EhsExHtYfbe5oAGQ19kqngTdCPUoPQzOPdUbD/pB9PJiwb2ptYKQDjSJT3R6dC+EPqfQ==",
"dependencies": {
"regenerator-runtime": "^0.13.11"
},
@ -363,19 +363,19 @@
}
},
"node_modules/@babel/traverse": {
"version": "7.21.5",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.5.tgz",
"integrity": "sha512-AhQoI3YjWi6u/y/ntv7k48mcrCXmus0t79J9qPNlk/lAsFlCiJ047RmbfMOawySTHtywXhbXgpx/8nXMYd+oFw==",
"version": "7.22.4",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.4.tgz",
"integrity": "sha512-Tn1pDsjIcI+JcLKq1AVlZEr4226gpuAQTsLMorsYg9tuS/kG7nuwwJ4AB8jfQuEgb/COBwR/DqJxmoiYFu5/rQ==",
"dev": true,
"dependencies": {
"@babel/code-frame": "^7.21.4",
"@babel/generator": "^7.21.5",
"@babel/helper-environment-visitor": "^7.21.5",
"@babel/generator": "^7.22.3",
"@babel/helper-environment-visitor": "^7.22.1",
"@babel/helper-function-name": "^7.21.0",
"@babel/helper-hoist-variables": "^7.18.6",
"@babel/helper-split-export-declaration": "^7.18.6",
"@babel/parser": "^7.21.5",
"@babel/types": "^7.21.5",
"@babel/parser": "^7.22.4",
"@babel/types": "^7.22.4",
"debug": "^4.1.0",
"globals": "^11.1.0"
},
@ -384,9 +384,9 @@
}
},
"node_modules/@babel/types": {
"version": "7.21.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.5.tgz",
"integrity": "sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q==",
"version": "7.22.4",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.4.tgz",
"integrity": "sha512-Tx9x3UBHTTsMSW85WB2kphxYQVvrZ/t1FxD88IpSgIjiUJlCm9z+xWIDwyo1vffTwSqteqyznB8ZE9vYYk16zA==",
"dev": true,
"dependencies": {
"@babel/helper-string-parser": "^7.21.5",
@ -755,9 +755,9 @@
"integrity": "sha512-EvYTiXet5XqweYGClEmpu3BoxmsQ4hkj3QaYA6qEnigCWffTP3vNRwBReTdrwDwo7OoJ3wM8Uoe9Uk4n+d4hfg=="
},
"node_modules/@floating-ui/dom": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.2.8.tgz",
"integrity": "sha512-XLwhYV90MxiHDq6S0rzFZj00fnDM+A1R9jhSioZoMsa7G0Q0i+Q4x40ajR8FHSdYDE1bgjG45mIWe6jtv9UPmg==",
"version": "1.2.9",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.2.9.tgz",
"integrity": "sha512-sosQxsqgxMNkV3C+3UqTS6LxP7isRLwX8WMepp843Rb3/b0Wz8+MdUkxJksByip3C2WwLugLHN1b4ibn//zKwQ==",
"dependencies": {
"@floating-ui/core": "^1.2.6"
}
@ -789,9 +789,9 @@
}
},
"node_modules/@headlessui/react": {
"version": "1.7.14",
"resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.14.tgz",
"integrity": "sha512-znzdq9PG8rkwcu9oQ2FwIy0ZFtP9Z7ycS+BAqJ3R5EIqC/0bJGvhT7193rFf+45i9nnPsYvCQVW4V/bB9Xc+gA==",
"version": "1.7.15",
"resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.15.tgz",
"integrity": "sha512-OTO0XtoRQ6JPB1cKNFYBZv2Q0JMqMGNhYP1CjPvcJvjz8YGokz8oAj89HIYZGN0gZzn/4kk9iUpmMF4Q21Gsqw==",
"dev": true,
"dependencies": {
"client-only": "^0.0.1"
@ -910,9 +910,9 @@
}
},
"node_modules/@popperjs/core": {
"version": "2.11.7",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.7.tgz",
"integrity": "sha512-Cr4OjIkipTtcXKjAsm8agyleBuDHvxzeBoa1v543lbv1YaIwQjESsVcmjiWiPEbC1FIeHOG/Op9kdCmAmiS3Kw==",
"version": "2.11.8",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
@ -1088,9 +1088,9 @@
}
},
"node_modules/browserslist": {
"version": "4.21.5",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz",
"integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==",
"version": "4.21.7",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.7.tgz",
"integrity": "sha512-BauCXrQ7I2ftSqd2mvKHGo85XR0u7Ru3C/Hxsy/0TkfCtjrmAbPdzLGasmoiBxplpDXlPvdjX9u7srIMfgasNA==",
"dev": true,
"funding": [
{
@ -1100,13 +1100,17 @@
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/browserslist"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"dependencies": {
"caniuse-lite": "^1.0.30001449",
"electron-to-chromium": "^1.4.284",
"node-releases": "^2.0.8",
"update-browserslist-db": "^1.0.10"
"caniuse-lite": "^1.0.30001489",
"electron-to-chromium": "^1.4.411",
"node-releases": "^2.0.12",
"update-browserslist-db": "^1.0.11"
},
"bin": {
"browserslist": "cli.js"
@ -1136,9 +1140,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001489",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001489.tgz",
"integrity": "sha512-x1mgZEXK8jHIfAxm+xgdpHpk50IN3z3q3zP261/WS+uvePxW8izXuCu6AHz0lkuYTlATDehiZ/tNyYBdSQsOUQ==",
"version": "1.0.30001492",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001492.tgz",
"integrity": "sha512-2efF8SAZwgAX1FJr87KWhvuJxnGJKOnctQa8xLOskAXNXq8oiuqgl6u1kk3fFpsp3GgvzlRjiK1sl63hNtFADw==",
"dev": true,
"funding": [
{
@ -1386,9 +1390,9 @@
"integrity": "sha512-GJRqdiy2h+EXy6a8E6R+ubmqUM08BK0FWNq41k24fup6045biQ8NXxoXimiwegMQvFFV3t1emADdGNL1TlS61A=="
},
"node_modules/electron-to-chromium": {
"version": "1.4.405",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.405.tgz",
"integrity": "sha512-JdDgnwU69FMZURoesf9gNOej2Cms1XJFfLk24y1IBtnAdhTcJY/mXnokmpmxHN59PcykBP4bgUU98vLY44Lhuw==",
"version": "1.4.416",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.416.tgz",
"integrity": "sha512-AUYh0XDTb2vrj0rj82jb3P9hHSyzQNdTPYWZIhPdCOui7/vpme7+HTE07BE5jwuqg/34TZ8ktlRz6GImJ4IXjA==",
"dev": true
},
"node_modules/error-stack-parser": {
@ -1847,9 +1851,9 @@
}
},
"node_modules/laravel-vite-plugin": {
"version": "0.7.7",
"resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-0.7.7.tgz",
"integrity": "sha512-/KsnyNUOMylBVLvGz1VlL1ukxyQeMQUz4zmnMflYe8fAWzLvjHDXnbh6fLgIrzAmevzNjYm/TUqmD5Io0+kqhg==",
"version": "0.7.8",
"resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-0.7.8.tgz",
"integrity": "sha512-HWYqpQYHR3kEQ1LsHX7gHJoNNf0bz5z5mDaHBLzS+PGLCTmYqlU5/SZyeEgObV7z7bC/cnStYcY9H1DI1D5Udg==",
"dev": true,
"dependencies": {
"picocolors": "^1.0.0",
@ -2150,9 +2154,9 @@
}
},
"node_modules/postcss": {
"version": "8.4.23",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.23.tgz",
"integrity": "sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==",
"version": "8.4.24",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz",
"integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==",
"funding": [
{
"type": "opencollective",
@ -2334,9 +2338,9 @@
}
},
"node_modules/react-datepicker": {
"version": "4.11.0",
"resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-4.11.0.tgz",
"integrity": "sha512-50n93o7mQwBEhg05tbopjFKgs8qgi8VBCAOMC4VqrKut72eAjESc/wXS/k5hRtnP0oe2FCGw7MJuIwh37wuXOw==",
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-4.12.0.tgz",
"integrity": "sha512-czCEp4T8ctyN9NBMG6Xq6pEpOvMtoqf4U5DxFzJoTtv1/gTX3QNJaWBUwD88j//+eF8gVkNvOW8aTAqpaIJBvw==",
"dependencies": {
"@popperjs/core": "^2.9.2",
"classnames": "^2.2.6",
@ -2368,9 +2372,9 @@
"integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ=="
},
"node_modules/react-icons": {
"version": "4.8.0",
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.8.0.tgz",
"integrity": "sha512-N6+kOLcihDiAnj5Czu637waJqSnwlMNROzVZMhfX68V/9bu9qHaMIJC4UdozWoOk57gahFCNHwVvWzm0MTzRjg==",
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.9.0.tgz",
"integrity": "sha512-ijUnFr//ycebOqujtqtV9PFS7JjhWg0QU6ykURVHuL4cbofvRCf3f6GMn9+fBktEFQOIVZnuAYLZdiyadRQRFg==",
"peerDependencies": {
"react": "*"
}
@ -2897,9 +2901,9 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
},
"node_modules/vite": {
"version": "4.3.8",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.3.8.tgz",
"integrity": "sha512-uYB8PwN7hbMrf4j1xzGDk/lqjsZvCDbt/JC5dyfxc19Pg8kRm14LinK/uq+HSLNswZEoKmweGdtpbnxRtrAXiQ==",
"version": "4.3.9",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.3.9.tgz",
"integrity": "sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==",
"dev": true,
"dependencies": {
"esbuild": "^0.17.5",
@ -2977,12 +2981,11 @@
"dev": true
},
"node_modules/yaml": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.0.tgz",
"integrity": "sha512-8/1wgzdKc7bc9E6my5wZjmdavHLvO/QOmLG1FBugblEvY4IXrLjlViIOmL24HthU042lWTDRO90Fz1Yp66UnMw==",
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz",
"integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==",
"engines": {
"node": ">= 14",
"npm": ">= 7"
"node": ">= 14"
}
}
}

@ -0,0 +1 @@
<svg stroke="currentColor" fill="none" stroke-width="1.5" viewBox="0 0 24 24" aria-hidden="true" class="text-white h-14 w-14" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" d="M17.982 18.725A7.488 7.488 0 0012 15.75a7.488 7.488 0 00-5.982 2.975m11.963 0a9 9 0 10-11.963 0m11.963 0A8.966 8.966 0 0112 21a8.966 8.966 0 01-5.982-2.275M15 9.75a3 3 0 11-6 0 3 3 0 016 0z"></path></svg>

After

Width:  |  Height:  |  Size: 446 B

@ -0,0 +1,39 @@
import { isEmpty } from 'lodash'
import React from 'react'
export default function Alert({ type = '', children }) {
if (type === null) {
return ''
}
if (type === 'error') {
return (
<div
className="p-4 mb-4 text-sm text-red-800 rounded-lg bg-red-50 dark:bg-gray-800 dark:text-red-400"
role="alert"
>
{children}
</div>
)
}
if (type === 'success') {
return (
<div
className="p-4 mb-4 text-sm text-red-800 rounded-lg bg-red-50 dark:bg-gray-800 dark:text-red-400"
role="alert"
>
{children}
</div>
)
}
return (
<div
className="p-4 mb-4 text-sm text-blue-800 rounded-lg bg-blue-50 dark:bg-gray-800 dark:text-blue-400"
role="alert"
>
{children}
</div>
)
}

@ -0,0 +1,35 @@
import React, { useRef } from 'react'
export default function FormFile({
label,
onChange,
error,
preview,
inputRef = useRef(),
}) {
return (
<div className="my-4">
{label !== '' && (
<label
htmlFor={label}
className="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
>
{label}
</label>
)}
{preview && preview}
<input
id={label}
className="block w-full mb-5 text-xs text-gray-900 border border-gray-300 rounded-lg cursor-pointer bg-gray-50 dark:text-gray-400 focus:outline-none dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400"
type="file"
onChange={onChange}
ref={inputRef}
/>
{error && (
<p className="mb-2 text-sm text-red-600 dark:text-red-500">
{error}
</p>
)}
</div>
)
}

@ -1,22 +1,50 @@
import React from "react";
import Input from "./Input";
import React from 'react'
import Input from './Input'
export default function FormInput({ type, name, onChange, value, label, className, error, autoComplete, autoFocus, placeholder, disabled, readOnly}) {
export default function FormInput({
type,
name,
onChange,
value,
label,
className,
error,
autoComplete,
autoFocus,
placeholder,
disabled,
readOnly,
onKeyDownCapture,
leftItem,
formClassName,
}) {
return (
<div className={className}>
<label htmlFor="first_name" className="block mb-2 text-sm font-medium text-gray-900 dark:text-white">{label}</label>
<Input
type={type}
name={name}
onChange={onChange}
value={value}
error={error}
autoComplete={autoComplete}
autoFocus={autoFocus}
placeholder={placeholder}
disabled={disabled}
readOnly={readOnly}
/>
<label
htmlFor={name}
className="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
>
{label}
</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
{leftItem}
</div>
<Input
className={formClassName}
type={type}
name={name}
onChange={onChange}
value={value}
error={error}
autoComplete={autoComplete}
autoFocus={autoFocus}
placeholder={placeholder}
disabled={disabled}
readOnly={readOnly}
onKeyDownCapture={onKeyDownCapture}
/>
</div>
</div>
)
}
}

@ -0,0 +1,58 @@
import React from 'react'
import Input from './Input'
export default function FormInputWith({
type,
name,
onChange,
value,
label,
className,
error,
autoComplete,
autoFocus,
placeholder,
disabled,
readOnly,
onKeyDownCapture,
leftItem,
formClassName,
}) {
return (
<div className={className}>
<label
htmlFor={name}
className="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
>
{label}
</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
{leftItem}
</div>
<input
type={type}
className={`mb-2 bg-gray-50 border text-gray-900 text-sm rounded-lg block w-full p-2.5 dark:bg-gray-700 dark:placeholder-gray-400 dark:text-white ${
error
? 'border-red-500 dark:border-red-500 focus:ring-red-500 focus:border-red-500 dark:focus:ring-red-500 dark:focus:border-red-500'
: 'border-gray-300 dark:border-gray-600 focus:ring-blue-500 focus:border-blue-500 dark:focus:ring-blue-500 dark:focus:border-blue-500'
} ${formClassName}`}
onChange={onChange}
name={name}
value={value}
autoComplete={autoComplete ? 'on' : 'off'}
autoFocus={autoFocus}
placeholder={placeholder}
disabled={disabled}
readOnly={readOnly}
onKeyDownCapture={onKeyDownCapture}
/>
</div>
{error && (
<p className="mb-2 text-sm text-red-600 dark:text-red-500">
{error}
</p>
)}
</div>
)
}

@ -1,24 +1,43 @@
import React from 'react'
export default function Input({ type = 'text', name, onChange, value, error = "", autoComplete = false, autoFocus = false, placeholder , className ,disabled, readOnly}) {
export default function Input({
type = 'text',
name,
onChange,
value,
error = '',
autoComplete = false,
autoFocus = false,
placeholder,
className,
disabled,
readOnly,
onKeyDownCapture = null,
}) {
return (
<>
<input
<input
type={type}
className={`mb-2 bg-gray-50 border text-gray-900 text-sm rounded-lg block w-full p-2.5 dark:bg-gray-700 dark:placeholder-gray-400 dark:text-white ${error ? "border-red-500 dark:border-red-500 focus:ring-red-500 focus:border-red-500 dark:focus:ring-red-500 dark:focus:border-red-500" : "border-gray-300 dark:border-gray-600 focus:ring-blue-500 focus:border-blue-500 dark:focus:ring-blue-500 dark:focus:border-blue-500"} ${className}`}
onChange={onChange}
className={`mb-2 bg-gray-50 border text-gray-900 text-sm rounded-lg block w-full p-2.5 dark:bg-gray-700 dark:placeholder-gray-400 dark:text-white ${
error
? 'border-red-500 dark:border-red-500 focus:ring-red-500 focus:border-red-500 dark:focus:ring-red-500 dark:focus:border-red-500'
: 'border-gray-300 dark:border-gray-600 focus:ring-blue-500 focus:border-blue-500 dark:focus:ring-blue-500 dark:focus:border-blue-500'
} ${className}`}
onChange={onChange}
name={name}
value={value}
autoComplete={autoComplete ? "on" : "off"}
autoComplete={autoComplete ? 'on' : 'off'}
autoFocus={autoFocus}
placeholder={placeholder}
disabled={disabled}
readOnly={readOnly}
onKeyDownCapture={onKeyDownCapture}
/>
{error && (
<p className="mb-2 text-sm text-red-600 dark:text-red-500">{error}</p>
<p className="mb-2 text-sm text-red-600 dark:text-red-500">
{error}
</p>
)}
</>
)
}
}

@ -1,6 +1,15 @@
import React from "react";
import React from 'react'
export default function TextArea({ label = '', value, name, onChange, rows, error, readOnly }) {
export default function TextArea({
label = '',
value,
name,
onChange,
rows,
error,
readOnly,
placeholder,
}) {
return (
<>
{label !== '' && (
@ -8,17 +17,24 @@ export default function TextArea({ label = '', value, name, onChange, rows, erro
{label}
</label>
)}
<textarea
<textarea
rows={rows}
className={`block p-2.5 w-full text-sm text-gray-900 bg-gray-50 rounded-lg border dark:bg-gray-700 dark:placeholder-gray-400 dark:text-white mb-2 ${error ? "border-red-500 dark:border-red-500 focus:ring-red-500 focus:border-red-500 dark:focus:ring-red-500 dark:focus:border-red-500" : "border-gray-300 dark:border-gray-600 focus:ring-blue-500 focus:border-blue-500 dark:focus:ring-blue-500 dark:focus:border-blue-500"}`}
className={`block p-2.5 w-full text-sm text-gray-900 bg-gray-50 rounded-lg border dark:bg-gray-700 dark:placeholder-gray-400 dark:text-white mb-2 ${
error
? 'border-red-500 dark:border-red-500 focus:ring-red-500 focus:border-red-500 dark:focus:ring-red-500 dark:focus:border-red-500'
: 'border-gray-300 dark:border-gray-600 focus:ring-blue-500 focus:border-blue-500 dark:focus:ring-blue-500 dark:focus:border-blue-500'
}`}
name={name}
value={value}
onChange={onChange}
readOnly={readOnly}
placeholder={placeholder}
/>
{error && (
<p className="mb-2 text-sm text-red-600 dark:text-red-500">{error}</p>
<p className="mb-2 text-sm text-red-600 dark:text-red-500">
{error}
</p>
)}
</>
)
}
}

@ -1,6 +1,6 @@
import React from 'react'
import ApplicationLogo from '@/Components/Defaults/ApplicationLogo'
import { Link } from '@inertiajs/react'
import { Link, router, usePage } from '@inertiajs/react'
import { HiHome, HiOutlineHome, HiOutlineUserCircle } from 'react-icons/hi'
import {
HiArrowPathRoundedSquare,
@ -8,13 +8,33 @@ import {
} from 'react-icons/hi2'
export default function CustomerLayout({ children }) {
const {
props: {
auth: { user },
},
} = usePage()
const handleOnClick = (r) => {
router.get(route(r))
}
const isActive = (r) => {
if (route().current(r)) {
return 'text-blue-700 h-8 w-8'
}
return 'text-gray-600 h-8 w-8'
}
return (
<div className="min-h-screen flex flex-col sm:justify-center items-center">
<div className="flex flex-col w-full min-h-screen max-w-md bg-white shadow-md">
<div className="mb-10">{children}</div>
<div className="sticky bottom-0 flex flex-row justify-evenly w-full max-w-md bg-gray-50">
<div className="py-2 px-10 hover:bg-blue-200">
<HiOutlineHome className="text-blue-700 h-8 w-8" />
<div
className="py-2 px-10 hover:bg-blue-200"
onClick={() => handleOnClick('home.index')}
>
<HiOutlineHome className={isActive('home.index')} />
</div>
<div className="py-2 px-10 hover:bg-blue-200 flex flex-row">
<HiOutlineShoppingCart className="text-gray-600 h-8 w-8" />
@ -27,9 +47,27 @@ export default function CustomerLayout({ children }) {
<div className="py-2 px-10 hover:bg-blue-200">
<HiArrowPathRoundedSquare className="text-gray-600 h-8 w-8" />
</div>
<div className="py-2 px-10 hover:bg-blue-200">
<HiOutlineUserCircle className="text-gray-600 h-8 w-8" />
</div>
{user !== null ? (
<div
className="py-2 px-10 hover:bg-blue-200"
onClick={() =>
handleOnClick('customer.profile.index')
}
>
<HiOutlineUserCircle
className={isActive('customer.profile.*')}
/>
</div>
) : (
<div
className="py-2 px-10 hover:bg-blue-200"
onClick={() => handleOnClick('customer.login')}
>
<HiOutlineUserCircle
className={isActive('customer.login')}
/>
</div>
)}
</div>
</div>
</div>

@ -0,0 +1,104 @@
import React from 'react'
import { Head, useForm, Link } from '@inertiajs/react'
import { FcGoogle } from 'react-icons/fc'
import CustomerLayout from '@/Layouts/CustomerLayout'
import FormInput from '@/Components/FormInput'
import Button from '@/Components/Button'
import Alert from '@/Components/Alert'
export default function Index({ app_name, flash }) {
const { data, setData, post, processing, errors } = useForm({
username: '',
password: '',
})
const handleOnChange = (event) => {
setData(
event.target.name,
event.target.type === 'checkbox'
? event.target.checked
? 1
: 0
: event.target.value
)
}
const handleSubmit = () => {
post(route('customer.login'))
}
const handleKeyDown = (e) => {
if (e.code === 'Enter') {
handleSubmit()
}
}
const handleLoginWithGoogle = () => {
window.location = route('customer.login.google')
}
return (
<CustomerLayout>
<Head title="Login" />
<div className="flex flex-col justify-center min-h-screen">
<div className="m-4 border shadow-md p-6 pt-4">
<div className="text-2xl font-bold mb-4">Sign in</div>
<Alert type={flash.message.type}>
<span className="font-semibold">
{flash.message.message}
</span>
</Alert>
<div className="w-full">
<FormInput
placeholder="username"
name="username"
value={data.username}
onChange={handleOnChange}
error={errors.username}
onKeyDownCapture={(e) => handleKeyDown(e)}
/>
</div>
<div className="w-full">
<FormInput
type="password"
placeholder="password"
name="password"
value={data.password}
onChange={handleOnChange}
error={errors.username}
onKeyDownCapture={(e) => handleKeyDown(e)}
/>
</div>
<div className="w-full flex flex-row justify-between">
<Button processing={processing} onClick={handleSubmit}>
Sign in
</Button>
{/* <a href="#" className="text-sm underline text-blue-600">
forgot password
</a> */}
</div>
<div className="flex flex-row items-center space-x-2 justify-between my-3">
<div className="border-b-2 w-full" />
<div>OR</div>
<div className="border-b-2 w-full" />
</div>
<div className="flex flex-row w-full justify-center">
<div
className="flex flex-row items-center space-x-2 border-2 border-blue-600 px-4 py-2 hover:bg-blue-500 hover:text-white rounded"
onClick={handleLoginWithGoogle}
>
<FcGoogle className="h-6 w-6" />
<div>Sign in with Google</div>
</div>
</div>
<div className="mt-10 w-full text-center text-blue-600 underline">
<Link href={route('customer.register')}>
dont have an account ? register
</Link>
</div>
</div>
</div>
</CustomerLayout>
)
}

@ -0,0 +1,163 @@
import React from 'react'
import { Head, useForm, Link } from '@inertiajs/react'
import { FcGoogle } from 'react-icons/fc'
import CustomerLayout from '@/Layouts/CustomerLayout'
import FormInput from '@/Components/FormInput'
import Button from '@/Components/Button'
import Alert from '@/Components/Alert'
import FormInputWith from '@/Components/FormInputWith'
import TextArea from '@/Components/TextArea'
export default function Index({ app_name, flash }) {
const { data, setData, post, processing, errors } = useForm({
username: '',
password: '',
password_confirmation: '',
fullname: '',
name: '',
address: '',
phone: '',
})
const handleOnChange = (event) => {
setData(
event.target.name,
event.target.type === 'checkbox'
? event.target.checked
? 1
: 0
: event.target.value
)
}
const handleSubmit = () => {
post(route('customer.register'))
}
const handleKeyDown = (e) => {
if (e.code === 'Enter') {
handleSubmit()
}
}
const handleLoginWithGoogle = () => {
window.location = route('customer.login.google')
}
return (
<CustomerLayout>
<Head title="Login" />
<div className="flex flex-col justify-center min-h-screen">
<div className="m-4 border shadow-md p-6 pt-4">
<div className="text-2xl font-bold mb-4">Register</div>
<Alert type={flash.message.type}>
<span className="font-semibold">
{flash.message.message}
</span>
</Alert>
<div className="w-full">
<FormInput
placeholder="nama lengkap"
name="fullname"
value={data.fullname}
onChange={handleOnChange}
error={errors.fullname}
onKeyDownCapture={(e) => handleKeyDown(e)}
/>
</div>
<div className="w-full">
<FormInput
placeholder="nama panggilan"
name="name"
value={data.name}
onChange={handleOnChange}
error={errors.name}
onKeyDownCapture={(e) => handleKeyDown(e)}
/>
</div>
<FormInputWith
type="number"
leftItem={<div className="text-sm">+62</div>}
placeholder="whatsapp"
name="phone"
value={data.phone}
onChange={handleOnChange}
error={errors.phone}
onKeyDownCapture={(e) => handleKeyDown(e)}
formClassName={'pl-10'}
/>
<div className="w-full">
<TextArea
placeholder="alamat lengkap"
name="address"
value={data.address}
onChange={handleOnChange}
error={errors.address}
onKeyDownCapture={(e) => handleKeyDown(e)}
/>
</div>
<div className="w-full">
<FormInput
placeholder="username"
name="username"
value={data.username}
onChange={handleOnChange}
error={errors.username}
onKeyDownCapture={(e) => handleKeyDown(e)}
/>
</div>
<div className="w-full">
<FormInput
type="password"
placeholder="password"
name="password"
value={data.password}
onChange={handleOnChange}
error={errors.password}
onKeyDownCapture={(e) => handleKeyDown(e)}
/>
</div>
<div className="w-full">
<FormInput
type="password"
placeholder="retype password"
name="password_confirmation"
value={data.password_confirmation}
onChange={handleOnChange}
error={errors.password_confirmation}
onKeyDownCapture={(e) => handleKeyDown(e)}
/>
</div>
<div className="w-full flex flex-row justify-between">
<Button processing={processing} onClick={handleSubmit}>
Register
</Button>
{/* <a href="#" className="text-sm underline text-blue-600">
forgot password
</a> */}
</div>
<div className="flex flex-row items-center space-x-2 justify-between my-3">
<div className="border-b-2 w-full" />
<div>OR</div>
<div className="border-b-2 w-full" />
</div>
<div className="flex flex-row w-full justify-center">
<div
className="flex flex-row items-center space-x-2 border-2 border-blue-600 px-4 py-2 hover:bg-blue-500 hover:text-white rounded"
onClick={handleLoginWithGoogle}
>
<FcGoogle className="h-6 w-6" />
<div>Register with Google</div>
</div>
</div>
<div className="mt-10 w-full text-center text-blue-600 underline">
<Link href={route('customer.login')}>
already have account ? login
</Link>
</div>
</div>
</div>
</CustomerLayout>
)
}

@ -0,0 +1,14 @@
import React from 'react'
import { Head } from '@inertiajs/react'
import CustomerLayout from '@/Layouts/CustomerLayout'
export default function Index({ status }) {
return (
<CustomerLayout>
<Head title="Login" />
<div className="flex flex-col min-h-screen">
<div>Login</div>
</div>
</CustomerLayout>
)
}

@ -1,60 +1,45 @@
import React from 'react'
import { Head } from '@inertiajs/react'
import { Head, usePage } from '@inertiajs/react'
import CustomerLayout from '@/Layouts/CustomerLayout'
import { HiOutlineBell } from 'react-icons/hi2'
import { HiOutlineCash } from 'react-icons/hi'
import UserBanner from './UserBanner'
export default function Index({ status }) {
const GuestBanner = () => {
const {
props: { app_name },
} = usePage()
return (
<CustomerLayout>
<Head title="Home" />
<div className="flex flex-col">
{/* user */}
<div className="flex flex-row justify-between items-center px-5 py-6 text-lg bg-blue-600">
<div className="flex flex-col text-white">
<div className="font-bold">Aji</div>
<div className="flex flex-row items-center space-x-1">
<div>083840745543</div>
<div className="text-xs font-semibold px-2 py-1 bg-white text-black rounded-xl">
Gold
</div>
</div>
</div>
<div className="flex flex-row">
<HiOutlineBell className="text-white w-7 h-7" />
<div>
<div className="bg-white text-blue-700 rounded-lg px-1 text-xs -ml-2.5">
1
</div>
</div>
</div>
<div>
{/* user */}
<div className="flex flex-row justify-between items-center px-5 py-6 text-lg bg-blue-600">
<div className="flex flex-col text-white">
<div className="font-bold">Welcome to {app_name}</div>
</div>
{/* saldo */}
<div className="flex flex-row px-5 pb-3 text-base bg-blue-600">
<div className="flex flex-row w-full shadow py-2 px-2 rounded bg-white items-center justify-between">
<div className="flex flex-col">
<div className="text-xs flex flex-row items-center space-x-1 text-gray-400">
<HiOutlineCash />
<div>Saldo</div>
</div>
<div className="font-bold">Rp 10.000</div>
<div className="text-xs flex flex-row items-center space-x-1 text-gray-400">
<div>Coin 10.000</div>
</div>
</div>
<div className="flex flex-col border-l-2 pl-5 pr-5">
<div className="text-xs flex flex-row items-center space-x-1 text-gray-400">
{/* <HiOutlineAwa /> */}
<div>Rewards</div>
</div>
<div className="font-bold">Gold Member</div>
<div className="text-xs flex flex-row items-center space-x-1 text-gray-400">
<div>Limit 100.000</div>
</div>
<div className="flex flex-row">
<HiOutlineBell className="text-white w-7 h-7" />
<div>
<div className="bg-white text-blue-700 rounded-lg px-1 text-xs -ml-2.5">
0
</div>
</div>
</div>
</div>
</div>
)
}
export default function Index({ status }) {
const {
props: {
auth: { user },
},
} = usePage()
return (
<CustomerLayout>
<Head title="Home" />
<div className="flex flex-col">
{user !== null ? <UserBanner user={user} /> : <GuestBanner />}
{/* banner */}
<div className="w-full">

@ -0,0 +1,54 @@
import React from 'react'
import { HiOutlineCash, HiOutlineBell } from 'react-icons/hi'
export default function UserBanner({ user }) {
return (
<div>
{/* user */}
<div className="flex flex-row justify-between items-center px-5 py-6 text-lg bg-blue-600">
<div className="flex flex-col text-white">
<div className="font-bold">{user.name}</div>
<div className="flex flex-row items-center space-x-1">
<div>+62{user.phone}</div>
<div className="text-xs font-semibold px-2 py-1 bg-white text-black rounded-xl">
Gold
</div>
</div>
</div>
<div className="flex flex-row">
<HiOutlineBell className="text-white w-7 h-7" />
<div>
<div className="bg-white text-blue-700 rounded-lg px-1 text-xs -ml-2.5">
1
</div>
</div>
</div>
</div>
{/* saldo */}
<div className="flex flex-row px-5 pb-3 text-base bg-blue-600">
<div className="flex flex-row w-full shadow py-2 px-2 rounded bg-white items-center justify-between">
<div className="flex flex-col">
<div className="text-xs flex flex-row items-center space-x-1 text-gray-400">
<HiOutlineCash />
<div>Saldo</div>
</div>
<div className="font-bold">Rp 10.000</div>
<div className="text-xs flex flex-row items-center space-x-1 text-gray-400">
<div>Coin 10.000</div>
</div>
</div>
<div className="flex flex-col border-l-2 pl-5 pr-5">
<div className="text-xs flex flex-row items-center space-x-1 text-gray-400">
{/* <HiOutlineAwa /> */}
<div>Rewards</div>
</div>
<div className="font-bold">Gold Member</div>
<div className="text-xs flex flex-row items-center space-x-1 text-gray-400">
<div>Limit 100.000</div>
</div>
</div>
</div>
</div>
</div>
)
}

@ -0,0 +1,159 @@
import React from 'react'
import { Head, useForm, Link } from '@inertiajs/react'
import { FcGoogle } from 'react-icons/fc'
import CustomerLayout from '@/Layouts/CustomerLayout'
import FormInput from '@/Components/FormInput'
import Button from '@/Components/Button'
import Alert from '@/Components/Alert'
import FormInputWith from '@/Components/FormInputWith'
import TextArea from '@/Components/TextArea'
import FormFile from '@/Components/FormFile'
export default function Index({ auth: { user }, flash }) {
const { data, setData, post, processing, errors } = useForm({
username: user.username,
password: '',
password_confirmation: '',
fullname: user.fullname,
name: user.name,
address: user.address,
phone: user.phone,
image: null,
image_url: user.image_url,
})
const handleOnChange = (event) => {
setData(
event.target.name,
event.target.type === 'checkbox'
? event.target.checked
? 1
: 0
: event.target.value
)
}
const handleSubmit = () => {
post(route('customer.show'))
}
const handleKeyDown = (e) => {
if (e.code === 'Enter') {
handleSubmit()
}
}
return (
<CustomerLayout>
<Head title="Login" />
<div className="flex flex-col min-h-screen">
<div className="m-4 p-2">
<div className="text-2xl font-bold mb-4">Profile</div>
<Alert type={flash.message.type}>
<span className="font-semibold">
{flash.message.message}
</span>
</Alert>
<div className="w-full">
<FormInput
placeholder="nama lengkap"
name="fullname"
value={data.fullname}
onChange={handleOnChange}
error={errors.fullname}
onKeyDownCapture={(e) => handleKeyDown(e)}
/>
</div>
<div className="w-full">
<FormInput
placeholder="nama panggilan"
name="name"
value={data.name}
onChange={handleOnChange}
error={errors.name}
onKeyDownCapture={(e) => handleKeyDown(e)}
/>
</div>
<FormInputWith
type="number"
leftItem={<div className="text-sm">+62</div>}
placeholder="whatsapp"
name="phone"
value={data.phone}
onChange={handleOnChange}
error={errors.phone}
onKeyDownCapture={(e) => handleKeyDown(e)}
formClassName={'pl-10'}
/>
<div className="w-full">
<TextArea
placeholder="alamat lengkap"
name="address"
value={data.address}
onChange={handleOnChange}
error={errors.address}
onKeyDownCapture={(e) => handleKeyDown(e)}
/>
</div>
<div className="w-full">
<FormInput
placeholder="username"
name="username"
value={data.username}
onChange={handleOnChange}
error={errors.username}
onKeyDownCapture={(e) => handleKeyDown(e)}
/>
</div>
<div className="w-full">
<FormInput
type="password"
placeholder="password , leave blank if unchange"
name="password"
value={data.password}
onChange={handleOnChange}
error={errors.password}
onKeyDownCapture={(e) => handleKeyDown(e)}
/>
</div>
<div className="w-full">
<FormInput
type="password"
placeholder="retype password"
name="password_confirmation"
value={data.password_confirmation}
onChange={handleOnChange}
error={errors.password_confirmation}
onKeyDownCapture={(e) => handleKeyDown(e)}
/>
</div>
<div className="w-full">
<FormFile
label={'Profile Image'}
onChange={(e) =>
setData('image', e.target.files[0])
}
error={errors.image}
preview={
<img
src={`${data.image_url}`}
className="w-40 mb-1"
alt="site logo"
/>
}
/>
</div>
<div className="w-full flex flex-row justify-between">
<Button processing={processing} onClick={handleSubmit}>
Simpan
</Button>
{/* <a href="#" className="text-sm underline text-blue-600">
forgot password
</a> */}
</div>
</div>
</div>
</CustomerLayout>
)
}

@ -0,0 +1,115 @@
import React from 'react'
import { Head, Link, router } from '@inertiajs/react'
import CustomerLayout from '@/Layouts/CustomerLayout'
import UserBanner from '../Index/UserBanner'
import {
HiChevronRight,
HiOutlineUser,
HiOutlineUserCircle,
} from 'react-icons/hi2'
import { HiOutlineBell, HiOutlineCash } from 'react-icons/hi'
import { useModalState } from '@/hooks'
import ModalConfirm from '@/Components/ModalConfirm'
export default function Index({ auth: { user } }) {
const confirmModal = useModalState()
const handleLogoutClick = () => {
confirmModal.toggle()
}
const onLogout = () => {
router.post(route('customer.logout'))
}
return (
<CustomerLayout>
<Head title="Login" />
<div className="flex flex-col min-h-screen">
<div>
{/* user */}
<div className="flex flex-row justify-between items-center px-5 py-6 text-lg bg-blue-600">
<div className="flex flex-row items-center space-x-2">
<HiOutlineUserCircle className="text-white h-14 w-14" />
<div className="flex flex-col text-white">
<div className="font-bold">{user.name}</div>
<div className="flex flex-row items-center space-x-1">
<div>+62{user.phone}</div>
<div className="text-xs font-semibold px-2 py-1 bg-white text-black rounded-xl">
Gold
</div>
</div>
</div>
</div>
<div className="flex flex-row">
<HiOutlineBell className="text-white w-7 h-7" />
<div>
<div className="bg-white text-blue-700 rounded-lg px-1 text-xs -ml-2.5">
1
</div>
</div>
</div>
</div>
{/* saldo */}
<div className="flex flex-row px-5 pb-3 text-base bg-blue-600">
<div className="flex flex-row w-full shadow py-2 px-2 rounded bg-white items-center justify-between">
<div className="flex flex-col">
<div className="text-xs flex flex-row items-center space-x-1 text-gray-400">
<HiOutlineCash />
<div>Saldo</div>
</div>
<div className="font-bold">Rp 10.000</div>
<div className="text-xs flex flex-row items-center space-x-1 text-gray-400">
<div>Coin 10.000</div>
</div>
</div>
<div className="flex flex-col border-l-2 pl-5 pr-5">
<div className="text-xs flex flex-row items-center space-x-1 text-gray-400">
<div>Rewards</div>
</div>
<div className="font-bold">Gold Member</div>
<div className="text-xs flex flex-row items-center space-x-1 text-gray-400">
<div>Limit 100.000</div>
</div>
</div>
</div>
</div>
</div>
<div className="p-4 flex flex-col">
<div className="flex flex-row justify-between items-center px-2 py-4 w-full border-b border-gray-400 hover:bg-gray-100">
<div>Upgrade Member</div>
<HiChevronRight className="h-5 w-5" />
</div>
<div className="flex flex-row justify-between items-center px-2 py-4 w-full border-b border-gray-400 hover:bg-gray-100">
<div>Deposit Saldo</div>
<HiChevronRight className="h-5 w-5" />
</div>
<div className="flex flex-row justify-between items-center px-2 py-4 w-full border-b border-gray-400 hover:bg-gray-100">
<div>Transaksi</div>
<HiChevronRight className="h-5 w-5" />
</div>
<div className="flex flex-row justify-between items-center px-2 py-4 w-full border-b border-gray-400 hover:bg-gray-100">
<div>Notifikasi</div>
<HiChevronRight className="h-5 w-5" />
</div>
<div
className="flex flex-row justify-between items-center px-2 py-4 w-full border-b border-gray-400 hover:bg-gray-100"
onClick={() =>
router.get(route('customer.profile.show'))
}
>
<div>Profile</div>
<HiChevronRight className="h-5 w-5" />
</div>
<div
onClick={() => handleLogoutClick()}
className="flex flex-row justify-between items-center px-2 py-4 w-full border-b border-gray-400 text-red-700 hover:bg-gray-100"
>
Logout
</div>
</div>
</div>
<ModalConfirm modalState={confirmModal} onConfirm={onLogout} />
</CustomerLayout>
)
}

@ -1,6 +1,9 @@
<?php
use App\Http\Controllers\Customer\AuthController;
use App\Http\Controllers\Customer\ProfileController;
use App\Http\Controllers\Customer\HomeController;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Route;
/*
@ -14,8 +17,29 @@ use Illuminate\Support\Facades\Route;
|
*/
Route::middleware(['inertia.customer'])->group(function () {
Route::middleware(['guard_should_customer', 'inertia.customer'])->group(function () {
Route::get('/', [HomeController::class, 'index'])->name('home.index');
Route::middleware('auth:customer')->group(function () {
// profile
Route::get('profile', [ProfileController::class, 'index'])->name('customer.profile.index');
Route::get('profile/update', [ProfileController::class, 'show'])->name('customer.profile.show');
Route::post('profile/update', [ProfileController::class, 'update']);
// logout
Route::post('logout', [AuthController::class, 'destroy'])->name('customer.logout');
});
Route::middleware('guest:customer')->group(function () {
// login
Route::get('/login', [AuthController::class, 'login'])->name('customer.login');
Route::post('/login', [AuthController::class, 'update']);
Route::get('/login/google', [AuthController::class, 'signin_google'])->name('customer.login.google');
Route::get('/login/callback_google', [AuthController::class, 'callback_google'])->name('customer.login.callback_google');
// register
Route::get('/register', [AuthController::class, 'register'])->name('customer.register');
Route::post('/register', [AuthController::class, 'store']);
});
});
require_once 'admin.php';

Loading…
Cancel
Save