This commit is contained in:
Bayu Lukman Yusuf 2026-01-19 14:38:16 +07:00
parent b8bc23ee1f
commit b0eda5e389
15 changed files with 751 additions and 242 deletions

View File

@ -2,16 +2,122 @@
namespace App\Http\Controllers\Auth; namespace App\Http\Controllers\Auth;
use App\Helpers\AutoNumbering;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Customer;
use Exception;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Intervention\Image\Drivers\Imagick\Driver;
use Intervention\Image\ImageManager;
class ProfileController extends Controller class ProfileController extends Controller
{ {
public function index(Request $request) public function index(Request $request)
{ {
if (!auth()->check()) { if (! auth()->check()) {
return redirect()->route('login'); return redirect()->route('login');
} }
return view('account.info'); return view('account.info');
} }
public function update(Request $request)
{
try {
$request->validate([
'name' => 'required|string|max:255',
'birth_date' => 'nullable|date',
'email' => 'required|email|max:255',
'phone' => 'required|string|max:255',
'photo' => 'required|image|mimes:jpg,jpeg,png,webp|max:2048',
]);
$user = auth()->user();
$user->name = $request->name;
$user->email = $request->email;
$user->phone = $request->phone;
// Handle avatar upload
if ($request->hasFile('photo')) {
$ext = $request->file('photo')->extension();
$filename = $request->file('photo')->storeAs("profile", $user->id.".".$ext, "public");
$user->photo = $filename;
}
$user->save();
$customer = $user->customer;
if ($user->customer == null) {
$customer = new Customer;
$autoNumbering = new AutoNumbering([
'type' => 'CUST',
'prefix' => 'CAPP',
'location_id' => 0,
'pad' => 9,
]);
do {
$number = $autoNumbering->getCurrent();
$count = Customer::where('number', $number)->count();
} while ($count > 0);
$customer->number = $number;
$customer->user_id = $user->id;
}
if ($request->name){
$customer->name = $request->name;
}
if ($request->email) {
$customer->email = $request->email;
}
if ($request->phone) {
$customer->phone = $request->phone;
}
if ($request->birth_date != null) {
$customer->date_of_birth = $request->birth_date;
}
$customer->save();
return back()->with('success', 'Profile updated successfully!');
} catch (Exception $e) {
Log::error($e);
return back()->with('error', $e->getMessage());
}
}
public function updatePassword(Request $request)
{
try {
$request->validate([
'current_password' => 'required|string',
'password' => 'required|string|min:8|confirmed',
]);
$user = auth()->user();
// Verify current password
if (!Hash::check($request->current_password, $user->password)) {
return back()->with('error', 'Current password is incorrect.');
}
$user->password = bcrypt($request->password);
$user->save();
return back()->with('success', 'Password updated successfully!');
} catch (Exception $e) {
return back()->with('error', $e->getMessage());
}
}
} }

View File

@ -25,7 +25,7 @@ class User extends Authenticatable
'photo', 'photo',
'fcm_token', 'fcm_token',
'phone', 'phone',
'phone_verified_at' 'phone_verified_at',
]; ];
/** /**
@ -50,4 +50,10 @@ class User extends Authenticatable
'password' => 'hashed', 'password' => 'hashed',
]; ];
} }
public function customer()
{
return $this->hasOne(Customer::class);
}
} }

39
lang/en/account_info.php Normal file
View File

@ -0,0 +1,39 @@
<?php
return [
'page_title' => 'Account - Personal Info',
'personal_info' => 'Personal info',
'basic_info' => 'Basic info',
'edit' => 'Edit',
'name' => 'Name',
'date_of_birth' => 'Date of birth',
'choose_date' => 'Choose date',
'please_enter_first_name' => 'Please enter your first name!',
'save_changes' => 'Save changes',
'close' => 'Close',
'contact' => 'Contact',
'email_address' => 'Email address',
'please_enter_valid_email' => 'Please enter a valid email address!',
'phone_number' => 'Phone number',
'please_enter_phone_number' => 'Please enter your phone number!',
'password' => 'Password',
'current_password' => 'Current password',
'enter_current_password' => 'Enter your current password',
'new_password' => 'New password',
'create_new_password' => 'Create new password',
'delete_account' => 'Delete account',
'delete_account_description' => 'When you delete your account, your public profile will be deactivated immediately. If you change your mind before the 14 days are up, sign in with your email and password, and we\'ll send you a link to reactivate your account.',
'not_set' => 'Not Set',
'verified' => 'Verified',
'date_format' => 'j F Y',
'language' => 'Language',
'select_language' => 'Select language',
'english' => 'English',
'french' => 'Français',
'german' => 'Deutsch',
'italian' => 'Italiano',
'confirm_password' => 'Confirm password',
'confirm_new_password' => 'Confirm new password',
'profile_photo' => 'Profile Photo',
'photo_requirements' => 'Recommended: Square image, at least 200x200px',
];

View File

@ -0,0 +1,17 @@
<?php
return [
'points' => 'Points',
'orders' => 'Orders',
'wishlist' => 'Wishlist',
'payment_methods' => 'Payment methods',
'my_reviews' => 'My reviews',
'manage_account' => 'Manage account',
'personal_info' => 'Personal info',
'addresses' => 'Addresses',
'notifications' => 'Notifications',
'customer_service' => 'Customer service',
'help_center' => 'Help center',
'terms_and_conditions' => 'Terms and conditions',
'log_out' => 'Log out',
];

6
lang/en/locale.php Normal file
View File

@ -0,0 +1,6 @@
<?php
return [
'en' => 'English',
'id' => 'Indonesia',
];

39
lang/id/account_info.php Normal file
View File

@ -0,0 +1,39 @@
<?php
return [
'page_title' => 'Akun - Info Pribadi',
'personal_info' => 'Info Pribadi',
'basic_info' => 'Info Dasar',
'edit' => 'Edit',
'name' => 'Nama',
'date_of_birth' => 'Tanggal Lahir',
'choose_date' => 'Pilih Tanggal',
'please_enter_first_name' => 'Silakan masukkan nama depan Anda!',
'save_changes' => 'Simpan Perubahan',
'close' => 'Tutup',
'contact' => 'Kontak',
'email_address' => 'Alamat Email',
'please_enter_valid_email' => 'Silakan masukkan alamat email yang valid!',
'phone_number' => 'Nomor Telepon',
'please_enter_phone_number' => 'Silakan masukkan nomor telepon Anda!',
'password' => 'Kata Sandi',
'current_password' => 'Kata Sandi Saat Ini',
'enter_current_password' => 'Masukkan kata sandi saat ini',
'new_password' => 'Kata Sandi Baru',
'create_new_password' => 'Buat kata sandi baru',
'delete_account' => 'Hapus Akun',
'delete_account_description' => 'Ketika Anda menghapus akun, profil publik Anda akan dinonaktifkan segera. Jika Anda berubah pikiran sebelum 14 hari berakhir, masuk dengan email dan kata sandi Anda, dan kami akan mengirimkan tautan untuk mengaktifkan kembali akun Anda.',
'not_set' => 'Belum Diatur',
'verified' => 'Terverifikasi',
'date_format' => 'j F Y',
'language' => 'Bahasa',
'select_language' => 'Pilih Bahasa',
'english' => 'Inggris',
'french' => 'Prancis',
'german' => 'Jerman',
'italian' => 'Italia',
'confirm_password' => 'Konfirmasi Kata Sandi',
'confirm_new_password' => 'Konfirmasi Kata Sandi Baru',
'profile_photo' => 'Foto Profil',
'photo_requirements' => 'Direkomendasikan: Gambar persegi, minimal 200x200px',
];

View File

@ -0,0 +1,17 @@
<?php
return [
'points' => 'Poin',
'orders' => 'Pesanan',
'wishlist' => 'Daftar Keinginan',
'payment_methods' => 'Metode Pembayaran',
'my_reviews' => 'Ulasan Saya',
'manage_account' => 'Kelola Akun',
'personal_info' => 'Info Pribadi',
'addresses' => 'Alamat',
'notifications' => 'Notifikasi',
'customer_service' => 'Layanan Pelanggan',
'help_center' => 'Pusat Bantuan',
'terms_and_conditions' => 'Syarat dan Ketentuan',
'log_out' => 'Keluar',
];

6
lang/id/locale.php Normal file
View File

@ -0,0 +1,6 @@
<?php
return [
'en' => 'English',
'id' => 'Indonesia',
];

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

@ -1,76 +1,128 @@
@extends('layouts.account', ['title' => 'Account - Personal Info']) @extends('layouts.account', ['title' => __('account_info.page_title')])
@section('content') @section('content')
<!-- Personal info content --> <!-- Personal info content -->
<div class="col-lg-9"> <div class="col-lg-9">
<div class="ps-lg-3 ps-xl-0"> <div class="ps-lg-3 ps-xl-0">
<!-- Page title --> <!-- Page title -->
<h1 class="h2 mb-1 mb-sm-2">Personal info</h1> <h1 class="h2 mb-1 mb-sm-2">{{ __('account_info.personal_info') }}</h1>
<!-- Basic info --> <!-- Basic info -->
<div class="border-bottom py-4"> <div class="border-bottom py-4">
<div class="nav flex-nowrap align-items-center justify-content-between pb-1 mb-3"> <div class="nav flex-nowrap align-items-center justify-content-between pb-1 mb-3">
<h2 class="h6 mb-0">Basic info</h2> <h2 class="h6 mb-0">{{ __('account_info.basic_info') }}</h2>
<a aria-controls="basicInfoPreview basicInfoEdit" aria-expanded="false" <a aria-controls="basicInfoPreview basicInfoEdit" aria-expanded="false"
class="nav-link hiding-collapse-toggle text-decoration-underline p-0 collapsed" class="nav-link hiding-collapse-toggle text-decoration-underline p-0 collapsed"
data-bs-toggle="collapse" href=".basic-info">Edit</a> data-bs-toggle="collapse" href=".basic-info">{{ __('account_info.edit') }}</a>
</div> </div>
<div class="collapse basic-info show" id="basicInfoPreview"> <div class="collapse basic-info @if(session('error') || $errors->has('name') || $errors->has('email') || $errors->has('phone') || $errors->has('birth_date') || $errors->has('photo')) @else show @endif" id="basicInfoPreview">
<ul class="list-unstyled fs-sm m-0"> <ul class="list-unstyled fs-sm m-0">
<li>{{ auth()->user()->name }}</li> <li>{{ auth()->user()->name }}</li>
<li>{{ auth()->user()->email }}</li> <li>{{ auth()->user()->email }}</li>
{{-- <li>English</li> --}} {{-- <li>English</li> --}}
</ul> </ul>
</div> </div>
<div class="collapse basic-info" id="basicInfoEdit">
<form class="row g-3 g-sm-4 needs-validation" novalidate=""> <div class="collapse basic-info @if(session('error') || $errors->has('name') || $errors->has('email') || $errors->has('phone') || $errors->has('birth_date') || $errors->has('photo')) show @endif" id="basicInfoEdit">
<form class="row g-3 g-sm-4 needs-validation" novalidate="" enctype="multipart/form-data" method="POST" action="{{ route('profile.update') }}">
@csrf
@if(session('error'))
<div class="alert alert-danger" role="alert">
{{ session('error') }}
</div>
@endif
<div class="col-sm-12">
<label class="form-label">{{ __('account_info.profile_photo') }}</label>
<div class="d-flex align-items-center gap-4">
<div class="position-relative">
<div class="avatar avatar-lg" style="aspect-ratio: 1/1; overflow: hidden; max-width: 300px; width: 100%; height: auto;">
@if(auth()->user()->photo)
<img src="{{ asset('storage/' . auth()->user()->photo) }}" alt="{{ auth()->user()->name }}" class="avatar-img rounded-circle" style="width: 100%; height: 100%; object-fit: cover;" onerror="this.src='{{ asset('img/photo-placeholder.png') }}'">
@else
<img src="{{ asset('img/photo-placeholder.png') }}" alt="{{ auth()->user()->name }}" class="avatar-img rounded-circle" style="width: 100%; height: 100%; object-fit: cover;">
@endif
</div>
<label for="photo-upload" class="btn btn-sm btn-primary position-absolute bottom-0 end-0 rounded-circle" style="width: 32px; height: 32px; padding: 0;">
<i class="ci-camera" style="font-size: 14px;"></i>
</label>
<input type="file" id="photo-upload" name="photo" class="d-none" accept="image/*" onchange="previewAvatar(event)">
</div>
<div class="flex-grow-1">
<p class="fs-sm text-muted mb-1">{{ __('account_info.photo_requirements') }}</p>
<p class="fs-sm text-muted mb-0">JPG, PNG or GIF (Max 2MB)</p>
</div>
</div>
</div>
<div class="col-sm-6"> <div class="col-sm-6">
<label class="form-label" for="fn">Name</label> <label class="form-label" for="fn">{{ __('account_info.name') }}</label>
<div class="position-relative"> <div class="position-relative">
<input class="form-control" id="fn" required="" type="text" <input class="form-control" id="fn" name="name" required="" type="text"
value="{{ auth()->user()->name }}" /> value="{{ auth()->user()->name }}" />
<div class="invalid-feedback">Please enter your first name!</div> <div class="invalid-feedback">{{ __('account_info.please_enter_first_name') }}</div>
</div> </div>
</div> </div>
<div class="col-sm-6"> <div class="col-sm-6">
<label class="form-label" for="birthdate">Date of birth</label> <label class="form-label" for="email">{{ __('account_info.email_address') }}</label>
<div class="position-relative">
<input class="form-control" id="email" name="email" required="" type="email"
value="{{ auth()->user()->email ?? __('account_info.not_set') }}" />
<div class="invalid-feedback">{{ __('account_info.please_enter_valid_email') }}</div>
</div>
</div>
<div class="col-sm-6">
<label class="form-label" for="phone">{{ __('account_info.phone_number') }}</label>
<div class="position-relative">
<input class="form-control"
id="phone" name="phone" placeholder="" required="" type="text"
value="{{ auth()->user()->phone ?? __('account_info.not_set') }}" />
<div class="invalid-feedback">{{ __('account_info.please_enter_phone_number') }}</div>
</div>
</div>
<div class="col-sm-6">
<label class="form-label" for="birthdate">{{ __('account_info.date_of_birth') }}</label>
<div class="position-relative"> <div class="position-relative">
<input class="form-control form-icon-end" <input class="form-control form-icon-end"
name="birth_date"
data-datepicker='{ data-datepicker='{
"dateFormat": "j F Y", "dateFormat": "Y-m-d",
"defaultDate": "{{ auth()->user()->birth_date != null ? date('j F Y', strtotime(auth()->user()->birth_date)) : '' }}" "defaultDate": "{{ auth()->user()->customer->date_of_birth != null ? date('Y-m-d', strtotime(auth()->user()->customer->date_of_birth)) : '' }}"
}' }'
id="birthdate" placeholder="Choose date" type="text" /> placeholder="{{ __('account_info.choose_date') }}" type="text" />
<i class="ci-calendar position-absolute top-50 end-0 translate-middle-y me-3"></i> <i class="ci-calendar position-absolute top-50 end-0 translate-middle-y me-3"></i>
</div> </div>
</div> </div>
{{-- <div class="col-sm-6"> {{-- <div class="col-sm-6">
<label class="form-label">Language</label> <label class="form-label">{{ __('account_info.language') }}</label>
<select class="form-select" <select class="form-select"
data-select='{ data-select='{
"placeholderValue": "Select language", "placeholderValue": "{{ __('account_info.select_language') }}",
"choices": [ "choices": [
{ {
"value": "", "value": "",
"label": "Select language", "label": "{{ __('account_info.select_language') }}",
"placeholder": true "placeholder": true
}, },
{ {
"value": "English", "value": "English",
"label": "&lt;div class=\"d-flex align-items-center\"&gt;&lt;img src=\"/img/flags/en-us.png\" class=\"flex-shrink-0 me-2\" width=\"20\" alt=\"English\"&gt; English&lt;/div&gt;", "label": "&lt;div class=\"d-flex align-items-center\"&gt;&lt;img src=\"/img/flags/en-us.png\" class=\"flex-shrink-0 me-2\" width=\"20\" alt=\"English\"&gt; {{ __('account_info.english') }}&lt;/div&gt;",
"selected": true "selected": true
}, },
{ {
"value": "Français", "value": "Français",
"label": "&lt;div class=\"d-flex align-items-center\"&gt;&lt;img src=\"/img/flags/fr.png\" class=\"flex-shrink-0 me-2\" width=\"20\" alt=\"Français\"&gt; Français&lt;/div&gt;" "label": "&lt;div class=\"d-flex align-items-center\"&gt;&lt;img src=\"/img/flags/fr.png\" class=\"flex-shrink-0 me-2\" width=\"20\" alt=\"Français\"&gt; {{ __('account_info.french') }}&lt;/div&gt;"
}, },
{ {
"value": "Deutsch", "value": "Deutsch",
"label": "&lt;div class=\"d-flex align-items-center\"&gt;&lt;img src=\"/img/flags/de.png\" class=\"flex-shrink-0 me-2\" width=\"20\" alt=\"Deutsch\"&gt; Deutsch&lt;/div&gt;" "label": "&lt;div class=\"d-flex align-items-center\"&gt;&lt;img src=\"/img/flags/de.png\" class=\"flex-shrink-0 me-2\" width=\"20\" alt=\"Deutsch\"&gt; {{ __('account_info.german') }}&lt;/div&gt;"
}, },
{ {
"value": "Italiano", "value": "Italiano",
"label": "&lt;div class=\"d-flex align-items-center\"&gt;&lt;img src=\"/img/flags/it.png\" class=\"flex-shrink-0 me-2\" width=\"20\" alt=\"Italiano\"&gt; Italiano&lt;/div&gt;" "label": "&lt;div class=\"d-flex align-items-center\"&gt;&lt;img src=\"/img/flags/it.png\" class=\"flex-shrink-0 me-2\" width=\"20\" alt=\"Italiano\"&gt; {{ __('account_info.italian') }}&lt;/div&gt;"
} }
] ]
}' }'
@ -78,93 +130,101 @@
</div> --}} </div> --}}
<div class="col-12"> <div class="col-12">
<div class="d-flex gap-3 pt-2 pt-sm-0"> <div class="d-flex gap-3 pt-2 pt-sm-0">
<button class="btn btn-primary" type="submit">Save changes</button> <button class="btn btn-primary" type="submit">{{ __('account_info.save_changes') }}</button>
<button aria-controls="basicInfoPreview basicInfoEdit" aria-expanded="true" <button aria-controls="basicInfoPreview basicInfoEdit" aria-expanded="true"
class="btn btn-secondary" data-bs-target=".basic-info" data-bs-toggle="collapse" class="btn btn-secondary" data-bs-target=".basic-info" data-bs-toggle="collapse"
type="button">Close</button> type="button">{{ __('account_info.close') }}</button>
</div> </div>
</div> </div>
</form> </form>
</div> </div>
</div> </div>
<!-- Contact --> <!-- Contact -->
<div class="border-bottom py-4"> {{-- <div class="border-bottom py-4">
<div class="nav flex-nowrap align-items-center justify-content-between pb-1 mb-3"> <div class="nav flex-nowrap align-items-center justify-content-between pb-1 mb-3">
<div class="d-flex align-items-center gap-3 me-4"> <div class="d-flex align-items-center gap-3 me-4">
<h2 class="h6 mb-0">Contact</h2> <h2 class="h6 mb-0">{{ __('account_info.contact') }}</h2>
</div> </div>
<a aria-controls="contactInfoPreview contactInfoEdit" aria-expanded="false" <a aria-controls="contactInfoPreview contactInfoEdit" aria-expanded="false"
class="nav-link hiding-collapse-toggle text-decoration-underline p-0 collapsed" class="nav-link hiding-collapse-toggle text-decoration-underline p-0 collapsed"
data-bs-toggle="collapse" href=".contact-info">Edit</a> data-bs-toggle="collapse" href=".contact-info">{{ __('account_info.edit') }}</a>
</div> </div>
<div class="collapse contact-info show" id="contactInfoPreview"> <div class="collapse contact-info show" id="contactInfoPreview">
<ul class="list-unstyled fs-sm m-0"> <ul class="list-unstyled fs-sm m-0">
<li class="mb-1">{{ auth()->user()->email }}</li> <li class="mb-1">{{ auth()->user()->email }}</li>
<li>{{ auth()->user()->phone ?? 'Not Set' }}<span class="text-success ms-1">Verified</span></li> <li>{{ auth()->user()->phone ?? __('account_info.not_set') }}<span class="text-success ms-1">{{ __('account_info.verified') }}</span></li>
</ul> </ul>
</div> </div>
<div class="collapse contact-info" id="contactInfoEdit"> <div class="collapse contact-info" id="contactInfoEdit">
<form class="row g-3 g-sm-4 needs-validation" novalidate=""> <form class="row g-3 g-sm-4 needs-validation" novalidate="">
<div class="col-sm-6">
<label class="form-label" for="email">Email address</label>
<div class="position-relative">
<input class="form-control" id="email" required="" type="email"
value="{{ auth()->user()->email ?? 'Not Set' }}" />
<div class="invalid-feedback">Please enter a valid email address!</div>
</div>
</div>
<div class="col-sm-6">
<label class="form-label" for="phone">Phone number</label>
<div class="position-relative">
<input class="form-control"
data-input-format='{"numericOnly": true, "delimiters": ["", " "], "blocks": [0, 3, 0, 3, 0,3,0,3,0,3]}'
id="phone" placeholder="" required="" type="text"
value="{{ auth()->user()->phone ?? 'Not Set' }}" />
<div class="invalid-feedback">Please enter your phone number!</div>
</div>
</div>
<div class="col-12"> <div class="col-12">
<div class="d-flex gap-3 pt-2 pt-sm-0"> <div class="d-flex gap-3 pt-2 pt-sm-0">
<button class="btn btn-primary" type="submit">Save changes</button> <button class="btn btn-primary" type="submit">{{ __('account_info.save_changes') }}</button>
<button aria-controls="contactInfoPreview contactInfoEdit" aria-expanded="true" <button aria-controls="contactInfoPreview contactInfoEdit" aria-expanded="true"
class="btn btn-secondary" data-bs-target=".contact-info" data-bs-toggle="collapse" class="btn btn-secondary" data-bs-target=".contact-info" data-bs-toggle="collapse"
type="button">Close</button> type="button">{{ __('account_info.close') }}</button>
</div> </div>
</div> </div>
</form> </form>
</div> </div>
</div> </div> --}}
<!-- Password --> <!-- Password -->
<div class="border-bottom py-4"> {{-- <div class="border-bottom py-4">
<div class="nav flex-nowrap align-items-center justify-content-between pb-1 mb-3"> <div class="nav flex-nowrap align-items-center justify-content-between pb-1 mb-3">
<div class="d-flex align-items-center gap-3 me-4"> <div class="d-flex align-items-center gap-3 me-4">
<h2 class="h6 mb-0">Password</h2> <h2 class="h6 mb-0">{{ __('account_info.password') }}</h2>
</div> </div>
<a aria-controls="passChangePreview passChangeEdit" aria-expanded="false" <a aria-controls="passChangePreview passChangeEdit" aria-expanded="false"
class="nav-link hiding-collapse-toggle text-decoration-underline p-0 collapsed" class="nav-link hiding-collapse-toggle text-decoration-underline p-0 collapsed"
data-bs-toggle="collapse" href=".password-change">Edit</a> data-bs-toggle="collapse" href=".password-change">{{ __('account_info.edit') }}</a>
</div> </div>
<div class="collapse password-change show" id="passChangePreview"> <div class="collapse password-change @if(session('error') || $errors->has('current_password') || $errors->has('password') || $errors->has('password_confirmation')) @else show @endif" id="passChangePreview">
<ul class="list-unstyled fs-sm m-0"> <ul class="list-unstyled fs-sm m-0">
<li>**************</li> <li>**************</li>
</ul> </ul>
</div> </div>
<div class="collapse password-change" id="passChangeEdit"> <div class="collapse password-change @if(session('error') || $errors->has('current_password') || $errors->has('password') || $errors->has('password_confirmation')) show @endif" id="passChangeEdit">
<form class="row g-3 g-sm-4 needs-validation" novalidate=""> <form class="row g-3 g-sm-4 needs-validation" novalidate="" method="POST" action="{{ route('profile.password.update') }}">
@csrf
@method('PUT')
@if(session('error'))
<div class="alert alert-danger" role="alert">
{{ session('error') }}
</div>
@endif
@if(session('success'))
<div class="alert alert-success" role="alert">
{{ session('success') }}
</div>
@endif
<div class="col-sm-6"> <div class="col-sm-6">
<label class="form-label" for="current-password">Current password</label> <label class="form-label" for="current-password">{{ __('account_info.current_password') }}</label>
<div class="password-toggle"> <div class="password-toggle">
<input class="form-control" id="current-password" <input class="form-control" id="current-password" name="current_password"
placeholder="Enter your current password" required="" type="password" /> placeholder="{{ __('account_info.enter_current_password') }}" required="" type="password" />
<label aria-label="Show/hide password" class="password-toggle-button"> <label aria-label="Show/hide password" class="password-toggle-button">
<input class="btn-check" type="checkbox" /> <input class="btn-check" type="checkbox" />
</label> </label>
</div> </div>
</div> </div>
<div class="col-sm-6"> <div class="col-sm-6">
<label class="form-label" for="new-password">New password</label> <label class="form-label" for="new-password">{{ __('account_info.new_password') }}</label>
<div class="password-toggle"> <div class="password-toggle">
<input class="form-control" id="new-password" placeholder="Create new password" <input class="form-control" id="new-password" name="password" placeholder="{{ __('account_info.create_new_password') }}"
required="" type="password" />
<label aria-label="Show/hide password" class="password-toggle-button">
<input class="btn-check" type="checkbox" />
</label>
</div>
</div>
<div class="col-sm-6">
<label class="form-label" for="confirm-password">{{ __('account_info.confirm_password') }}</label>
<div class="password-toggle">
<input class="form-control" id="confirm-password" name="password_confirmation" placeholder="{{ __('account_info.confirm_new_password') }}"
required="" type="password" /> required="" type="password" />
<label aria-label="Show/hide password" class="password-toggle-button"> <label aria-label="Show/hide password" class="password-toggle-button">
<input class="btn-check" type="checkbox" /> <input class="btn-check" type="checkbox" />
@ -173,26 +233,53 @@
</div> </div>
<div class="col-12"> <div class="col-12">
<div class="d-flex gap-3 pt-2 pt-sm-0"> <div class="d-flex gap-3 pt-2 pt-sm-0">
<button class="btn btn-primary" type="submit">Save changes</button> <button class="btn btn-primary" type="submit">{{ __('account_info.save_changes') }}</button>
<button aria-controls="passChangePreview passChangeEdit" aria-expanded="true" <button aria-controls="passChangePreview passChangeEdit" aria-expanded="true"
class="btn btn-secondary" data-bs-target=".password-change" data-bs-toggle="collapse" class="btn btn-secondary" data-bs-target=".password-change" data-bs-toggle="collapse"
type="button">Close</button> type="button">{{ __('account_info.close') }}</button>
</div> </div>
</div> </div>
</form> </form>
</div> </div>
</div> </div> --}}
<!-- Delete account --> <!-- Delete account -->
<div class="pt-3 mt-2 mt-sm-3"> {{-- <div class="pt-3 mt-2 mt-sm-3">
<h2 class="h6">Delete account</h2> <h2 class="h6">{{ __('account_info.delete_account') }}</h2>
<p class="fs-sm">When you delete your account, your public profile will be deactivated <p class="fs-sm">{{ __('account_info.delete_account_description') }}</p>
immediately. If you change your mind before the 14 days are up, sign in with your email and <a class="text-danger fs-sm fw-medium" href="#!">{{ __('account_info.delete_account') }}</a>
password, and we'll send you a link to reactivate your account.</p> </div> --}}
<a class="text-danger fs-sm fw-medium" href="#!">Delete account</a>
</div>
</div> </div>
</div> </div>
@endsection @endsection
@section('scripts') @section('scripts')
<script>
function previewAvatar(event) {
const file = event.target.files[0];
if (file) {
// Check file size (2MB limit)
if (file.size > 2 * 1024 * 1024) {
alert('File size must be less than 2MB');
event.target.value = '';
return;
}
// Check file type
if (!file.type.match('image.*')) {
alert('Please select an image file');
event.target.value = '';
return;
}
const reader = new FileReader();
reader.onload = function(e) {
const avatarImg = document.querySelector('.avatar-img');
if (avatarImg) {
avatarImg.src = e.target.result;
}
}
reader.readAsDataURL(file);
}
}
</script>
@endsection @endsection

View File

@ -1,8 +1,8 @@
<!-- Language selector for sidebar/offcanvas --> <!-- Language selector for sidebar/offcanvas -->
<div class="dropdown nav"> <div class="dropdown nav">
<a class="nav-link dropdown-toggle py-1 px-0" href="#" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" aria-label="{{ __('languages.Country select') }}: {{ app()->getLocale() === 'en' ? 'USA' : 'Indonesia' }}"> <a class="nav-link dropdown-toggle py-1 px-0" href="#" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" aria-label="{{ __('locale.' . app()->getLocale()) }}">
<div class="ratio ratio-1x1" style="width: 20px"> <div class="ratio ratio-1x1" style="width: 20px">
<img src="/img/flags/{{ app()->getLocale() === 'en' ? 'en-us' : 'id' }}.png" alt="{{ app()->getLocale() === 'en' ? 'USA' : 'Indonesia' }}"> <img src="/img/flags/{{ app()->getLocale() === 'en' ? 'en-us' : 'id' }}.png" alt="{{ __('locale.' . app()->getLocale()) }}">
</div> </div>
</a> </a>
<ul class="dropdown-menu fs-sm" style="--cz-dropdown-spacer: .5rem"> <ul class="dropdown-menu fs-sm" style="--cz-dropdown-spacer: .5rem">

View File

@ -1,8 +1,8 @@
<!-- Country selector visible on screens > 768px wide (md breakpoint) --> <!-- Country selector visible on screens > 768px wide (md breakpoint) -->
<div class="dropdown d-none d-md-block nav"> <div class="dropdown d-none d-md-block nav">
<a class="nav-link dropdown-toggle py-1 px-0" href="#" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" aria-label="{{ __('messages.Country select') }}: {{ app()->getLocale() === 'en' ? 'USA' : 'Indonesia' }}"> <a class="nav-link dropdown-toggle py-1 px-0" href="#" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" aria-label="{{ __('locale.' . session('locale')) }}">
<div class="ratio ratio-1x1" style="width: 20px"> <div class="ratio ratio-1x1" style="width: 20px">
<img src="/img/flags/{{ session('locale') === 'en' ? 'en-us' : 'id' }}.png" alt="{{ session('locale') === 'en' ? 'USA' : 'Indonesia' }}"> <img src="/img/flags/{{ session('locale') === 'en' ? 'en-us' : 'id' }}.png" alt="{{ __('locale.' . session('locale')) }}">
</div> </div>
</a> </a>
<ul class="dropdown-menu fs-sm" style="--cz-dropdown-spacer: .5rem"> <ul class="dropdown-menu fs-sm" style="--cz-dropdown-spacer: .5rem">

View File

@ -5,8 +5,16 @@
<!-- Header --> <!-- Header -->
<div class="offcanvas-header d-lg-block py-3 p-lg-0"> <div class="offcanvas-header d-lg-block py-3 p-lg-0">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<div class="h5 d-flex justify-content-center align-items-center flex-shrink-0 text-primary bg-primary-subtle lh-1 rounded-circle mb-0" <div class="flex-shrink-0 ms-3">
style="width: 3rem; height: 3rem">{{ auth()->user()->name ? substr(auth()->user()->name, 0, 1) : 'S' }}</div> @if(auth()->user()->photo)
<img src="{{ asset('storage/' . auth()->user()->photo) }}" alt="{{ auth()->user()->name }}"
class="rounded-circle" style="width: 3rem; height: 3rem; object-fit: cover;"
onerror="this.src='{{ asset('img/photo-placeholder.png') }}'">
@else
<div class="h5 d-flex justify-content-center align-items-center text-primary bg-primary-subtle lh-1 rounded-circle mb-0"
style="width: 3rem; height: 3rem;">{{ auth()->user()->name ? substr(auth()->user()->name, 0, 1) : 'S' }}</div>
@endif
</div>
<div class="min-w-0 ps-3"> <div class="min-w-0 ps-3">
<h5 class="h6 mb-1">{{ auth()->user()->name }}</h5> <h5 class="h6 mb-1">{{ auth()->user()->name }}</h5>
<div class="nav flex-nowrap text-nowrap min-w-0"> <div class="nav flex-nowrap text-nowrap min-w-0">
@ -22,7 +30,7 @@
</svg> --}} </svg> --}}
<span <span
class="animate-target me-1">{{ number_format(auth()->user()->customer->point ?? 0, 0, ',', '.') }} class="animate-target me-1">{{ number_format(auth()->user()->customer->point ?? 0, 0, ',', '.') }}
Points</span> {{ __('account_sidebar.points') }}</span>
{{-- <span class="text-body fw-normal text-truncate">available</span> --}} {{-- <span class="text-body fw-normal text-truncate">available</span> --}}
</a> </a>
</div> </div>
@ -38,61 +46,61 @@
<a class="list-group-item list-group-item-action d-flex align-items-center" <a class="list-group-item list-group-item-action d-flex align-items-center"
href="{{ route('second', ['account', 'orders']) }}"> href="{{ route('second', ['account', 'orders']) }}">
<i class="ci-shopping-bag fs-base opacity-75 me-2"></i> <i class="ci-shopping-bag fs-base opacity-75 me-2"></i>
Orders {{ __('account_sidebar.orders') }}
<span class="badge bg-primary rounded-pill ms-auto">1</span> <span class="badge bg-primary rounded-pill ms-auto">1</span>
</a> </a>
<a class="list-group-item list-group-item-action d-flex align-items-center" <a class="list-group-item list-group-item-action d-flex align-items-center"
href="{{ route('second', ['account', 'wishlist']) }}"> href="{{ route('second', ['account', 'wishlist']) }}">
<i class="ci-heart fs-base opacity-75 me-2"></i> <i class="ci-heart fs-base opacity-75 me-2"></i>
Wishlist {{ __('account_sidebar.wishlist') }}
</a> </a>
<a class="list-group-item list-group-item-action d-flex align-items-center" <a class="list-group-item list-group-item-action d-flex align-items-center"
href="{{ route('second', ['account', 'payment']) }}"> href="{{ route('second', ['account', 'payment']) }}">
<i class="ci-credit-card fs-base opacity-75 me-2"></i> <i class="ci-credit-card fs-base opacity-75 me-2"></i>
Payment methods {{ __('account_sidebar.payment_methods') }}
</a> </a>
<a class="list-group-item list-group-item-action d-flex align-items-center" <a class="list-group-item list-group-item-action d-flex align-items-center"
href="{{ route('second', ['account', 'reviews']) }}"> href="{{ route('second', ['account', 'reviews']) }}">
<i class="ci-star fs-base opacity-75 me-2"></i> <i class="ci-star fs-base opacity-75 me-2"></i>
My reviews {{ __('account_sidebar.my_reviews') }}
</a> </a>
</nav> </nav>
<h6 class="pt-4 ps-2 ms-1">Manage account</h6> <h6 class="pt-4 ps-2 ms-1">{{ __('account_sidebar.manage_account') }}</h6>
<nav class="list-group list-group-borderless"> <nav class="list-group list-group-borderless">
<a class="list-group-item list-group-item-action d-flex align-items-center" <a class="list-group-item list-group-item-action d-flex align-items-center"
href="{{ route('second', ['account', 'info']) }}"> href="{{ route('second', ['account', 'info']) }}">
<i class="ci-user fs-base opacity-75 me-2"></i> <i class="ci-user fs-base opacity-75 me-2"></i>
Personal info {{ __('account_sidebar.personal_info') }}
</a> </a>
<a class="list-group-item list-group-item-action d-flex align-items-center" <a class="list-group-item list-group-item-action d-flex align-items-center"
href="{{ route('second', ['account', 'addresses']) }}"> href="{{ route('second', ['account', 'addresses']) }}">
<i class="ci-map-pin fs-base opacity-75 me-2"></i> <i class="ci-map-pin fs-base opacity-75 me-2"></i>
Addresses {{ __('account_sidebar.addresses') }}
</a> </a>
<a class="list-group-item list-group-item-action d-flex align-items-center" <a class="list-group-item list-group-item-action d-flex align-items-center"
href="{{ route('second', ['account', 'notifications']) }}"> href="{{ route('second', ['account', 'notifications']) }}">
<i class="ci-bell fs-base opacity-75 mt-1 me-2"></i> <i class="ci-bell fs-base opacity-75 mt-1 me-2"></i>
Notifications {{ __('account_sidebar.notifications') }}
</a> </a>
</nav> </nav>
<h6 class="pt-4 ps-2 ms-1">Customer service</h6> <h6 class="pt-4 ps-2 ms-1">{{ __('account_sidebar.customer_service') }}</h6>
<nav class="list-group list-group-borderless"> <nav class="list-group list-group-borderless">
<a class="list-group-item list-group-item-action d-flex align-items-center" <a class="list-group-item list-group-item-action d-flex align-items-center"
href="{{ route('second', ['help', 'topics-v1']) }}"> href="{{ route('second', ['help', 'topics-v1']) }}">
<i class="ci-help-circle fs-base opacity-75 me-2"></i> <i class="ci-help-circle fs-base opacity-75 me-2"></i>
Help center {{ __('account_sidebar.help_center') }}
</a> </a>
<a class="list-group-item list-group-item-action d-flex align-items-center" <a class="list-group-item list-group-item-action d-flex align-items-center"
href="{{ route('any', 'terms-and-conditions') }}"> href="{{ route('any', 'terms-and-conditions') }}">
<i class="ci-info fs-base opacity-75 me-2"></i> <i class="ci-info fs-base opacity-75 me-2"></i>
Terms and conditions {{ __('account_sidebar.terms_and_conditions') }}
</a> </a>
</nav> </nav>
<nav class="list-group list-group-borderless pt-3"> <nav class="list-group list-group-borderless pt-3">
<a class="list-group-item list-group-item-action d-flex align-items-center" <a class="list-group-item list-group-item-action d-flex align-items-center"
href="{{ route('second', ['account', 'signin']) }}"> href="{{ route('second', ['account', 'signin']) }}">
<i class="ci-log-out fs-base opacity-75 me-2"></i> <i class="ci-log-out fs-base opacity-75 me-2"></i>
Log out {{ __('account_sidebar.log_out') }}
</a> </a>
</nav> </nav>
</div> </div>

File diff suppressed because it is too large Load Diff

View File

@ -69,4 +69,5 @@ Route::group(['prefix' => '/login/google'], function () {
}); });
Route::get('/profile', [ProfileController::class, 'index'])->name('profile'); Route::get('/profile', [ProfileController::class, 'index'])->name('profile');
Route::post('/profile', [ProfileController::class, 'update'])->name('profile.update');
Route::put('/profile/password', [ProfileController::class, 'updatePassword'])->name('profile.password.update');