├── .editorconfig
├── .env.example
├── .gitattributes
├── .gitignore
├── README.md
├── app
├── Http
│ ├── Controllers
│ │ ├── Apps
│ │ │ ├── CategoryController.php
│ │ │ ├── CustomerController.php
│ │ │ ├── ProductController.php
│ │ │ ├── SaleController.php
│ │ │ └── TransactionController.php
│ │ ├── Auth
│ │ │ ├── AuthenticatedSessionController.php
│ │ │ ├── ConfirmablePasswordController.php
│ │ │ ├── EmailVerificationNotificationController.php
│ │ │ ├── EmailVerificationPromptController.php
│ │ │ ├── NewPasswordController.php
│ │ │ ├── PasswordController.php
│ │ │ ├── PasswordResetLinkController.php
│ │ │ ├── RegisteredUserController.php
│ │ │ └── VerifyEmailController.php
│ │ ├── Controller.php
│ │ ├── PermissionController.php
│ │ ├── ProfileController.php
│ │ ├── RoleController.php
│ │ └── UserController.php
│ ├── Middleware
│ │ └── HandleInertiaRequests.php
│ └── Requests
│ │ ├── Auth
│ │ └── LoginRequest.php
│ │ └── ProfileUpdateRequest.php
├── Models
│ ├── Cart.php
│ ├── Category.php
│ ├── Customer.php
│ ├── Product.php
│ ├── Profit.php
│ ├── Transaction.php
│ ├── TransactionDetail.php
│ └── User.php
└── Providers
│ └── AppServiceProvider.php
├── artisan
├── bootstrap
├── app.php
├── cache
│ └── .gitignore
└── providers.php
├── composer.json
├── composer.lock
├── config
├── app.php
├── auth.php
├── cache.php
├── database.php
├── filesystems.php
├── logging.php
├── mail.php
├── permission.php
├── queue.php
├── services.php
└── session.php
├── database
├── .gitignore
├── factories
│ └── UserFactory.php
├── migrations
│ ├── 0001_01_01_000000_create_users_table.php
│ ├── 0001_01_01_000001_create_cache_table.php
│ ├── 0001_01_01_000002_create_jobs_table.php
│ ├── 2024_06_13_082620_create_permission_tables.php
│ ├── 2024_06_13_091315_add_avatar_field_to_users_table.php
│ ├── 2024_06_13_125039_create_customers_table.php
│ ├── 2024_06_13_130507_create_categories_table.php
│ ├── 2024_06_13_131744_create_products_table.php
│ ├── 2024_06_13_132800_create_transactions_table.php
│ ├── 2024_06_13_133940_create_transaction_details_table.php
│ ├── 2024_06_13_133948_create_carts_table.php
│ └── 2024_06_13_133955_create_profits_table.php
└── seeders
│ ├── DatabaseSeeder.php
│ ├── PermissionSeeder.php
│ ├── RoleSeeder.php
│ └── UserSeeder.php
├── jsconfig.json
├── package-lock.json
├── package.json
├── phpunit.xml
├── postcss.config.js
├── public
├── .htaccess
├── assets
│ ├── background
│ │ ├── Hero-Banner.png
│ │ ├── alqowy.svg
│ │ └── benefit_illustration.png
│ ├── icon
│ │ ├── 3dcube.svg
│ │ ├── Group 7-1.svg
│ │ ├── Group 7-2.svg
│ │ ├── Group 7.svg
│ │ ├── Logo-1.svg
│ │ ├── Logo.svg
│ │ ├── Web Development 1-1.svg
│ │ ├── Web Development 1-2.svg
│ │ ├── Web Development 1-3.svg
│ │ ├── Web Development 1-4.svg
│ │ ├── Web Development 1.svg
│ │ ├── add.svg
│ │ ├── arrow-right.svg
│ │ ├── avatar-group.png
│ │ ├── award-outline.svg
│ │ ├── award.svg
│ │ ├── book.svg
│ │ ├── brifecase-tick.svg
│ │ ├── crown.svg
│ │ ├── logo-52.svg
│ │ ├── logo-54.svg
│ │ ├── logo-55.svg
│ │ ├── medal-star.svg
│ │ ├── note-add.svg
│ │ ├── note-favorite.svg
│ │ ├── play-circle.svg
│ │ ├── profile-2user.svg
│ │ ├── star.svg
│ │ ├── tick-circle.svg
│ │ └── video-play.svg
│ ├── logo
│ │ ├── logo-black.svg
│ │ └── logo.svg
│ ├── photo
│ │ ├── photo1.png
│ │ ├── photo2.png
│ │ ├── photo3.png
│ │ ├── photo4.png
│ │ └── photo5.png
│ └── thumbnail
│ │ ├── image-1.png
│ │ ├── image-2.png
│ │ ├── image-3.png
│ │ ├── image.png
│ │ ├── thumbnail-1.png
│ │ ├── thumbnail-2.png
│ │ └── thumbnail-3.png
├── favicon.ico
├── index.php
└── robots.txt
├── resources
├── css
│ └── app.css
├── js
│ ├── Components
│ │ ├── ApplicationLogo.jsx
│ │ ├── Checkbox.jsx
│ │ ├── DangerButton.jsx
│ │ ├── Dashboard
│ │ │ ├── AuthDropdown.jsx
│ │ │ ├── Barcode.jsx
│ │ │ ├── Button.jsx
│ │ │ ├── Card.jsx
│ │ │ ├── Checkbox.jsx
│ │ │ ├── Header.jsx
│ │ │ ├── Input.jsx
│ │ │ ├── InputSelect.jsx
│ │ │ ├── LinkItem.jsx
│ │ │ ├── LinkItemDropdown.jsx
│ │ │ ├── ListBox.jsx
│ │ │ ├── Modal.jsx
│ │ │ ├── Navbar.jsx
│ │ │ ├── Notification.jsx
│ │ │ ├── Pagination.jsx
│ │ │ ├── Search.jsx
│ │ │ ├── Sidebar.jsx
│ │ │ ├── Table.jsx
│ │ │ ├── TextArea.jsx
│ │ │ └── Widget.jsx
│ │ ├── Dropdown.jsx
│ │ ├── InputError.jsx
│ │ ├── InputLabel.jsx
│ │ ├── Modal.jsx
│ │ ├── NavLink.jsx
│ │ ├── PrimaryButton.jsx
│ │ ├── ResponsiveNavLink.jsx
│ │ ├── SecondaryButton.jsx
│ │ └── TextInput.jsx
│ ├── Context
│ │ └── ThemeSwitcherContext.jsx
│ ├── Layouts
│ │ ├── AuthenticatedLayout.jsx
│ │ ├── DashboardLayout.jsx
│ │ └── GuestLayout.jsx
│ ├── Pages
│ │ ├── Auth
│ │ │ ├── ConfirmPassword.jsx
│ │ │ ├── ForgotPassword.jsx
│ │ │ ├── Login.jsx
│ │ │ ├── Register.jsx
│ │ │ ├── ResetPassword.jsx
│ │ │ └── VerifyEmail.jsx
│ │ ├── Dashboard.jsx
│ │ ├── Dashboard
│ │ │ ├── Categories
│ │ │ │ ├── Create.jsx
│ │ │ │ ├── Edit.jsx
│ │ │ │ └── Index.jsx
│ │ │ ├── Customers
│ │ │ │ ├── Create.jsx
│ │ │ │ ├── Edit.jsx
│ │ │ │ └── Index.jsx
│ │ │ ├── Index.jsx
│ │ │ ├── Permissions
│ │ │ │ └── Index.jsx
│ │ │ ├── Products
│ │ │ │ ├── Create.jsx
│ │ │ │ ├── Edit.jsx
│ │ │ │ └── Index.jsx
│ │ │ ├── Roles
│ │ │ │ └── Index.jsx
│ │ │ ├── Transactions
│ │ │ │ ├── Index.jsx
│ │ │ │ └── Print.jsx
│ │ │ └── Users
│ │ │ │ ├── Create.jsx
│ │ │ │ ├── Edit.jsx
│ │ │ │ └── Index.jsx
│ │ ├── Profile
│ │ │ ├── Edit.jsx
│ │ │ └── Partials
│ │ │ │ ├── DeleteUserForm.jsx
│ │ │ │ ├── UpdatePasswordForm.jsx
│ │ │ │ └── UpdateProfileInformationForm.jsx
│ │ └── Welcome.jsx
│ ├── Utils
│ │ ├── Menu.jsx
│ │ └── Permission.jsx
│ ├── app.jsx
│ └── bootstrap.js
└── views
│ └── app.blade.php
├── routes
├── auth.php
├── console.php
└── web.php
├── storage
├── app
│ ├── .gitignore
│ └── public
│ │ └── .gitignore
├── framework
│ ├── .gitignore
│ ├── cache
│ │ ├── .gitignore
│ │ └── data
│ │ │ └── .gitignore
│ ├── sessions
│ │ └── .gitignore
│ ├── testing
│ │ └── .gitignore
│ └── views
│ │ └── .gitignore
└── logs
│ └── .gitignore
├── tailwind.config.js
├── tests
├── Feature
│ ├── Auth
│ │ ├── AuthenticationTest.php
│ │ ├── EmailVerificationTest.php
│ │ ├── PasswordConfirmationTest.php
│ │ ├── PasswordResetTest.php
│ │ ├── PasswordUpdateTest.php
│ │ └── RegistrationTest.php
│ ├── ExampleTest.php
│ └── ProfileTest.php
├── TestCase.php
└── Unit
│ └── ExampleTest.php
└── vite.config.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | indent_size = 4
7 | indent_style = space
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
14 | [*.{yml,yaml}]
15 | indent_size = 2
16 |
17 | [docker-compose.yml]
18 | indent_size = 4
19 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | APP_NAME=Laravel
2 | APP_ENV=local
3 | APP_KEY=
4 | APP_DEBUG=true
5 | APP_TIMEZONE=UTC
6 | APP_URL=http://localhost
7 |
8 | APP_LOCALE=en
9 | APP_FALLBACK_LOCALE=en
10 | APP_FAKER_LOCALE=en_US
11 |
12 | APP_MAINTENANCE_DRIVER=file
13 | APP_MAINTENANCE_STORE=database
14 |
15 | BCRYPT_ROUNDS=12
16 |
17 | LOG_CHANNEL=stack
18 | LOG_STACK=single
19 | LOG_DEPRECATIONS_CHANNEL=null
20 | LOG_LEVEL=debug
21 |
22 | DB_CONNECTION=mysql
23 | DB_HOST=127.0.0.1
24 | DB_PORT=3306
25 | DB_DATABASE=point_of_sales
26 | DB_USERNAME=root
27 | DB_PASSWORD=
28 |
29 | SESSION_DRIVER=database
30 | SESSION_LIFETIME=120
31 | SESSION_ENCRYPT=false
32 | SESSION_PATH=/
33 | SESSION_DOMAIN=null
34 |
35 | BROADCAST_CONNECTION=log
36 | FILESYSTEM_DISK=local
37 | QUEUE_CONNECTION=database
38 |
39 | CACHE_STORE=database
40 | CACHE_PREFIX=
41 |
42 | MEMCACHED_HOST=127.0.0.1
43 |
44 | REDIS_CLIENT=phpredis
45 | REDIS_HOST=127.0.0.1
46 | REDIS_PASSWORD=null
47 | REDIS_PORT=6379
48 |
49 | MAIL_MAILER=log
50 | MAIL_HOST=127.0.0.1
51 | MAIL_PORT=2525
52 | MAIL_USERNAME=null
53 | MAIL_PASSWORD=null
54 | MAIL_ENCRYPTION=null
55 | MAIL_FROM_ADDRESS="hello@example.com"
56 | MAIL_FROM_NAME="${APP_NAME}"
57 |
58 | AWS_ACCESS_KEY_ID=
59 | AWS_SECRET_ACCESS_KEY=
60 | AWS_DEFAULT_REGION=us-east-1
61 | AWS_BUCKET=
62 | AWS_USE_PATH_STYLE_ENDPOINT=false
63 |
64 | VITE_APP_NAME="${APP_NAME}"
65 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=lf
2 |
3 | *.blade.php diff=html
4 | *.css diff=css
5 | *.html diff=html
6 | *.md diff=markdown
7 | *.php diff=php
8 |
9 | /.github export-ignore
10 | CHANGELOG.md export-ignore
11 | .styleci.yml export-ignore
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.phpunit.cache
2 | /node_modules
3 | /public/build
4 | /public/hot
5 | /public/storage
6 | /storage/*.key
7 | /vendor
8 | .env
9 | .env.backup
10 | .env.production
11 | .phpactor.json
12 | .phpunit.result.cache
13 | Homestead.json
14 | Homestead.yaml
15 | auth.json
16 | npm-debug.log
17 | yarn-error.log
18 | /.fleet
19 | /.idea
20 | /.vscode
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Aplikasi Kasir (Point of Sales)
2 | Aplikasi ini dapat digunakan untuk melakukan pencatatan transaksi jual beli pada sebuah warung atau toko. Secara template, aplikasi ini menggunakan resource dari https://github.com/Raf-Taufiqurrahman/RILT-Starter dengan beberapa modifikasi yang saya lakukan terhadap komponen-komponen untuk mendukung aplikasi kasir.
3 |
4 | ## Tech Stack
5 |
6 | - Laravel 11.x
7 | - Inertia
8 | - React
9 | - TailwindCSS
10 | - MySQL
11 | ## Authors
12 |
13 | - [Arya Dwi Putra](https://www.github.com/aryadwiputra)
14 | - [Rafi Taufiqurrahman](https://github.com/Raf-Taufiqurrahman)
15 |
16 |
17 | ## 📌 Fitur
18 |
19 | | No | Nama | Status |
20 | |-----|------------------------------------------------------------|------|
21 | | 1 | Authentikasi Admin. |Done|
22 | | 2 | Manajemen Pengguna. |Done|
23 | | 3 | Manajemen Hak Akses Pengguna. | Done|
24 | | 4 | Manajemen Role Pengguna |Done|
25 | | 5 | Manajemen Kategori. |Done|
26 | | 6 | Manajemen Produk. |Done|
27 | | 7 | Manajemen Pelanggan. |Done|
28 | | 8 | Print Invoice. |Done|
29 | | 9 | Laporan Penjualan. |On progress|
30 | | 10 | Laporan Keuntungan. |On progress|
31 | | 11 | Riwayat Order. |On progress|
32 | | 12 | Chart/Grafik Pendapatan. |On progress|
33 |
34 | ------------
35 | ## 💻 Panduan Instalasi Project
36 |
37 | 1. **Clone Repository**
38 | ```bash
39 | git clone https://github.com/aryadwiputra/point-of-sales
40 | ```
41 | 2. **Buka terminal, lalu ketik**
42 | ```
43 | cd point-of-sales
44 | composer install
45 | npm install
46 | cp .env.example .env
47 | php artisan key:generate
48 | ```
49 |
50 | 3. **Buka ```.env``` lalu ubah baris berikut sesuaikan dengan databasemu yang ingin dipakai**
51 | ```
52 | DB_PORT=3306
53 | DB_DATABASE=laravel
54 | DB_USERNAME=root
55 | DB_PASSWORD=
56 | ```
57 |
58 | 3. **Jalankan bash**
59 | ```bash
60 | php artisan config:cache
61 | php artisan storage:link
62 | php artisan route:clear
63 | ```
64 |
65 | 4. **Jalankan migrations dan seeders**
66 | ```
67 | php artisan migrate --seed
68 | ```
69 | 5. **Jalankan nodejs**
70 | ```
71 | npm run dev
72 | ```
73 |
74 | 5. **Jalankan website**
75 | ```bash
76 | php artisan serve
77 | ```
78 |
79 | ## Jika ada pertanyaan silahkan hubungi saya di email :
80 |
81 | ```
82 | aryaadwptr@gmail.com
83 | ```
84 |
85 | ## Request Fitur Baru dan Pelaporan Bug
86 |
87 | Anda dapat meminta fitur baru maupun melaporkan bug melalui menu **issues** yang sudah disediakan oleh GitHub (lihat menu di atas), posting issues baru dan kita akan berdiskusi disana.
88 |
89 | ## Berkontribusi
90 |
91 | Siapapun dapat berkontribusi pada proyek ini mulai dari pemrograman, pembuakan buku manual, sampai dengan mengenalkan produk ini kepada masyarakat Indonesia agar mengurangi kesenjangan pendidikan teknologi dengan cara membuat postingan issue di repository ini.
92 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Apps/CustomerController.php:
--------------------------------------------------------------------------------
1 | search, function ($customers) {
21 | $customers = $customers->where('name', 'like', '%' . request()->search . '%');
22 | })->latest()->paginate(5);
23 |
24 | //return inertia
25 | return Inertia::render('Dashboard/Customers/Index', [
26 | 'customers' => $customers,
27 | ]);
28 | }
29 |
30 | /**
31 | * Show the form for creating a new resource.
32 | *
33 | * @return \Illuminate\Http\Response
34 | */
35 | public function create()
36 | {
37 | return Inertia::render('Dashboard/Customers/Create');
38 | }
39 |
40 | /**
41 | * Store a newly created resource in storage.
42 | *
43 | * @param \Illuminate\Http\Request $request
44 | * @return \Illuminate\Http\Response
45 | */
46 | public function store(Request $request)
47 | {
48 | /**
49 | * validate
50 | */
51 | $request->validate([
52 | 'name' => 'required',
53 | 'no_telp' => 'required|unique:customers',
54 | 'address' => 'required',
55 | ]);
56 |
57 | //create customer
58 | Customer::create([
59 | 'name' => $request->name,
60 | 'no_telp' => $request->no_telp,
61 | 'address' => $request->address,
62 | ]);
63 |
64 | //redirect
65 | return to_route('customers.index');
66 | }
67 |
68 | /**
69 | * Show the form for editing the specified resource.
70 | *
71 | * @param int $id
72 | * @return \Illuminate\Http\Response
73 | */
74 | public function edit(Customer $customer)
75 | {
76 | return Inertia::render('Dashboard/Customers/Edit', [
77 | 'customer' => $customer,
78 | ]);
79 | }
80 |
81 | /**
82 | * Update the specified resource in storage.
83 | *
84 | * @param \Illuminate\Http\Request $request
85 | * @param int $id
86 | * @return \Illuminate\Http\Response
87 | */
88 | public function update(Request $request, Customer $customer)
89 | {
90 | /**
91 | * validate
92 | */
93 | $request->validate([
94 | 'name' => 'required',
95 | 'no_telp' => 'required|unique:customers,no_telp,' . $customer->id,
96 | 'address' => 'required',
97 | ]);
98 |
99 | //update customer
100 | $customer->update([
101 | 'name' => $request->name,
102 | 'no_telp' => $request->no_telp,
103 | 'address' => $request->address,
104 | ]);
105 |
106 | //redirect
107 | return to_route('customers.index');
108 | }
109 |
110 | /**
111 | * Remove the specified resource from storage.
112 | *
113 | * @param int $id
114 | * @return \Illuminate\Http\Response
115 | */
116 | public function destroy($id)
117 | {
118 | //find customer by ID
119 | $customer = Customer::findOrFail($id);
120 |
121 | //delete customer
122 | $customer->delete();
123 |
124 | //redirect
125 | return back();
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Apps/SaleController.php:
--------------------------------------------------------------------------------
1 | Route::has('password.request'),
23 | 'status' => session('status'),
24 | ]);
25 | }
26 |
27 | /**
28 | * Handle an incoming authentication request.
29 | */
30 | public function store(LoginRequest $request): RedirectResponse
31 | {
32 | $request->authenticate();
33 |
34 | $request->session()->regenerate();
35 |
36 | return redirect()->intended(route('dashboard', absolute: false));
37 | }
38 |
39 | /**
40 | * Destroy an authenticated session.
41 | */
42 | public function destroy(Request $request): RedirectResponse
43 | {
44 | Auth::guard('web')->logout();
45 |
46 | $request->session()->invalidate();
47 |
48 | $request->session()->regenerateToken();
49 |
50 | return redirect('/');
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/ConfirmablePasswordController.php:
--------------------------------------------------------------------------------
1 | validate([
29 | 'email' => $request->user()->email,
30 | 'password' => $request->password,
31 | ])) {
32 | throw ValidationException::withMessages([
33 | 'password' => __('auth.password'),
34 | ]);
35 | }
36 |
37 | $request->session()->put('auth.password_confirmed_at', time());
38 |
39 | return redirect()->intended(route('dashboard', absolute: false));
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/EmailVerificationNotificationController.php:
--------------------------------------------------------------------------------
1 | user()->hasVerifiedEmail()) {
17 | return redirect()->intended(route('dashboard', absolute: false));
18 | }
19 |
20 | $request->user()->sendEmailVerificationNotification();
21 |
22 | return back()->with('status', 'verification-link-sent');
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/EmailVerificationPromptController.php:
--------------------------------------------------------------------------------
1 | user()->hasVerifiedEmail()
19 | ? redirect()->intended(route('dashboard', absolute: false))
20 | : Inertia::render('Auth/VerifyEmail', ['status' => session('status')]);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/NewPasswordController.php:
--------------------------------------------------------------------------------
1 | $request->email,
26 | 'token' => $request->route('token'),
27 | ]);
28 | }
29 |
30 | /**
31 | * Handle an incoming new password request.
32 | *
33 | * @throws \Illuminate\Validation\ValidationException
34 | */
35 | public function store(Request $request): RedirectResponse
36 | {
37 | $request->validate([
38 | 'token' => 'required',
39 | 'email' => 'required|email',
40 | 'password' => ['required', 'confirmed', Rules\Password::defaults()],
41 | ]);
42 |
43 | // Here we will attempt to reset the user's password. If it is successful we
44 | // will update the password on an actual user model and persist it to the
45 | // database. Otherwise we will parse the error and return the response.
46 | $status = Password::reset(
47 | $request->only('email', 'password', 'password_confirmation', 'token'),
48 | function ($user) use ($request) {
49 | $user->forceFill([
50 | 'password' => Hash::make($request->password),
51 | 'remember_token' => Str::random(60),
52 | ])->save();
53 |
54 | event(new PasswordReset($user));
55 | }
56 | );
57 |
58 | // If the password was successfully reset, we will redirect the user back to
59 | // the application's home authenticated view. If there is an error we can
60 | // redirect them back to where they came from with their error message.
61 | if ($status == Password::PASSWORD_RESET) {
62 | return redirect()->route('login')->with('status', __($status));
63 | }
64 |
65 | throw ValidationException::withMessages([
66 | 'email' => [trans($status)],
67 | ]);
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/PasswordController.php:
--------------------------------------------------------------------------------
1 | validate([
19 | 'current_password' => ['required', 'current_password'],
20 | 'password' => ['required', Password::defaults(), 'confirmed'],
21 | ]);
22 |
23 | $request->user()->update([
24 | 'password' => Hash::make($validated['password']),
25 | ]);
26 |
27 | return back();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/PasswordResetLinkController.php:
--------------------------------------------------------------------------------
1 | session('status'),
22 | ]);
23 | }
24 |
25 | /**
26 | * Handle an incoming password reset link request.
27 | *
28 | * @throws \Illuminate\Validation\ValidationException
29 | */
30 | public function store(Request $request): RedirectResponse
31 | {
32 | $request->validate([
33 | 'email' => 'required|email',
34 | ]);
35 |
36 | // We will send the password reset link to this user. Once we have attempted
37 | // to send the link, we will examine the response then see the message we
38 | // need to show to the user. Finally, we'll send out a proper response.
39 | $status = Password::sendResetLink(
40 | $request->only('email')
41 | );
42 |
43 | if ($status == Password::RESET_LINK_SENT) {
44 | return back()->with('status', __($status));
45 | }
46 |
47 | throw ValidationException::withMessages([
48 | 'email' => [trans($status)],
49 | ]);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/RegisteredUserController.php:
--------------------------------------------------------------------------------
1 | validate([
34 | 'name' => 'required|string|max:255',
35 | 'email' => 'required|string|lowercase|email|max:255|unique:'.User::class,
36 | 'password' => ['required', 'confirmed', Rules\Password::defaults()],
37 | ]);
38 |
39 | $user = User::create([
40 | 'name' => $request->name,
41 | 'email' => $request->email,
42 | 'password' => Hash::make($request->password),
43 | ]);
44 |
45 | event(new Registered($user));
46 |
47 | Auth::login($user);
48 |
49 | return redirect(route('dashboard', absolute: false));
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/VerifyEmailController.php:
--------------------------------------------------------------------------------
1 | user()->hasVerifiedEmail()) {
18 | return redirect()->intended(route('dashboard', absolute: false).'?verified=1');
19 | }
20 |
21 | if ($request->user()->markEmailAsVerified()) {
22 | event(new Verified($request->user()));
23 | }
24 |
25 | return redirect()->intended(route('dashboard', absolute: false).'?verified=1');
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Controller.php:
--------------------------------------------------------------------------------
1 | when(request()->search, fn($query) => $query->where('name', 'like', '%' . request()->search . '%'))
18 | ->select('id', 'name')
19 | ->latest()
20 | ->paginate(7)
21 | ->withQueryString();
22 |
23 | // render view
24 | return Inertia::render('Dashboard/Permissions/Index', [
25 | 'permissions' => $permissions
26 | ]);
27 | }
28 |
29 | /**
30 | * Show the form for creating a new resource.
31 | */
32 | public function create()
33 | {
34 | //
35 | }
36 |
37 | /**
38 | * Store a newly created resource in storage.
39 | */
40 | public function store(Request $request)
41 | {
42 | //
43 | }
44 |
45 | /**
46 | * Display the specified resource.
47 | */
48 | public function show(string $id)
49 | {
50 | //
51 | }
52 |
53 | /**
54 | * Show the form for editing the specified resource.
55 | */
56 | public function edit(string $id)
57 | {
58 | //
59 | }
60 |
61 | /**
62 | * Update the specified resource in storage.
63 | */
64 | public function update(Request $request, string $id)
65 | {
66 | //
67 | }
68 |
69 | /**
70 | * Remove the specified resource from storage.
71 | */
72 | public function destroy(string $id)
73 | {
74 | //
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/app/Http/Controllers/ProfileController.php:
--------------------------------------------------------------------------------
1 | $request->user() instanceof MustVerifyEmail,
23 | 'status' => session('status'),
24 | ]);
25 | }
26 |
27 | /**
28 | * Update the user's profile information.
29 | */
30 | public function update(ProfileUpdateRequest $request): RedirectResponse
31 | {
32 | $request->user()->fill($request->validated());
33 |
34 | if ($request->user()->isDirty('email')) {
35 | $request->user()->email_verified_at = null;
36 | }
37 |
38 | $request->user()->save();
39 |
40 | return Redirect::route('profile.edit');
41 | }
42 |
43 | /**
44 | * Delete the user's account.
45 | */
46 | public function destroy(Request $request): RedirectResponse
47 | {
48 | $request->validate([
49 | 'password' => ['required', 'current_password'],
50 | ]);
51 |
52 | $user = $request->user();
53 |
54 | Auth::logout();
55 |
56 | $user->delete();
57 |
58 | $request->session()->invalidate();
59 | $request->session()->regenerateToken();
60 |
61 | return Redirect::to('/');
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/app/Http/Controllers/RoleController.php:
--------------------------------------------------------------------------------
1 | with('permissions')
22 | ->when(request()->search, fn($query) => $query->where('name', 'like', '%' . request()->search . '%'))
23 | ->select('id', 'name')
24 | ->latest()
25 | ->paginate(7)
26 | ->withQueryString();
27 |
28 | // get all permission data
29 | $permissions = Permission::query()
30 | ->select('id', 'name')
31 | ->orderBy('name')
32 | ->get();
33 |
34 | // render view
35 | return Inertia::render('Dashboard/Roles/Index', [
36 | 'roles' => $roles,
37 | 'permissions' => $permissions
38 | ]);
39 | }
40 |
41 | /**
42 | * Store a newly created resource in storage.
43 | */
44 | public function store(RoleRequest $request)
45 | {
46 | // create new role data
47 | $role = Role::create(['name' => $request->name]);
48 |
49 | // give permissions to role
50 | $role->givePermissionTo($request->selectedPermission);
51 |
52 | // render view
53 | return back();
54 | }
55 |
56 | /**
57 | * Update the specified resource in storage.
58 | */
59 | public function update(RoleRequest $request, Role $role)
60 | {
61 | // update role data
62 | $role->update(['name' => $request->name]);
63 |
64 | // sync role permissions
65 | $role->syncPermissions($request->selectedPermission);
66 |
67 | // render view
68 | return back();
69 | }
70 |
71 | /**
72 | * Remove the specified resource from storage.
73 | */
74 | public function destroy(Role $role)
75 | {
76 | // delete role data
77 | $role->delete();
78 |
79 | // render view
80 | return back();
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/app/Http/Controllers/UserController.php:
--------------------------------------------------------------------------------
1 | with('roles')
22 | ->when(request()->search, fn($query) => $query->where('name', 'like', '%' . request()->search . '%'))
23 | ->select('id', 'name', 'avatar', 'email')
24 | ->latest()
25 | ->paginate(7)
26 | ->withQueryString();
27 |
28 | // render view
29 | return Inertia::render('Dashboard/Users/Index', [
30 | 'users' => $users
31 | ]);
32 | }
33 |
34 | /**
35 | * Show the form for creating a new resource.
36 | */
37 | public function create()
38 | {
39 | // get all role data
40 | $roles = Role::query()
41 | ->select('id', 'name')
42 | ->orderBy('name')
43 | ->get();
44 |
45 | // render view
46 | return Inertia::render('Dashboard/Users/Create', [
47 | 'roles' => $roles
48 | ]);
49 | }
50 |
51 | /**
52 | * Store a newly created resource in storage.
53 | */
54 | public function store(UserRequest $request)
55 | {
56 | // create new user data
57 | $user = User::create([
58 | 'name' => $request->name,
59 | 'email' => $request->email,
60 | 'password' => bcrypt($request->password),
61 | ]);
62 |
63 | // assign role to user
64 | $user->assignRole($request->selectedRoles);
65 |
66 | // render view
67 | return to_route('users.index');
68 | }
69 |
70 | /**
71 | * Show the form for editing the specified resource.
72 | */
73 | public function edit(User $user)
74 | {
75 | // get all role data
76 | $roles = Role::query()
77 | ->select('id', 'name')
78 | ->orderBy('name')
79 | ->get();
80 |
81 | // load relationship
82 | $user->load(['roles' => fn($query) => $query->select('id', 'name'), 'roles.permissions' => fn($query) => $query->select('id', 'name')]);
83 |
84 | // render view
85 | return Inertia::render('Dashboard/Users/Edit', [
86 | 'roles' => $roles,
87 | 'user' => $user
88 | ]);
89 | }
90 |
91 | /**
92 | * Update the specified resource in storage.
93 | */
94 | public function update(UserRequest $request, User $user)
95 | {
96 | // check if user send request password
97 | if ($request->password)
98 | // update user data password
99 | $user->update([
100 | 'password' => bcrypt($request->password),
101 | ]);
102 |
103 | // update user data name
104 | $user->update([
105 | 'name' => $request->name,
106 | ]);
107 |
108 | // assign role to user
109 | $user->syncRoles($request->selectedRoles);
110 |
111 | // render view
112 | return to_route('users.index');
113 | }
114 |
115 | /**
116 | * Remove the specified resource from storage.
117 | */
118 | public function destroy($id)
119 | {
120 | $ids = explode(',', $id);
121 |
122 | if (count($ids) > 0)
123 | User::whereIn('id', $ids)->delete();
124 | else
125 | User::findOrFail($id)->delete();
126 |
127 | // render view
128 | return back();
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/app/Http/Middleware/HandleInertiaRequests.php:
--------------------------------------------------------------------------------
1 |
29 | */
30 | public function share(Request $request): array
31 | {
32 | return [
33 | ...parent::share($request),
34 | 'auth' => [
35 | 'user' => $request->user(),
36 | 'permissions' => $request->user() ? $request->user()->getPermissions() : [],
37 | 'super' => $request->user() ? $request->user()->isSuperAdmin() : false,
38 | ],
39 | ];
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/app/Http/Requests/Auth/LoginRequest.php:
--------------------------------------------------------------------------------
1 |
26 | */
27 | public function rules(): array
28 | {
29 | return [
30 | 'email' => ['required', 'string', 'email'],
31 | 'password' => ['required', 'string'],
32 | ];
33 | }
34 |
35 | /**
36 | * Attempt to authenticate the request's credentials.
37 | *
38 | * @throws \Illuminate\Validation\ValidationException
39 | */
40 | public function authenticate(): void
41 | {
42 | $this->ensureIsNotRateLimited();
43 |
44 | if (! Auth::attempt($this->only('email', 'password'), $this->boolean('remember'))) {
45 | RateLimiter::hit($this->throttleKey());
46 |
47 | throw ValidationException::withMessages([
48 | 'email' => trans('auth.failed'),
49 | ]);
50 | }
51 |
52 | RateLimiter::clear($this->throttleKey());
53 | }
54 |
55 | /**
56 | * Ensure the login request is not rate limited.
57 | *
58 | * @throws \Illuminate\Validation\ValidationException
59 | */
60 | public function ensureIsNotRateLimited(): void
61 | {
62 | if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) {
63 | return;
64 | }
65 |
66 | event(new Lockout($this));
67 |
68 | $seconds = RateLimiter::availableIn($this->throttleKey());
69 |
70 | throw ValidationException::withMessages([
71 | 'email' => trans('auth.throttle', [
72 | 'seconds' => $seconds,
73 | 'minutes' => ceil($seconds / 60),
74 | ]),
75 | ]);
76 | }
77 |
78 | /**
79 | * Get the rate limiting throttle key for the request.
80 | */
81 | public function throttleKey(): string
82 | {
83 | return Str::transliterate(Str::lower($this->string('email')).'|'.$this->ip());
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/app/Http/Requests/ProfileUpdateRequest.php:
--------------------------------------------------------------------------------
1 |
15 | */
16 | public function rules(): array
17 | {
18 | return [
19 | 'name' => ['required', 'string', 'max:255'],
20 | 'email' => ['required', 'string', 'lowercase', 'email', 'max:255', Rule::unique(User::class)->ignore($this->user()->id)],
21 | ];
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/Models/Cart.php:
--------------------------------------------------------------------------------
1 | belongsTo(Product::class);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/app/Models/Category.php:
--------------------------------------------------------------------------------
1 | hasMany(Product::class);
30 | }
31 |
32 | /**
33 | * image
34 | *
35 | * @return Attribute
36 | */
37 | protected function image(): Attribute
38 | {
39 | return Attribute::make(
40 | get: fn ($value) => asset('/storage/category/' . $value),
41 | );
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/app/Models/Customer.php:
--------------------------------------------------------------------------------
1 | belongsTo(Category::class);
30 | }
31 |
32 | /**
33 | * image
34 | *
35 | * @return Attribute
36 | */
37 | protected function image(): Attribute
38 | {
39 | return Attribute::make(
40 | get: fn ($value) => asset('/storage/products/' . $value),
41 | );
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/app/Models/Profit.php:
--------------------------------------------------------------------------------
1 | belongsTo(Transaction::class);
31 | }
32 |
33 | /**
34 | * createdAt
35 | *
36 | * @return Attribute
37 | */
38 | protected function createdAt(): Attribute
39 | {
40 | return Attribute::make(
41 | get: fn ($value) => Carbon::parse($value)->format('d-M-Y H:i:s'),
42 | );
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/app/Models/Transaction.php:
--------------------------------------------------------------------------------
1 | hasMany(TransactionDetail::class);
31 | }
32 |
33 | /**
34 | * customer
35 | *
36 | * @return void
37 | */
38 | public function customer()
39 | {
40 | return $this->belongsTo(Customer::class);
41 | }
42 |
43 | /**
44 | * cashier
45 | *
46 | * @return void
47 | */
48 | public function cashier()
49 | {
50 | return $this->belongsTo(User::class, 'cashier_id');
51 | }
52 |
53 | /**
54 | * profits
55 | *
56 | * @return void
57 | */
58 | public function profits()
59 | {
60 | return $this->hasMany(Profit::class);
61 | }
62 |
63 | /**
64 | * createdAt
65 | *
66 | * @return Attribute
67 | */
68 | protected function createdAt(): Attribute
69 | {
70 | return Attribute::make(
71 | get: fn ($value) => Carbon::parse($value)->format('d-M-Y H:i:s'),
72 | );
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/app/Models/TransactionDetail.php:
--------------------------------------------------------------------------------
1 | belongsTo(Transaction::class);
29 | }
30 |
31 | /**
32 | * product
33 | *
34 | * @return void
35 | */
36 | public function product()
37 | {
38 | return $this->belongsTo(Product::class);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/app/Models/User.php:
--------------------------------------------------------------------------------
1 |
19 | */
20 | protected $fillable = [
21 | 'name',
22 | 'email',
23 | 'password',
24 | ];
25 |
26 | /**
27 | * The attributes that should be hidden for serialization.
28 | *
29 | * @var array
30 | */
31 | protected $hidden = [
32 | 'password',
33 | 'remember_token',
34 | ];
35 |
36 | /**
37 | * Get the attributes that should be cast.
38 | *
39 | * @return array
40 | */
41 | protected function casts(): array
42 | {
43 | return [
44 | 'email_verified_at' => 'datetime',
45 | 'password' => 'hashed',
46 | ];
47 | }
48 |
49 | /**
50 | * get all permissions users
51 | */
52 | public function getPermissions()
53 | {
54 | return $this->getAllPermissions()->mapWithKeys(function ($permission) {
55 | return [
56 | $permission['name'] => true
57 | ];
58 | });
59 | }
60 |
61 | /**
62 | * check role isSuperAdmin
63 | */
64 | public function isSuperAdmin()
65 | {
66 | return $this->hasRole('super-admin');
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/app/Providers/AppServiceProvider.php:
--------------------------------------------------------------------------------
1 | handleCommand(new ArgvInput);
14 |
15 | exit($status);
16 |
--------------------------------------------------------------------------------
/bootstrap/app.php:
--------------------------------------------------------------------------------
1 | withRouting(
9 | web: __DIR__.'/../routes/web.php',
10 | commands: __DIR__.'/../routes/console.php',
11 | health: '/up',
12 | )
13 | ->withMiddleware(function (Middleware $middleware) {
14 | $middleware->web(append: [
15 | \App\Http\Middleware\HandleInertiaRequests::class,
16 | \Illuminate\Http\Middleware\AddLinkHeadersForPreloadedAssets::class,
17 | ]);
18 |
19 | //
20 | })
21 | ->withExceptions(function (Exceptions $exceptions) {
22 | //
23 | })->create();
24 |
--------------------------------------------------------------------------------
/bootstrap/cache/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/bootstrap/providers.php:
--------------------------------------------------------------------------------
1 | env('CACHE_STORE', 'database'),
19 |
20 | /*
21 | |--------------------------------------------------------------------------
22 | | Cache Stores
23 | |--------------------------------------------------------------------------
24 | |
25 | | Here you may define all of the cache "stores" for your application as
26 | | well as their drivers. You may even define multiple stores for the
27 | | same cache driver to group types of items stored in your caches.
28 | |
29 | | Supported drivers: "array", "database", "file", "memcached",
30 | | "redis", "dynamodb", "octane", "null"
31 | |
32 | */
33 |
34 | 'stores' => [
35 |
36 | 'array' => [
37 | 'driver' => 'array',
38 | 'serialize' => false,
39 | ],
40 |
41 | 'database' => [
42 | 'driver' => 'database',
43 | 'table' => env('DB_CACHE_TABLE', 'cache'),
44 | 'connection' => env('DB_CACHE_CONNECTION'),
45 | 'lock_connection' => env('DB_CACHE_LOCK_CONNECTION'),
46 | ],
47 |
48 | 'file' => [
49 | 'driver' => 'file',
50 | 'path' => storage_path('framework/cache/data'),
51 | 'lock_path' => storage_path('framework/cache/data'),
52 | ],
53 |
54 | 'memcached' => [
55 | 'driver' => 'memcached',
56 | 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'),
57 | 'sasl' => [
58 | env('MEMCACHED_USERNAME'),
59 | env('MEMCACHED_PASSWORD'),
60 | ],
61 | 'options' => [
62 | // Memcached::OPT_CONNECT_TIMEOUT => 2000,
63 | ],
64 | 'servers' => [
65 | [
66 | 'host' => env('MEMCACHED_HOST', '127.0.0.1'),
67 | 'port' => env('MEMCACHED_PORT', 11211),
68 | 'weight' => 100,
69 | ],
70 | ],
71 | ],
72 |
73 | 'redis' => [
74 | 'driver' => 'redis',
75 | 'connection' => env('REDIS_CACHE_CONNECTION', 'cache'),
76 | 'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'),
77 | ],
78 |
79 | 'dynamodb' => [
80 | 'driver' => 'dynamodb',
81 | 'key' => env('AWS_ACCESS_KEY_ID'),
82 | 'secret' => env('AWS_SECRET_ACCESS_KEY'),
83 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
84 | 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),
85 | 'endpoint' => env('DYNAMODB_ENDPOINT'),
86 | ],
87 |
88 | 'octane' => [
89 | 'driver' => 'octane',
90 | ],
91 |
92 | ],
93 |
94 | /*
95 | |--------------------------------------------------------------------------
96 | | Cache Key Prefix
97 | |--------------------------------------------------------------------------
98 | |
99 | | When utilizing the APC, database, memcached, Redis, and DynamoDB cache
100 | | stores, there might be other applications using the same cache. For
101 | | that reason, you may prefix every cache key to avoid collisions.
102 | |
103 | */
104 |
105 | 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'),
106 |
107 | ];
108 |
--------------------------------------------------------------------------------
/config/filesystems.php:
--------------------------------------------------------------------------------
1 | env('FILESYSTEM_DISK', 'local'),
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Filesystem Disks
21 | |--------------------------------------------------------------------------
22 | |
23 | | Below you may configure as many filesystem disks as necessary, and you
24 | | may even configure multiple disks for the same driver. Examples for
25 | | most supported storage drivers are configured here for reference.
26 | |
27 | | Supported drivers: "local", "ftp", "sftp", "s3"
28 | |
29 | */
30 |
31 | 'disks' => [
32 |
33 | 'local' => [
34 | 'driver' => 'local',
35 | 'root' => storage_path('app'),
36 | 'throw' => false,
37 | ],
38 |
39 | 'public' => [
40 | 'driver' => 'local',
41 | 'root' => storage_path('app/public'),
42 | 'url' => env('APP_URL').'/storage',
43 | 'visibility' => 'public',
44 | 'throw' => false,
45 | ],
46 |
47 | 's3' => [
48 | 'driver' => 's3',
49 | 'key' => env('AWS_ACCESS_KEY_ID'),
50 | 'secret' => env('AWS_SECRET_ACCESS_KEY'),
51 | 'region' => env('AWS_DEFAULT_REGION'),
52 | 'bucket' => env('AWS_BUCKET'),
53 | 'url' => env('AWS_URL'),
54 | 'endpoint' => env('AWS_ENDPOINT'),
55 | 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
56 | 'throw' => false,
57 | ],
58 |
59 | ],
60 |
61 | /*
62 | |--------------------------------------------------------------------------
63 | | Symbolic Links
64 | |--------------------------------------------------------------------------
65 | |
66 | | Here you may configure the symbolic links that will be created when the
67 | | `storage:link` Artisan command is executed. The array keys should be
68 | | the locations of the links and the values should be their targets.
69 | |
70 | */
71 |
72 | 'links' => [
73 | public_path('storage') => storage_path('app/public'),
74 | ],
75 |
76 | ];
77 |
--------------------------------------------------------------------------------
/config/mail.php:
--------------------------------------------------------------------------------
1 | env('MAIL_MAILER', 'log'),
18 |
19 | /*
20 | |--------------------------------------------------------------------------
21 | | Mailer Configurations
22 | |--------------------------------------------------------------------------
23 | |
24 | | Here you may configure all of the mailers used by your application plus
25 | | their respective settings. Several examples have been configured for
26 | | you and you are free to add your own as your application requires.
27 | |
28 | | Laravel supports a variety of mail "transport" drivers that can be used
29 | | when delivering an email. You may specify which one you're using for
30 | | your mailers below. You may also add additional mailers if needed.
31 | |
32 | | Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2",
33 | | "postmark", "resend", "log", "array",
34 | | "failover", "roundrobin"
35 | |
36 | */
37 |
38 | 'mailers' => [
39 |
40 | 'smtp' => [
41 | 'transport' => 'smtp',
42 | 'url' => env('MAIL_URL'),
43 | 'host' => env('MAIL_HOST', '127.0.0.1'),
44 | 'port' => env('MAIL_PORT', 2525),
45 | 'encryption' => env('MAIL_ENCRYPTION', 'tls'),
46 | 'username' => env('MAIL_USERNAME'),
47 | 'password' => env('MAIL_PASSWORD'),
48 | 'timeout' => null,
49 | 'local_domain' => env('MAIL_EHLO_DOMAIN', parse_url(env('APP_URL', 'http://localhost'), PHP_URL_HOST)),
50 | ],
51 |
52 | 'ses' => [
53 | 'transport' => 'ses',
54 | ],
55 |
56 | 'postmark' => [
57 | 'transport' => 'postmark',
58 | // 'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'),
59 | // 'client' => [
60 | // 'timeout' => 5,
61 | // ],
62 | ],
63 |
64 | 'resend' => [
65 | 'transport' => 'resend',
66 | ],
67 |
68 | 'sendmail' => [
69 | 'transport' => 'sendmail',
70 | 'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'),
71 | ],
72 |
73 | 'log' => [
74 | 'transport' => 'log',
75 | 'channel' => env('MAIL_LOG_CHANNEL'),
76 | ],
77 |
78 | 'array' => [
79 | 'transport' => 'array',
80 | ],
81 |
82 | 'failover' => [
83 | 'transport' => 'failover',
84 | 'mailers' => [
85 | 'smtp',
86 | 'log',
87 | ],
88 | ],
89 |
90 | 'roundrobin' => [
91 | 'transport' => 'roundrobin',
92 | 'mailers' => [
93 | 'ses',
94 | 'postmark',
95 | ],
96 | ],
97 |
98 | ],
99 |
100 | /*
101 | |--------------------------------------------------------------------------
102 | | Global "From" Address
103 | |--------------------------------------------------------------------------
104 | |
105 | | You may wish for all emails sent by your application to be sent from
106 | | the same address. Here you may specify a name and address that is
107 | | used globally for all emails that are sent by your application.
108 | |
109 | */
110 |
111 | 'from' => [
112 | 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
113 | 'name' => env('MAIL_FROM_NAME', 'Example'),
114 | ],
115 |
116 | ];
117 |
--------------------------------------------------------------------------------
/config/services.php:
--------------------------------------------------------------------------------
1 | [
18 | 'token' => env('POSTMARK_TOKEN'),
19 | ],
20 |
21 | 'ses' => [
22 | 'key' => env('AWS_ACCESS_KEY_ID'),
23 | 'secret' => env('AWS_SECRET_ACCESS_KEY'),
24 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
25 | ],
26 |
27 | 'resend' => [
28 | 'key' => env('RESEND_KEY'),
29 | ],
30 |
31 | 'slack' => [
32 | 'notifications' => [
33 | 'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'),
34 | 'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'),
35 | ],
36 | ],
37 |
38 | ];
39 |
--------------------------------------------------------------------------------
/database/.gitignore:
--------------------------------------------------------------------------------
1 | *.sqlite*
2 |
--------------------------------------------------------------------------------
/database/factories/UserFactory.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 | class UserFactory extends Factory
13 | {
14 | /**
15 | * The current password being used by the factory.
16 | */
17 | protected static ?string $password;
18 |
19 | /**
20 | * Define the model's default state.
21 | *
22 | * @return array
23 | */
24 | public function definition(): array
25 | {
26 | return [
27 | 'name' => fake()->name(),
28 | 'email' => fake()->unique()->safeEmail(),
29 | 'email_verified_at' => now(),
30 | 'password' => static::$password ??= Hash::make('password'),
31 | 'remember_token' => Str::random(10),
32 | ];
33 | }
34 |
35 | /**
36 | * Indicate that the model's email address should be unverified.
37 | */
38 | public function unverified(): static
39 | {
40 | return $this->state(fn (array $attributes) => [
41 | 'email_verified_at' => null,
42 | ]);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/database/migrations/0001_01_01_000000_create_users_table.php:
--------------------------------------------------------------------------------
1 | id();
16 | $table->string('name');
17 | $table->string('email')->unique();
18 | $table->timestamp('email_verified_at')->nullable();
19 | $table->string('password');
20 | $table->rememberToken();
21 | $table->timestamps();
22 | });
23 |
24 | Schema::create('password_reset_tokens', function (Blueprint $table) {
25 | $table->string('email')->primary();
26 | $table->string('token');
27 | $table->timestamp('created_at')->nullable();
28 | });
29 |
30 | Schema::create('sessions', function (Blueprint $table) {
31 | $table->string('id')->primary();
32 | $table->foreignId('user_id')->nullable()->index();
33 | $table->string('ip_address', 45)->nullable();
34 | $table->text('user_agent')->nullable();
35 | $table->longText('payload');
36 | $table->integer('last_activity')->index();
37 | });
38 | }
39 |
40 | /**
41 | * Reverse the migrations.
42 | */
43 | public function down(): void
44 | {
45 | Schema::dropIfExists('users');
46 | Schema::dropIfExists('password_reset_tokens');
47 | Schema::dropIfExists('sessions');
48 | }
49 | };
50 |
--------------------------------------------------------------------------------
/database/migrations/0001_01_01_000001_create_cache_table.php:
--------------------------------------------------------------------------------
1 | string('key')->primary();
16 | $table->mediumText('value');
17 | $table->integer('expiration');
18 | });
19 |
20 | Schema::create('cache_locks', function (Blueprint $table) {
21 | $table->string('key')->primary();
22 | $table->string('owner');
23 | $table->integer('expiration');
24 | });
25 | }
26 |
27 | /**
28 | * Reverse the migrations.
29 | */
30 | public function down(): void
31 | {
32 | Schema::dropIfExists('cache');
33 | Schema::dropIfExists('cache_locks');
34 | }
35 | };
36 |
--------------------------------------------------------------------------------
/database/migrations/0001_01_01_000002_create_jobs_table.php:
--------------------------------------------------------------------------------
1 | id();
16 | $table->string('queue')->index();
17 | $table->longText('payload');
18 | $table->unsignedTinyInteger('attempts');
19 | $table->unsignedInteger('reserved_at')->nullable();
20 | $table->unsignedInteger('available_at');
21 | $table->unsignedInteger('created_at');
22 | });
23 |
24 | Schema::create('job_batches', function (Blueprint $table) {
25 | $table->string('id')->primary();
26 | $table->string('name');
27 | $table->integer('total_jobs');
28 | $table->integer('pending_jobs');
29 | $table->integer('failed_jobs');
30 | $table->longText('failed_job_ids');
31 | $table->mediumText('options')->nullable();
32 | $table->integer('cancelled_at')->nullable();
33 | $table->integer('created_at');
34 | $table->integer('finished_at')->nullable();
35 | });
36 |
37 | Schema::create('failed_jobs', function (Blueprint $table) {
38 | $table->id();
39 | $table->string('uuid')->unique();
40 | $table->text('connection');
41 | $table->text('queue');
42 | $table->longText('payload');
43 | $table->longText('exception');
44 | $table->timestamp('failed_at')->useCurrent();
45 | });
46 | }
47 |
48 | /**
49 | * Reverse the migrations.
50 | */
51 | public function down(): void
52 | {
53 | Schema::dropIfExists('jobs');
54 | Schema::dropIfExists('job_batches');
55 | Schema::dropIfExists('failed_jobs');
56 | }
57 | };
58 |
--------------------------------------------------------------------------------
/database/migrations/2024_06_13_091315_add_avatar_field_to_users_table.php:
--------------------------------------------------------------------------------
1 | string('avatar')->nullable();
16 | });
17 | }
18 |
19 | /**
20 | * Reverse the migrations.
21 | */
22 | public function down(): void
23 | {
24 | Schema::table('users', function (Blueprint $table) {
25 | $table->dropColumn('avatar');
26 | });
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/database/migrations/2024_06_13_125039_create_customers_table.php:
--------------------------------------------------------------------------------
1 | id();
17 | $table->string('name');
18 | $table->bigInteger('no_telp');
19 | $table->text('address');
20 | $table->timestamps();
21 | });
22 | }
23 |
24 | /**
25 | * Reverse the migrations.
26 | *
27 | * @return void
28 | */
29 | public function down()
30 | {
31 | Schema::dropIfExists('customers');
32 | }
33 | };
34 |
--------------------------------------------------------------------------------
/database/migrations/2024_06_13_130507_create_categories_table.php:
--------------------------------------------------------------------------------
1 | id();
18 | $table->string('image');
19 | $table->string('name');
20 | $table->text('description');
21 | $table->timestamps();
22 | });
23 | }
24 |
25 | /**
26 | * Reverse the migrations.
27 | *
28 | * @return void
29 | */
30 | public function down()
31 | {
32 | Schema::dropIfExists('categories');
33 | }
34 | };
35 |
--------------------------------------------------------------------------------
/database/migrations/2024_06_13_131744_create_products_table.php:
--------------------------------------------------------------------------------
1 | id();
18 | $table->unsignedBigInteger('category_id');
19 | $table->string('image');
20 | $table->string('barcode')->unique();
21 | $table->string('title');
22 | $table->text('description');
23 | $table->bigInteger('buy_price');
24 | $table->bigInteger('sell_price');
25 | $table->integer('stock');
26 | $table->timestamps();
27 |
28 | //relationship categories
29 | $table->foreign('category_id')->references('id')->on('categories');
30 | });
31 | }
32 |
33 | /**
34 | * Reverse the migrations.
35 | *
36 | * @return void
37 | */
38 | public function down()
39 | {
40 | Schema::dropIfExists('products');
41 | }
42 | };
43 |
--------------------------------------------------------------------------------
/database/migrations/2024_06_13_132800_create_transactions_table.php:
--------------------------------------------------------------------------------
1 | id();
18 | $table->unsignedBigInteger('cashier_id');
19 | $table->unsignedBigInteger('customer_id')->nullable();
20 | $table->string('invoice');
21 | $table->bigInteger('cash');
22 | $table->bigInteger('change');
23 | $table->bigInteger('discount');
24 | $table->bigInteger('grand_total');
25 | $table->timestamps();
26 |
27 | //relationship users
28 | $table->foreign('cashier_id')->references('id')->on('users');
29 |
30 | //relationship customers
31 | $table->foreign('customer_id')->references('id')->on('customers');
32 | });
33 | }
34 |
35 | /**
36 | * Reverse the migrations.
37 | *
38 | * @return void
39 | */
40 | public function down()
41 | {
42 | Schema::dropIfExists('transactions');
43 | }
44 | };
45 |
--------------------------------------------------------------------------------
/database/migrations/2024_06_13_133940_create_transaction_details_table.php:
--------------------------------------------------------------------------------
1 | id();
18 | $table->unsignedBigInteger('transaction_id');
19 | $table->unsignedBigInteger('product_id');
20 | $table->integer('qty');
21 | $table->bigInteger('price');
22 | $table->timestamps();
23 |
24 | //relationship transactions
25 | $table->foreign('transaction_id')->references('id')->on('transactions');
26 |
27 | //relationship products
28 | $table->foreign('product_id')->references('id')->on('products');
29 | });
30 | }
31 |
32 | /**
33 | * Reverse the migrations.
34 | *
35 | * @return void
36 | */
37 | public function down()
38 | {
39 | Schema::dropIfExists('transaction_details');
40 | }
41 | };
42 |
--------------------------------------------------------------------------------
/database/migrations/2024_06_13_133948_create_carts_table.php:
--------------------------------------------------------------------------------
1 | id();
18 | $table->unsignedBigInteger('cashier_id');
19 | $table->unsignedBigInteger('product_id');
20 | $table->integer('qty');
21 | $table->bigInteger('price');
22 | $table->timestamps();
23 |
24 | //relationship users
25 | $table->foreign('cashier_id')->references('id')->on('users');
26 |
27 | //relationship products
28 | $table->foreign('product_id')->references('id')->on('products');
29 | });
30 | }
31 |
32 | /**
33 | * Reverse the migrations.
34 | *
35 | * @return void
36 | */
37 | public function down()
38 | {
39 | Schema::dropIfExists('carts');
40 | }
41 | };
42 |
--------------------------------------------------------------------------------
/database/migrations/2024_06_13_133955_create_profits_table.php:
--------------------------------------------------------------------------------
1 | id();
18 | $table->unsignedBigInteger('transaction_id');
19 | $table->bigInteger('total');
20 | $table->timestamps();
21 |
22 | //relationship transactions
23 | $table->foreign('transaction_id')->references('id')->on('transactions');
24 | });
25 | }
26 |
27 | /**
28 | * Reverse the migrations.
29 | *
30 | * @return void
31 | */
32 | public function down()
33 | {
34 | Schema::dropIfExists('profits');
35 | }
36 | };
37 |
--------------------------------------------------------------------------------
/database/seeders/DatabaseSeeder.php:
--------------------------------------------------------------------------------
1 | call([
17 | PermissionSeeder::class,
18 | RoleSeeder::class,
19 | UserSeeder::class,
20 | ]);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/database/seeders/PermissionSeeder.php:
--------------------------------------------------------------------------------
1 | 'dashboard-access']);
18 |
19 | // users permissions
20 | Permission::create(['name' => 'users-access']);
21 | Permission::create(['name' => 'users-create']);
22 | Permission::create(['name' => 'users-update']);
23 | Permission::create(['name' => 'users-delete']);
24 |
25 | // roles permissions
26 | Permission::create(['name' => 'roles-access']);
27 | Permission::create(['name' => 'roles-create']);
28 | Permission::create(['name' => 'roles-update']);
29 | Permission::create(['name' => 'roles-delete']);
30 |
31 | // permissions permissions
32 | Permission::create(['name' => 'permissions-access']);
33 | Permission::create(['name' => 'permissions-create']);
34 | Permission::create(['name' => 'permissions-update']);
35 | Permission::create(['name' => 'permissions-delete']);
36 |
37 | //permission categories
38 | Permission::create(['name' => 'categories-access']);
39 | Permission::create(['name' => 'categories-create']);
40 | Permission::create(['name' => 'categories-edit']);
41 | Permission::create(['name' => 'categories-delete']);
42 |
43 | //permission products
44 | Permission::create(['name' => 'products-access']);
45 | Permission::create(['name' => 'products-create']);
46 | Permission::create(['name' => 'products-edit']);
47 | Permission::create(['name' => 'products-delete']);
48 |
49 | //permission customers
50 | Permission::create(['name' => 'customers-access']);
51 | Permission::create(['name' => 'customers-create']);
52 | Permission::create(['name' => 'customers-edit']);
53 | Permission::create(['name' => 'customers-delete']);
54 |
55 | //permission transactions
56 | Permission::create(['name' => 'transactions-access']);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/database/seeders/RoleSeeder.php:
--------------------------------------------------------------------------------
1 | createRoleWithPermissions('users-access', '%users%');
19 | $this->createRoleWithPermissions('roles-access', '%roles%');
20 | $this->createRoleWithPermissions('permission-access', '%permissions%');
21 | $this->createRoleWithPermissions('categories-access', '%categories%');
22 | $this->createRoleWithPermissions('products-access', '%products%');
23 | $this->createRoleWithPermissions('customers-access', '%customers%');
24 | $this->createRoleWithPermissions('transactions-access', '%transactions%');
25 |
26 | Role::create(['name' => 'super-admin']);
27 | }
28 |
29 | private function createRoleWithPermissions($roleName, $permissionNamePattern)
30 | {
31 | $permissions = Permission::where('name', 'like', $permissionNamePattern)->get();
32 | $role = Role::create(['name' => $roleName]);
33 | $role->givePermissionTo($permissions);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/database/seeders/UserSeeder.php:
--------------------------------------------------------------------------------
1 | 'Arya Dwi Putra',
20 | 'email' => 'arya@gmail.com',
21 | 'password' => bcrypt('password'),
22 | ]);
23 |
24 | // get admin role
25 | $role = Role::where('name', 'super-admin')->first();
26 |
27 | // get all permissions
28 | $permissions = Permission::all();
29 |
30 | // assign role to user
31 | $user->syncPermissions($permissions);
32 |
33 | // assign a role to user
34 | $user->assignRole($role);
35 |
36 | $cashier = User::create([
37 | 'name' => 'Cashier',
38 | 'email' => 'cashier@gmail.com',
39 | 'password' => bcrypt('password'),
40 | ]);
41 |
42 | $transactionsPermission = Permission::where('name', 'transactions-access')->first();
43 |
44 | $cashier->syncPermissions($transactionsPermission);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "paths": {
5 | "@/*": ["resources/js/*"],
6 | "ziggy-js": ["./vendor/tightenco/ziggy"]
7 | }
8 | },
9 | "exclude": ["node_modules", "public"]
10 | }
11 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "type": "module",
4 | "scripts": {
5 | "dev": "vite",
6 | "build": "vite build"
7 | },
8 | "devDependencies": {
9 | "@headlessui/react": "^1.4.2",
10 | "@inertiajs/react": "^1.0.0",
11 | "@tailwindcss/forms": "^0.5.3",
12 | "@vitejs/plugin-react": "^4.2.0",
13 | "autoprefixer": "^10.4.12",
14 | "axios": "^1.6.4",
15 | "laravel-vite-plugin": "^1.0",
16 | "postcss": "^8.4.31",
17 | "react": "^18.2.0",
18 | "react-dom": "^18.2.0",
19 | "tailwindcss": "^3.2.1",
20 | "vite": "^5.0"
21 | },
22 | "dependencies": {
23 | "@tabler/icons-react": "^3.5.0",
24 | "jsbarcode": "^3.11.6",
25 | "react-hot-toast": "^2.4.1",
26 | "sweetalert2": "^11.11.1"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 | tests/Unit
10 |
11 |
12 | tests/Feature
13 |
14 |
15 |
16 |
17 | app
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/public/.htaccess:
--------------------------------------------------------------------------------
1 |
2 |
3 | Options -MultiViews -Indexes
4 |
5 |
6 | RewriteEngine On
7 |
8 | # Handle Authorization Header
9 | RewriteCond %{HTTP:Authorization} .
10 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
11 |
12 | # Redirect Trailing Slashes If Not A Folder...
13 | RewriteCond %{REQUEST_FILENAME} !-d
14 | RewriteCond %{REQUEST_URI} (.+)/$
15 | RewriteRule ^ %1 [L,R=301]
16 |
17 | # Send Requests To Front Controller...
18 | RewriteCond %{REQUEST_FILENAME} !-d
19 | RewriteCond %{REQUEST_FILENAME} !-f
20 | RewriteRule ^ index.php [L]
21 |
22 |
--------------------------------------------------------------------------------
/public/assets/background/Hero-Banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aryadwiputra/point-of-sales/5be24fe68d4eabdbb99a3508927b67c82c2b4241/public/assets/background/Hero-Banner.png
--------------------------------------------------------------------------------
/public/assets/background/alqowy.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/public/assets/background/benefit_illustration.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aryadwiputra/point-of-sales/5be24fe68d4eabdbb99a3508927b67c82c2b4241/public/assets/background/benefit_illustration.png
--------------------------------------------------------------------------------
/public/assets/icon/3dcube.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/public/assets/icon/Group 7-1.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/public/assets/icon/Group 7-2.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/public/assets/icon/Group 7.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/public/assets/icon/add.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/public/assets/icon/arrow-right.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/public/assets/icon/avatar-group.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aryadwiputra/point-of-sales/5be24fe68d4eabdbb99a3508927b67c82c2b4241/public/assets/icon/avatar-group.png
--------------------------------------------------------------------------------
/public/assets/icon/award-outline.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/public/assets/icon/award.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/public/assets/icon/book.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/public/assets/icon/brifecase-tick.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/public/assets/icon/crown.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/public/assets/icon/medal-star.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/public/assets/icon/note-add.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/public/assets/icon/note-favorite.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/public/assets/icon/play-circle.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/public/assets/icon/profile-2user.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/public/assets/icon/star.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/public/assets/icon/tick-circle.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/public/assets/icon/video-play.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/public/assets/photo/photo1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aryadwiputra/point-of-sales/5be24fe68d4eabdbb99a3508927b67c82c2b4241/public/assets/photo/photo1.png
--------------------------------------------------------------------------------
/public/assets/photo/photo2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aryadwiputra/point-of-sales/5be24fe68d4eabdbb99a3508927b67c82c2b4241/public/assets/photo/photo2.png
--------------------------------------------------------------------------------
/public/assets/photo/photo3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aryadwiputra/point-of-sales/5be24fe68d4eabdbb99a3508927b67c82c2b4241/public/assets/photo/photo3.png
--------------------------------------------------------------------------------
/public/assets/photo/photo4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aryadwiputra/point-of-sales/5be24fe68d4eabdbb99a3508927b67c82c2b4241/public/assets/photo/photo4.png
--------------------------------------------------------------------------------
/public/assets/photo/photo5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aryadwiputra/point-of-sales/5be24fe68d4eabdbb99a3508927b67c82c2b4241/public/assets/photo/photo5.png
--------------------------------------------------------------------------------
/public/assets/thumbnail/image-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aryadwiputra/point-of-sales/5be24fe68d4eabdbb99a3508927b67c82c2b4241/public/assets/thumbnail/image-1.png
--------------------------------------------------------------------------------
/public/assets/thumbnail/image-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aryadwiputra/point-of-sales/5be24fe68d4eabdbb99a3508927b67c82c2b4241/public/assets/thumbnail/image-2.png
--------------------------------------------------------------------------------
/public/assets/thumbnail/image-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aryadwiputra/point-of-sales/5be24fe68d4eabdbb99a3508927b67c82c2b4241/public/assets/thumbnail/image-3.png
--------------------------------------------------------------------------------
/public/assets/thumbnail/image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aryadwiputra/point-of-sales/5be24fe68d4eabdbb99a3508927b67c82c2b4241/public/assets/thumbnail/image.png
--------------------------------------------------------------------------------
/public/assets/thumbnail/thumbnail-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aryadwiputra/point-of-sales/5be24fe68d4eabdbb99a3508927b67c82c2b4241/public/assets/thumbnail/thumbnail-1.png
--------------------------------------------------------------------------------
/public/assets/thumbnail/thumbnail-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aryadwiputra/point-of-sales/5be24fe68d4eabdbb99a3508927b67c82c2b4241/public/assets/thumbnail/thumbnail-2.png
--------------------------------------------------------------------------------
/public/assets/thumbnail/thumbnail-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aryadwiputra/point-of-sales/5be24fe68d4eabdbb99a3508927b67c82c2b4241/public/assets/thumbnail/thumbnail-3.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aryadwiputra/point-of-sales/5be24fe68d4eabdbb99a3508927b67c82c2b4241/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.php:
--------------------------------------------------------------------------------
1 | handleRequest(Request::capture());
18 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Disallow:
3 |
--------------------------------------------------------------------------------
/resources/css/app.css:
--------------------------------------------------------------------------------
1 | /* @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities; */
4 |
5 | @import 'tailwindcss/base';
6 | @import 'tailwindcss/components';
7 | @import 'tailwindcss/utilities';
8 |
9 |
--------------------------------------------------------------------------------
/resources/js/Components/ApplicationLogo.jsx:
--------------------------------------------------------------------------------
1 | export default function ApplicationLogo(props) {
2 | return (
3 |
4 |
5 |
6 | );
7 | }
8 |
--------------------------------------------------------------------------------
/resources/js/Components/Checkbox.jsx:
--------------------------------------------------------------------------------
1 | export default function Checkbox({ className = '', ...props }) {
2 | return (
3 |
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/resources/js/Components/DangerButton.jsx:
--------------------------------------------------------------------------------
1 | export default function DangerButton({ className = '', disabled, children, ...props }) {
2 | return (
3 |
12 | {children}
13 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/resources/js/Components/Dashboard/Barcode.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef } from 'react';
2 | import JsBarcode from 'jsbarcode';
3 |
4 | const Barcode = ({
5 | value,
6 | format,
7 | width,
8 | height,
9 | displayValue = true,
10 | text,
11 | fontOptions,
12 | font,
13 | textAlign,
14 | textPosition,
15 | textMargin,
16 | fontSize,
17 | background,
18 | lineColor,
19 | margin,
20 | marginTop,
21 | marginBottom,
22 | marginLeft,
23 | marginRight,
24 | flat,
25 | ean128,
26 | elementTag = 'svg'
27 | }) => {
28 | const barcodeRef = useRef(null);
29 |
30 | useEffect(() => {
31 | const settings = {
32 | format,
33 | width,
34 | height,
35 | displayValue,
36 | text,
37 | fontOptions,
38 | font,
39 | textAlign,
40 | textPosition,
41 | textMargin,
42 | fontSize,
43 | background,
44 | lineColor,
45 | margin,
46 | marginTop,
47 | marginBottom,
48 | marginLeft,
49 | marginRight,
50 | flat,
51 | ean128,
52 | valid: function (valid) {
53 | // Handle valid state if needed
54 | },
55 | };
56 |
57 | removeUndefinedProps(settings);
58 |
59 | JsBarcode(barcodeRef.current, value, settings);
60 | }, [value, format, width, height, displayValue, text, fontOptions, font, textAlign, textPosition, textMargin, fontSize, background, lineColor, margin, marginTop, marginBottom, marginLeft, marginRight, flat, ean128]);
61 |
62 | return React.createElement(elementTag, { ref: barcodeRef, id: "barcodegen" });
63 | };
64 |
65 | // Helper function to remove undefined properties from an object
66 | function removeUndefinedProps(obj) {
67 | Object.keys(obj).forEach(key => obj[key] === undefined ? delete obj[key] : {});
68 | }
69 |
70 | export default Barcode;
71 |
--------------------------------------------------------------------------------
/resources/js/Components/Dashboard/Button.jsx:
--------------------------------------------------------------------------------
1 | import { Link } from '@inertiajs/react'
2 | import React from 'react'
3 | import { useForm } from '@inertiajs/react';
4 | import Swal from 'sweetalert2'
5 |
6 | export default function Button({ className, icon, label, type, href, added, url, id, ...props }) {
7 |
8 | const { delete: destroy } = useForm();
9 |
10 | const deleteData = async (url) => {
11 | Swal.fire({
12 | title: 'Apakah kamu yakin ingin menghapus data ini ?',
13 | text: "Data yang dihapus tidak dapat dikembalikan!",
14 | icon: 'warning',
15 | showCancelButton: true,
16 | confirmButtonColor: '#3085d6',
17 | cancelButtonColor: '#d33',
18 | confirmButtonText: 'Ya, tolong hapus!',
19 | cancelButtonText: 'Tidak'
20 | }).then((result) => {
21 | if (result.isConfirmed) {
22 | destroy(url)
23 |
24 | Swal.fire({
25 | title: 'Success!',
26 | text: 'Data berhasil dihapus!',
27 | icon: 'success',
28 | showConfirmButton: false,
29 | timer: 1500
30 | })
31 | }
32 | })
33 | }
34 |
35 | return (
36 | <>
37 | {type === 'link' &&
38 |
39 | {icon} {label}
40 |
41 | }
42 | {type === 'button' &&
43 |
44 | {icon} {label}
45 |
46 | }
47 | {type === 'submit' &&
48 |
49 | {icon} {label}
50 |
51 | }
52 | {type === 'delete' &&
53 | deleteData(url)} className={`${className} px-3 py-2 flex items-center gap-1 rounded-lg text-sm font-semibold`} {...props}>
54 | {icon}
55 |
56 | }
57 | {type === 'modal' &&
58 |
59 | {icon}
60 |
61 | }
62 | {type === 'edit' &&
63 |
64 | {icon}
65 |
66 | }
67 | {type === 'bulk' &&
68 |
69 | {icon} {label}
70 |
71 | }
72 | >
73 | )
74 | }
75 |
--------------------------------------------------------------------------------
/resources/js/Components/Dashboard/Card.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default function Card({ icon, title, children, footer, className, form }) {
4 | return (
5 | <>
6 |
19 | >
20 | )
21 | }
22 |
--------------------------------------------------------------------------------
/resources/js/Components/Dashboard/Checkbox.jsx:
--------------------------------------------------------------------------------
1 | export default function Checkbox({ label, errors, ...props }) {
2 | return (
3 |
4 |
5 |
10 | {label}
11 |
12 | {errors && (
13 | {errors}
14 | )}
15 |
16 |
17 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/resources/js/Components/Dashboard/Header.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default function Header({ children, title, subtitle }) {
4 | return (
5 |
6 |
7 |
{title}
8 |
{subtitle}
9 |
10 | {children}
11 |
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/resources/js/Components/Dashboard/Input.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default function Input({ label, type, className, errors, ...props }) {
4 | return (
5 |
6 | {label}
7 |
12 | {errors && (
13 | {errors}
14 | )}
15 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/resources/js/Components/Dashboard/InputSelect.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 | import { Listbox } from '@headlessui/react'
3 | import { IconChevronDown, IconCircle, IconCircleFilled } from '@tabler/icons-react'
4 |
5 | export default function InputSelect({ selected, data, setSelected, label, errors, placeholder, multiple = false, searchable = false, displayKey = 'name' }) {
6 | const [search, setSearch] = useState('')
7 |
8 | const filteredData = data.filter(item =>
9 | item[displayKey]?.toLowerCase().includes(search.toLowerCase())
10 | )
11 |
12 | return (
13 |
14 |
{label}
15 |
16 |
17 | {multiple ? (
18 | selected.length > 0 ? selected.map(item => item[displayKey]).join(', ') : placeholder
19 | ) : (
20 | selected ? selected[displayKey] : placeholder
21 | )}
22 |
23 |
24 |
25 | {searchable && (
26 | setSearch(e.target.value)}
30 | placeholder="Search..."
31 | className="w-full px-3 py-1.5 mb-2 text-sm border rounded-md bg-white text-gray-700 border-gray-200 focus:outline-none focus:border-gray-300 dark:bg-gray-900 dark:text-gray-300 dark:border-gray-800 dark:focus:border-gray-700"
32 | />
33 | )}
34 | {filteredData.map((item) => (
35 |
36 | {({ selected }) => (
37 |
39 | {selected ? : }
40 | {item[displayKey]}
41 |
42 | )}
43 |
44 | ))}
45 |
46 |
47 | {errors && (
48 |
{errors}
49 | )}
50 |
51 | )
52 | }
53 |
--------------------------------------------------------------------------------
/resources/js/Components/Dashboard/LinkItem.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link, usePage } from '@inertiajs/react'
3 | export default function LinkItem({ href, icon, link, access, title, sidebarOpen, ...props }) {
4 | // destruct url from usepage
5 | const { url } = usePage();
6 |
7 | // destruct auth from usepage props
8 | const { auth } = usePage().props;
9 |
10 | return (
11 | <>
12 | {
13 | auth.super === true ?
14 | sidebarOpen ?
15 |
20 | {icon} {title}
21 |
22 | :
23 |
28 | {icon}
29 |
30 | :
31 | access === true &&
32 | sidebarOpen ?
33 |
38 | {icon} {title}
39 |
40 | :
41 |
46 | {icon}
47 |
48 | }
49 | >
50 | )
51 | }
52 |
--------------------------------------------------------------------------------
/resources/js/Components/Dashboard/ListBox.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Listbox } from '@headlessui/react'
3 | import { IconChevronDown, IconCircle, IconCircleFilled } from '@tabler/icons-react'
4 | export default function ListBox({ selected, data, setSelected, label, errors }) {
5 |
6 | const preview = selected.length ?
7 | selected.length >= 4 ? `jumlah hak akses terpilih ${selected.length}` :
8 | selected.map((item) => item.name).join(', ')
9 | :
10 | 'Pilh Hak Akses'
11 |
12 | return (
13 |
14 |
{label}
15 |
16 |
17 | {preview}
18 |
19 |
20 |
21 | {data.map((item) => (
22 |
23 | {({ selected }) => (
24 |
26 | {selected ? : }
27 | {item.name}
28 |
29 | )}
30 |
31 | ))}
32 |
33 |
34 | {errors && (
35 |
{errors}
36 | )}
37 |
38 | )
39 | }
40 |
--------------------------------------------------------------------------------
/resources/js/Components/Dashboard/Modal.jsx:
--------------------------------------------------------------------------------
1 | import { Fragment } from 'react';
2 | import { Dialog, Transition } from '@headlessui/react';
3 |
4 | export default function Modal({ children, title, show = false, maxWidth = '2xl', closeable = true, onClose = () => { } }) {
5 | const close = () => {
6 | if (closeable) {
7 | onClose();
8 | }
9 | };
10 |
11 | const maxWidthClass = {
12 | sm: 'sm:max-w-sm',
13 | md: 'sm:max-w-md',
14 | lg: 'sm:max-w-lg',
15 | xl: 'sm:max-w-xl',
16 | '2xl': 'sm:max-w-2xl',
17 | }[maxWidth];
18 |
19 | return (
20 |
21 |
27 |
36 |
37 |
38 |
39 |
48 |
51 |
52 | {title}
53 |
54 |
55 | {children}
56 |
57 |
58 |
59 |
60 |
61 | );
62 | }
63 |
--------------------------------------------------------------------------------
/resources/js/Components/Dashboard/Navbar.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 | import { usePage } from '@inertiajs/react';
3 | import { IconAlignLeft, IconMoon, IconSun } from '@tabler/icons-react'
4 | import AuthDropdown from '@/Components/Dashboard/AuthDropdown';
5 | import Menu from '@/Utils/Menu';
6 | import Notification from '@/Components/Dashboard/Notification';
7 |
8 | export default function Navbar({ toggleSidebar, themeSwitcher, darkMode }) {
9 | // destruct auth from props
10 | const { auth } = usePage().props;
11 |
12 | // get menu from utils
13 | const menuNavigation = Menu();
14 |
15 | // recreate array from menu navigations
16 | const links = menuNavigation.flatMap((item) => item.details);
17 | const filter_sublinks = links.filter((item) => item.hasOwnProperty('subdetails'));
18 | const sublinks = filter_sublinks.flatMap((item) => item.subdetails);
19 |
20 | // define state isMobile
21 | const [isMobile, setIsMobile] = useState(false);
22 |
23 | // define useEffect
24 | useEffect(() => {
25 | // define handle resize window
26 | const handleResize = () => {
27 | setIsMobile(window.innerWidth <= 768);
28 | };
29 |
30 | // define event listener
31 | window.addEventListener('resize', handleResize);
32 |
33 | // call handle resize window
34 | handleResize();
35 |
36 | // remove event listener
37 | return () => {
38 | window.removeEventListener('resize', handleResize);
39 | };
40 | })
41 |
42 | return (
43 |
44 |
45 |
46 |
47 |
48 |
49 | {/* {links.map((link, i) => (
50 | link.hasOwnProperty('subdetails') ?
51 | sublinks.map((sublink, x) => sublink.active === true && {sublink.title} )
52 | :
53 | link.active === true && {link.title}
54 | ))} */}
55 | {links.map((link, i) => (
56 | link.hasOwnProperty('subdetails') ?
57 | sublinks.map((sublink, x) => sublink.active === true && {sublink.title} )
58 | :
59 | link.active === true && {link.title}
60 | ))}
61 |
62 |
63 |
64 |
65 |
66 |
67 | {darkMode ? : }
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 | )
76 | }
77 |
--------------------------------------------------------------------------------
/resources/js/Components/Dashboard/Pagination.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link } from '@inertiajs/react';
3 | import { IconChevronRight, IconChevronLeft } from '@tabler/icons-react';
4 | export default function Pagination({ links }) {
5 |
6 | const style = 'p-1 text-sm border rounded-md bg-white text-gray-500 hover:bg-gray-100 dark:bg-gray-950 dark:text-gray-400 dark:hover:bg-gray-900 dark:border-gray-900'
7 |
8 | return (
9 | <>
10 |
11 | {links.map((item, i) => {
12 | return item.url != null ? (
13 | item.label.includes('Previous') ? (
14 |
15 |
16 |
17 | ) : item.label.includes('Next') ? (
18 |
19 |
20 |
21 | ) : (
22 |
23 | {item.label}
24 |
25 | )
26 | ) : null;
27 | })}
28 |
29 | >
30 | )
31 | }
32 |
--------------------------------------------------------------------------------
/resources/js/Components/Dashboard/Search.jsx:
--------------------------------------------------------------------------------
1 | import { useForm } from '@inertiajs/react';
2 | import { IconSearch } from '@tabler/icons-react';
3 | import React from 'react'
4 | export default function Search({ url, placeholder }) {
5 |
6 | // define use form inertia
7 | const { data, setData, get } = useForm({
8 | search: '',
9 | })
10 |
11 | // define method searchData
12 | const searchData = (e) => {
13 | e.preventDefault();
14 |
15 | get(`${url}?search=${data.search}`)
16 | }
17 |
18 | return (
19 |
32 | )
33 | }
34 |
--------------------------------------------------------------------------------
/resources/js/Components/Dashboard/Table.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const Card = ({ icon, title, className, children }) => {
4 | return (
5 | <>
6 |
11 |
12 | {children}
13 |
14 | >
15 |
16 | )
17 | }
18 |
19 | const Table = ({ children }) => {
20 | return (
21 |
26 | );
27 | };
28 |
29 | const Thead = ({ className, children }) => {
30 | return (
31 | {children}
32 | );
33 | };
34 |
35 | const Tbody = ({ className, children }) => {
36 | return (
37 |
38 | {children}
39 |
40 | );
41 | };
42 |
43 | const Td = ({ className, children }) => {
44 | return (
45 |
48 | {children}
49 |
50 | );
51 | };
52 |
53 | const Th = ({ className, children }) => {
54 | return (
55 |
59 | {children}
60 |
61 | );
62 | };
63 |
64 | const Empty = ({ colSpan, message, children }) => {
65 | return (
66 |
67 |
68 |
69 |
70 | {children}
71 |
72 | {message}
73 |
74 |
75 |
76 |
77 |
78 | )
79 | }
80 |
81 | Table.Card = Card;
82 | Table.Thead = Thead;
83 | Table.Tbody = Tbody;
84 | Table.Td = Td;
85 | Table.Th = Th;
86 | Table.Empty = Empty;
87 |
88 | export default Table;
89 |
--------------------------------------------------------------------------------
/resources/js/Components/Dashboard/TextArea.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default function Textarea({ label, type, className, errors, ...props }) {
4 | return (
5 |
6 | {label}
7 |
12 | {errors && (
13 | {errors}
14 | )}
15 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/resources/js/Components/Dashboard/Widget.jsx:
--------------------------------------------------------------------------------
1 | import { useTheme } from '@/Context/ThemeSwitcherContext'
2 | import React from 'react'
3 |
4 | export default function Widget({ title, icon, subtitle, className, total, color }) {
5 | return (
6 |
7 |
8 |
9 |
10 | {icon}
11 |
12 |
13 |
{title}
14 |
{subtitle}
15 |
16 |
17 |
18 | {total}
19 |
20 |
21 |
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/resources/js/Components/Dropdown.jsx:
--------------------------------------------------------------------------------
1 | import { useState, createContext, useContext, Fragment } from 'react';
2 | import { Link } from '@inertiajs/react';
3 | import { Transition } from '@headlessui/react';
4 |
5 | const DropDownContext = createContext();
6 |
7 | const Dropdown = ({ children }) => {
8 | const [open, setOpen] = useState(false);
9 |
10 | const toggleOpen = () => {
11 | setOpen((previousState) => !previousState);
12 | };
13 |
14 | return (
15 |
16 | {children}
17 |
18 | );
19 | };
20 |
21 | const Trigger = ({ children }) => {
22 | const { open, setOpen, toggleOpen } = useContext(DropDownContext);
23 |
24 | return (
25 | <>
26 | {children}
27 |
28 | {open && setOpen(false)}>
}
29 | >
30 | );
31 | };
32 |
33 | const Content = ({ align = 'right', width = '48', contentClasses = 'py-1 bg-white', children }) => {
34 | const { open, setOpen } = useContext(DropDownContext);
35 |
36 | let alignmentClasses = 'origin-top';
37 |
38 | if (align === 'left') {
39 | alignmentClasses = 'ltr:origin-top-left rtl:origin-top-right start-0';
40 | } else if (align === 'right') {
41 | alignmentClasses = 'ltr:origin-top-right rtl:origin-top-left end-0';
42 | }
43 |
44 | let widthClasses = '';
45 |
46 | if (width === '48') {
47 | widthClasses = 'w-48';
48 | }
49 |
50 | return (
51 | <>
52 |
62 | setOpen(false)}
65 | >
66 |
{children}
67 |
68 |
69 | >
70 | );
71 | };
72 |
73 | const DropdownLink = ({ className = '', children, ...props }) => {
74 | return (
75 |
82 | {children}
83 |
84 | );
85 | };
86 |
87 | Dropdown.Trigger = Trigger;
88 | Dropdown.Content = Content;
89 | Dropdown.Link = DropdownLink;
90 |
91 | export default Dropdown;
92 |
--------------------------------------------------------------------------------
/resources/js/Components/InputError.jsx:
--------------------------------------------------------------------------------
1 | export default function InputError({ message, className = '', ...props }) {
2 | return message ? (
3 |
4 | {message}
5 |
6 | ) : null;
7 | }
8 |
--------------------------------------------------------------------------------
/resources/js/Components/InputLabel.jsx:
--------------------------------------------------------------------------------
1 | export default function InputLabel({ value, className = '', children, ...props }) {
2 | return (
3 |
4 | {value ? value : children}
5 |
6 | );
7 | }
8 |
--------------------------------------------------------------------------------
/resources/js/Components/Modal.jsx:
--------------------------------------------------------------------------------
1 | import { Fragment } from 'react';
2 | import { Dialog, Transition } from '@headlessui/react';
3 |
4 | export default function Modal({ children, show = false, maxWidth = '2xl', closeable = true, onClose = () => {} }) {
5 | const close = () => {
6 | if (closeable) {
7 | onClose();
8 | }
9 | };
10 |
11 | const maxWidthClass = {
12 | sm: 'sm:max-w-sm',
13 | md: 'sm:max-w-md',
14 | lg: 'sm:max-w-lg',
15 | xl: 'sm:max-w-xl',
16 | '2xl': 'sm:max-w-2xl',
17 | }[maxWidth];
18 |
19 | return (
20 |
21 |
27 |
36 |
37 |
38 |
39 |
48 |
51 | {children}
52 |
53 |
54 |
55 |
56 | );
57 | }
58 |
--------------------------------------------------------------------------------
/resources/js/Components/NavLink.jsx:
--------------------------------------------------------------------------------
1 | import { Link } from '@inertiajs/react';
2 |
3 | export default function NavLink({ active = false, className = '', children, ...props }) {
4 | return (
5 |
15 | {children}
16 |
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/resources/js/Components/PrimaryButton.jsx:
--------------------------------------------------------------------------------
1 | export default function PrimaryButton({ className = '', disabled, children, ...props }) {
2 | return (
3 |
12 | {children}
13 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/resources/js/Components/ResponsiveNavLink.jsx:
--------------------------------------------------------------------------------
1 | import { Link } from '@inertiajs/react';
2 |
3 | export default function ResponsiveNavLink({ active = false, className = '', children, ...props }) {
4 | return (
5 |
13 | {children}
14 |
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/resources/js/Components/SecondaryButton.jsx:
--------------------------------------------------------------------------------
1 | export default function SecondaryButton({ type = 'button', className = '', disabled, children, ...props }) {
2 | return (
3 |
13 | {children}
14 |
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/resources/js/Components/TextInput.jsx:
--------------------------------------------------------------------------------
1 | import { forwardRef, useEffect, useRef } from 'react';
2 |
3 | export default forwardRef(function TextInput({ type = 'text', className = '', isFocused = false, ...props }, ref) {
4 | const input = ref ? ref : useRef();
5 |
6 | useEffect(() => {
7 | if (isFocused) {
8 | input.current.focus();
9 | }
10 | }, []);
11 |
12 | return (
13 |
22 | );
23 | });
24 |
--------------------------------------------------------------------------------
/resources/js/Context/ThemeSwitcherContext.jsx:
--------------------------------------------------------------------------------
1 | import React, { createContext, useContext, useState, useEffect } from 'react';
2 |
3 | const ThemeSwitcher = createContext();
4 |
5 | export const ThemeSwitcherProvider = ({ children }) => {
6 | // define state darkMode
7 | const [darkMode, setDarkMode] = useState(
8 | localStorage.getItem('darkMode') === 'true'
9 | )
10 |
11 | useEffect(() => {
12 | const root = document.documentElement;
13 | const toggleTransition = () => {
14 | root.classList.add('no-transition');
15 | setTimeout(() => {
16 | root.classList.remove('no-transition');
17 | }, 0);
18 | };
19 |
20 | toggleTransition();
21 |
22 | if (darkMode)
23 | document.body.classList.add('dark');
24 | else
25 | document.body.classList.remove('dark');
26 |
27 | // set darkMode in localstorage
28 | localStorage.setItem('darkMode', darkMode);
29 | }, [darkMode]);
30 |
31 | const themeSwitcher = () => setDarkMode(!darkMode);
32 |
33 | return (
34 |
35 | {children}
36 |
37 | )
38 | }
39 |
40 | export const useTheme = () => useContext(ThemeSwitcher);
41 |
--------------------------------------------------------------------------------
/resources/js/Layouts/DashboardLayout.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 | import Sidebar from '@/Components/Dashboard/Sidebar'
3 | import Navbar from '@/Components/Dashboard/Navbar'
4 | import { Toaster } from 'react-hot-toast';
5 | import { useTheme } from '@/Context/ThemeSwitcherContext';
6 |
7 | export default function AppLayout({ children }) {
8 |
9 | // destruct darkMode and themeSwitcher from context
10 | const { darkMode, themeSwitcher } = useTheme();
11 |
12 | // define state sidebarOpen
13 | const [sidebarOpen, setSidebarOpen] = useState(
14 | localStorage.getItem('sidebarOpen') === 'true'
15 | );
16 |
17 | // define react hooks
18 | useEffect(() => {
19 | localStorage.setItem('sidebarOpen', sidebarOpen);
20 | }, [sidebarOpen])
21 |
22 | // define function toggleSidebar
23 | const toggleSidebar = () => setSidebarOpen(!sidebarOpen);
24 |
25 | return (
26 |
27 |
28 |
29 |
30 |
31 |
32 | {children}
33 |
34 |
35 |
36 | )
37 | }
38 |
--------------------------------------------------------------------------------
/resources/js/Layouts/GuestLayout.jsx:
--------------------------------------------------------------------------------
1 | import ApplicationLogo from '@/Components/ApplicationLogo';
2 | import { Link } from '@inertiajs/react';
3 |
4 | export default function Guest({ children }) {
5 | return (
6 |
7 |
12 |
13 |
14 | {children}
15 |
16 |
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/resources/js/Pages/Auth/ConfirmPassword.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import GuestLayout from '@/Layouts/GuestLayout';
3 | import InputError from '@/Components/InputError';
4 | import InputLabel from '@/Components/InputLabel';
5 | import PrimaryButton from '@/Components/PrimaryButton';
6 | import TextInput from '@/Components/TextInput';
7 | import { Head, useForm } from '@inertiajs/react';
8 |
9 | export default function ConfirmPassword() {
10 | const { data, setData, post, processing, errors, reset } = useForm({
11 | password: '',
12 | });
13 |
14 | useEffect(() => {
15 | return () => {
16 | reset('password');
17 | };
18 | }, []);
19 |
20 | const submit = (e) => {
21 | e.preventDefault();
22 |
23 | post(route('password.confirm'));
24 | };
25 |
26 | return (
27 |
28 |
29 |
30 |
31 | This is a secure area of the application. Please confirm your password before continuing.
32 |
33 |
34 |
57 |
58 | );
59 | }
60 |
--------------------------------------------------------------------------------
/resources/js/Pages/Auth/ForgotPassword.jsx:
--------------------------------------------------------------------------------
1 | import GuestLayout from '@/Layouts/GuestLayout';
2 | import InputError from '@/Components/InputError';
3 | import PrimaryButton from '@/Components/PrimaryButton';
4 | import TextInput from '@/Components/TextInput';
5 | import { Head, useForm } from '@inertiajs/react';
6 |
7 | export default function ForgotPassword({ status }) {
8 | const { data, setData, post, processing, errors } = useForm({
9 | email: '',
10 | });
11 |
12 | const submit = (e) => {
13 | e.preventDefault();
14 |
15 | post(route('password.email'));
16 | };
17 |
18 | return (
19 |
20 |
21 |
22 |
23 | Forgot your password? No problem. Just let us know your email address and we will email you a password
24 | reset link that will allow you to choose a new one.
25 |
26 |
27 | {status && {status}
}
28 |
29 |
48 |
49 | );
50 | }
51 |
--------------------------------------------------------------------------------
/resources/js/Pages/Auth/ResetPassword.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import GuestLayout from '@/Layouts/GuestLayout';
3 | import InputError from '@/Components/InputError';
4 | import InputLabel from '@/Components/InputLabel';
5 | import PrimaryButton from '@/Components/PrimaryButton';
6 | import TextInput from '@/Components/TextInput';
7 | import { Head, useForm } from '@inertiajs/react';
8 |
9 | export default function ResetPassword({ token, email }) {
10 | const { data, setData, post, processing, errors, reset } = useForm({
11 | token: token,
12 | email: email,
13 | password: '',
14 | password_confirmation: '',
15 | });
16 |
17 | useEffect(() => {
18 | return () => {
19 | reset('password', 'password_confirmation');
20 | };
21 | }, []);
22 |
23 | const submit = (e) => {
24 | e.preventDefault();
25 |
26 | post(route('password.store'));
27 | };
28 |
29 | return (
30 |
31 |
32 |
33 |
89 |
90 | );
91 | }
92 |
--------------------------------------------------------------------------------
/resources/js/Pages/Auth/VerifyEmail.jsx:
--------------------------------------------------------------------------------
1 | import GuestLayout from '@/Layouts/GuestLayout';
2 | import PrimaryButton from '@/Components/PrimaryButton';
3 | import { Head, Link, useForm } from '@inertiajs/react';
4 |
5 | export default function VerifyEmail({ status }) {
6 | const { post, processing } = useForm({});
7 |
8 | const submit = (e) => {
9 | e.preventDefault();
10 |
11 | post(route('verification.send'));
12 | };
13 |
14 | return (
15 |
16 |
17 |
18 |
19 | Thanks for signing up! Before getting started, could you verify your email address by clicking on the
20 | link we just emailed to you? If you didn't receive the email, we will gladly send you another.
21 |
22 |
23 | {status === 'verification-link-sent' && (
24 |
25 | A new verification link has been sent to the email address you provided during registration.
26 |
27 | )}
28 |
29 |
43 |
44 | );
45 | }
46 |
--------------------------------------------------------------------------------
/resources/js/Pages/Dashboard.jsx:
--------------------------------------------------------------------------------
1 | import Card from '@/Components/Dashboard/Card';
2 | import Table from '@/Components/Dashboard/Table';
3 | import Widget from '@/Components/Dashboard/Widget';
4 | import DashboardLayout from '@/Layouts/DashboardLayout';
5 | import { Head } from '@inertiajs/react';
6 | import { IconBox, IconCategory, IconMoneybag, IconUsers } from '@tabler/icons-react';
7 | export default function Dashboard() {
8 |
9 |
10 |
11 | return (
12 | <>
13 |
14 |
15 | }
20 | total={20}
21 | />
22 | }
27 | total={30}
28 | />
29 | }
34 | total={45}
35 | />
36 | }
41 | total={2}
42 | />
43 |
44 | >
45 | );
46 | }
47 |
48 | Dashboard.layout = page =>
49 |
--------------------------------------------------------------------------------
/resources/js/Pages/Dashboard/Customers/Create.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import DashboardLayout from '@/Layouts/DashboardLayout'
3 | import { Head, useForm, usePage } from '@inertiajs/react'
4 | import Card from '@/Components/Dashboard/Card'
5 | import Button from '@/Components/Dashboard/Button'
6 | import { IconPencilPlus, IconUsersPlus } from '@tabler/icons-react'
7 | import Input from '@/Components/Dashboard/Input'
8 | import Textarea from '@/Components/Dashboard/TextArea'
9 | import toast from 'react-hot-toast'
10 |
11 | export default function Create() {
12 |
13 | const { errors } = usePage().props
14 |
15 | const { data, setData, post, processing } = useForm({
16 | name: '',
17 | no_telp: '',
18 | address: ''
19 | })
20 |
21 | const submit = (e) => {
22 | e.preventDefault()
23 | post(route('customers.store'), {
24 | onSuccess: () => {
25 | if (Object.keys(errors).length === 0) {
26 | toast('Data berhasil disimpan', {
27 | icon: '👏',
28 | style: {
29 | borderRadius: '10px',
30 | background: '#1C1F29',
31 | color: '#fff',
32 | },
33 | })
34 | }
35 | },
36 | onError: () => {
37 | toast('Terjadi kesalahan dalam penyimpanan data', {
38 | style: {
39 | borderRadius: '10px',
40 | background: '#FF0000',
41 | color: '#fff',
42 | },
43 | })
44 | },
45 | })
46 | }
47 |
48 | return (
49 | <>
50 |
51 | }
54 | footer={
55 | }
59 | className={'border bg-white text-gray-700 hover:bg-gray-100 dark:bg-gray-950 dark:border-gray-800 dark:text-gray-200 dark:hover:bg-gray-900'}
60 | />
61 | }
62 | form={submit}
63 | >
64 |
65 |
66 | setData('name', e.target.value)}
73 | />
74 |
75 |
76 | setData('no_telp', e.target.value)}
83 | />
84 |
85 |
86 |
95 |
96 |
97 | >
98 | )
99 | }
100 |
101 | Create.layout = page =>
102 |
--------------------------------------------------------------------------------
/resources/js/Pages/Dashboard/Index.jsx:
--------------------------------------------------------------------------------
1 | import Card from '@/Components/Dashboard/Card';
2 | import Table from '@/Components/Dashboard/Table';
3 | import Widget from '@/Components/Dashboard/Widget';
4 | import DashboardLayout from '@/Layouts/DashboardLayout';
5 | import { Head } from '@inertiajs/react';
6 | import { IconBox, IconCategory, IconMoneybag, IconUsers } from '@tabler/icons-react';
7 | export default function Dashboard() {
8 |
9 |
10 |
11 | return (
12 | <>
13 |
14 |
15 | }
20 | total={20}
21 | />
22 | }
27 | total={30}
28 | />
29 | }
34 | total={45}
35 | />
36 | }
41 | total={2}
42 | />
43 |
44 | >
45 | );
46 | }
47 |
48 | Dashboard.layout = page =>
49 |
--------------------------------------------------------------------------------
/resources/js/Pages/Dashboard/Permissions/Index.jsx:
--------------------------------------------------------------------------------
1 | import Pagination from '@/Components/Dashboard/Pagination';
2 | import Search from '@/Components/Dashboard/Search';
3 | import Table from '@/Components/Dashboard/Table'
4 | import DashboardLayout from '@/Layouts/DashboardLayout'
5 | import { Head, usePage } from '@inertiajs/react'
6 | import { IconDatabaseOff, IconUserBolt } from '@tabler/icons-react';
7 | import React from 'react'
8 | export default function Index() {
9 |
10 | // destruct permissions from props
11 | const { permissions } = usePage().props;
12 |
13 | return (
14 | <>
15 |
27 | @inertia
28 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/routes/auth.php:
--------------------------------------------------------------------------------
1 | group(function () {
15 | Route::get('register', [RegisteredUserController::class, 'create'])
16 | ->name('register');
17 |
18 | Route::post('register', [RegisteredUserController::class, 'store']);
19 |
20 | Route::get('login', [AuthenticatedSessionController::class, 'create'])
21 | ->name('login');
22 |
23 | Route::post('login', [AuthenticatedSessionController::class, 'store']);
24 |
25 | Route::get('forgot-password', [PasswordResetLinkController::class, 'create'])
26 | ->name('password.request');
27 |
28 | Route::post('forgot-password', [PasswordResetLinkController::class, 'store'])
29 | ->name('password.email');
30 |
31 | Route::get('reset-password/{token}', [NewPasswordController::class, 'create'])
32 | ->name('password.reset');
33 |
34 | Route::post('reset-password', [NewPasswordController::class, 'store'])
35 | ->name('password.store');
36 | });
37 |
38 | Route::middleware('auth')->group(function () {
39 | Route::get('verify-email', EmailVerificationPromptController::class)
40 | ->name('verification.notice');
41 |
42 | Route::get('verify-email/{id}/{hash}', VerifyEmailController::class)
43 | ->middleware(['signed', 'throttle:6,1'])
44 | ->name('verification.verify');
45 |
46 | Route::post('email/verification-notification', [EmailVerificationNotificationController::class, 'store'])
47 | ->middleware('throttle:6,1')
48 | ->name('verification.send');
49 |
50 | Route::get('confirm-password', [ConfirmablePasswordController::class, 'show'])
51 | ->name('password.confirm');
52 |
53 | Route::post('confirm-password', [ConfirmablePasswordController::class, 'store']);
54 |
55 | Route::put('password', [PasswordController::class, 'update'])->name('password.update');
56 |
57 | Route::post('logout', [AuthenticatedSessionController::class, 'destroy'])
58 | ->name('logout');
59 | });
60 |
--------------------------------------------------------------------------------
/routes/console.php:
--------------------------------------------------------------------------------
1 | comment(Inspiring::quote());
8 | })->purpose('Display an inspiring quote')->hourly();
9 |
--------------------------------------------------------------------------------
/routes/web.php:
--------------------------------------------------------------------------------
1 | Route::has('login'),
17 | 'canRegister' => Route::has('register'),
18 | 'laravelVersion' => Application::VERSION,
19 | 'phpVersion' => PHP_VERSION,
20 | ]);
21 | });
22 |
23 |
24 | Route::group(['prefix' => 'dashboard', 'middleware' => ['auth']], function () {
25 | Route::get('/', function () {
26 | return Inertia::render('Dashboard/Index');
27 | })->middleware(['auth', 'verified'])->name('dashboard');
28 | Route::get('/permissions', [PermissionController::class, 'index'])->name('permissions.index');
29 | // roles route
30 | Route::resource('/roles', RoleController::class)->except(['create', 'edit', 'show']);
31 | // users route
32 | Route::resource('/users', UserController::class)->except('show');
33 |
34 | Route::resource('categories', CategoryController::class);
35 | Route::resource('products', ProductController::class);
36 | Route::resource('customers', CustomerController::class);
37 | //route transaction
38 | Route::get('/transactions', [\App\Http\Controllers\Apps\TransactionController::class, 'index'])->name('transactions.index');
39 |
40 | //route transaction searchProduct
41 | Route::post('/transactions/searchProduct', [\App\Http\Controllers\Apps\TransactionController::class, 'searchProduct'])->name('transactions.searchProduct');
42 |
43 | //route transaction addToCart
44 | Route::post('/transactions/addToCart', [\App\Http\Controllers\Apps\TransactionController::class, 'addToCart'])->name('transactions.addToCart');
45 |
46 | //route transaction destroyCart
47 | Route::delete('/transactions/{cart_id}/destroyCart', [\App\Http\Controllers\Apps\TransactionController::class, 'destroyCart'])->name('transactions.destroyCart');
48 |
49 | //route transaction store
50 | Route::post('/transactions/store', [\App\Http\Controllers\Apps\TransactionController::class, 'store'])->name('transactions.store');
51 | Route::get('/transactions/{invoice}/print', [\App\Http\Controllers\Apps\TransactionController::class, 'print'])->name('transactions.print');
52 |
53 |
54 | Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
55 | Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
56 | Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
57 | });
58 |
59 | require __DIR__ . '/auth.php';
60 |
--------------------------------------------------------------------------------
/storage/app/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !public/
3 | !.gitignore
4 |
--------------------------------------------------------------------------------
/storage/app/public/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/framework/.gitignore:
--------------------------------------------------------------------------------
1 | compiled.php
2 | config.php
3 | down
4 | events.scanned.php
5 | maintenance.php
6 | routes.php
7 | routes.scanned.php
8 | schedule-*
9 | services.json
10 |
--------------------------------------------------------------------------------
/storage/framework/cache/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !data/
3 | !.gitignore
4 |
--------------------------------------------------------------------------------
/storage/framework/cache/data/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/framework/sessions/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/framework/testing/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/framework/views/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/logs/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | import defaultTheme from 'tailwindcss/defaultTheme';
2 | import forms from '@tailwindcss/forms';
3 |
4 | /** @type {import('tailwindcss').Config} */
5 | export default {
6 | content: [
7 | './vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php',
8 | './storage/framework/views/*.php',
9 | './resources/views/**/*.blade.php',
10 | './resources/js/**/*.jsx',
11 | ],
12 | darkMode: 'class',
13 | theme: {
14 | extend: {
15 | fontFamily: {
16 | sans: ['Figtree', ...defaultTheme.fontFamily.sans],
17 | },
18 | },
19 | },
20 |
21 | plugins: [forms],
22 | };
23 |
--------------------------------------------------------------------------------
/tests/Feature/Auth/AuthenticationTest.php:
--------------------------------------------------------------------------------
1 | get('/login');
16 |
17 | $response->assertStatus(200);
18 | }
19 |
20 | public function test_users_can_authenticate_using_the_login_screen(): void
21 | {
22 | $user = User::factory()->create();
23 |
24 | $response = $this->post('/login', [
25 | 'email' => $user->email,
26 | 'password' => 'password',
27 | ]);
28 |
29 | $this->assertAuthenticated();
30 | $response->assertRedirect(route('dashboard', absolute: false));
31 | }
32 |
33 | public function test_users_can_not_authenticate_with_invalid_password(): void
34 | {
35 | $user = User::factory()->create();
36 |
37 | $this->post('/login', [
38 | 'email' => $user->email,
39 | 'password' => 'wrong-password',
40 | ]);
41 |
42 | $this->assertGuest();
43 | }
44 |
45 | public function test_users_can_logout(): void
46 | {
47 | $user = User::factory()->create();
48 |
49 | $response = $this->actingAs($user)->post('/logout');
50 |
51 | $this->assertGuest();
52 | $response->assertRedirect('/');
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/tests/Feature/Auth/EmailVerificationTest.php:
--------------------------------------------------------------------------------
1 | unverified()->create();
19 |
20 | $response = $this->actingAs($user)->get('/verify-email');
21 |
22 | $response->assertStatus(200);
23 | }
24 |
25 | public function test_email_can_be_verified(): void
26 | {
27 | $user = User::factory()->unverified()->create();
28 |
29 | Event::fake();
30 |
31 | $verificationUrl = URL::temporarySignedRoute(
32 | 'verification.verify',
33 | now()->addMinutes(60),
34 | ['id' => $user->id, 'hash' => sha1($user->email)]
35 | );
36 |
37 | $response = $this->actingAs($user)->get($verificationUrl);
38 |
39 | Event::assertDispatched(Verified::class);
40 | $this->assertTrue($user->fresh()->hasVerifiedEmail());
41 | $response->assertRedirect(route('dashboard', absolute: false).'?verified=1');
42 | }
43 |
44 | public function test_email_is_not_verified_with_invalid_hash(): void
45 | {
46 | $user = User::factory()->unverified()->create();
47 |
48 | $verificationUrl = URL::temporarySignedRoute(
49 | 'verification.verify',
50 | now()->addMinutes(60),
51 | ['id' => $user->id, 'hash' => sha1('wrong-email')]
52 | );
53 |
54 | $this->actingAs($user)->get($verificationUrl);
55 |
56 | $this->assertFalse($user->fresh()->hasVerifiedEmail());
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/tests/Feature/Auth/PasswordConfirmationTest.php:
--------------------------------------------------------------------------------
1 | create();
16 |
17 | $response = $this->actingAs($user)->get('/confirm-password');
18 |
19 | $response->assertStatus(200);
20 | }
21 |
22 | public function test_password_can_be_confirmed(): void
23 | {
24 | $user = User::factory()->create();
25 |
26 | $response = $this->actingAs($user)->post('/confirm-password', [
27 | 'password' => 'password',
28 | ]);
29 |
30 | $response->assertRedirect();
31 | $response->assertSessionHasNoErrors();
32 | }
33 |
34 | public function test_password_is_not_confirmed_with_invalid_password(): void
35 | {
36 | $user = User::factory()->create();
37 |
38 | $response = $this->actingAs($user)->post('/confirm-password', [
39 | 'password' => 'wrong-password',
40 | ]);
41 |
42 | $response->assertSessionHasErrors();
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/tests/Feature/Auth/PasswordResetTest.php:
--------------------------------------------------------------------------------
1 | get('/forgot-password');
18 |
19 | $response->assertStatus(200);
20 | }
21 |
22 | public function test_reset_password_link_can_be_requested(): void
23 | {
24 | Notification::fake();
25 |
26 | $user = User::factory()->create();
27 |
28 | $this->post('/forgot-password', ['email' => $user->email]);
29 |
30 | Notification::assertSentTo($user, ResetPassword::class);
31 | }
32 |
33 | public function test_reset_password_screen_can_be_rendered(): void
34 | {
35 | Notification::fake();
36 |
37 | $user = User::factory()->create();
38 |
39 | $this->post('/forgot-password', ['email' => $user->email]);
40 |
41 | Notification::assertSentTo($user, ResetPassword::class, function ($notification) {
42 | $response = $this->get('/reset-password/'.$notification->token);
43 |
44 | $response->assertStatus(200);
45 |
46 | return true;
47 | });
48 | }
49 |
50 | public function test_password_can_be_reset_with_valid_token(): void
51 | {
52 | Notification::fake();
53 |
54 | $user = User::factory()->create();
55 |
56 | $this->post('/forgot-password', ['email' => $user->email]);
57 |
58 | Notification::assertSentTo($user, ResetPassword::class, function ($notification) use ($user) {
59 | $response = $this->post('/reset-password', [
60 | 'token' => $notification->token,
61 | 'email' => $user->email,
62 | 'password' => 'password',
63 | 'password_confirmation' => 'password',
64 | ]);
65 |
66 | $response
67 | ->assertSessionHasNoErrors()
68 | ->assertRedirect(route('login'));
69 |
70 | return true;
71 | });
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/tests/Feature/Auth/PasswordUpdateTest.php:
--------------------------------------------------------------------------------
1 | create();
17 |
18 | $response = $this
19 | ->actingAs($user)
20 | ->from('/profile')
21 | ->put('/password', [
22 | 'current_password' => 'password',
23 | 'password' => 'new-password',
24 | 'password_confirmation' => 'new-password',
25 | ]);
26 |
27 | $response
28 | ->assertSessionHasNoErrors()
29 | ->assertRedirect('/profile');
30 |
31 | $this->assertTrue(Hash::check('new-password', $user->refresh()->password));
32 | }
33 |
34 | public function test_correct_password_must_be_provided_to_update_password(): void
35 | {
36 | $user = User::factory()->create();
37 |
38 | $response = $this
39 | ->actingAs($user)
40 | ->from('/profile')
41 | ->put('/password', [
42 | 'current_password' => 'wrong-password',
43 | 'password' => 'new-password',
44 | 'password_confirmation' => 'new-password',
45 | ]);
46 |
47 | $response
48 | ->assertSessionHasErrors('current_password')
49 | ->assertRedirect('/profile');
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/tests/Feature/Auth/RegistrationTest.php:
--------------------------------------------------------------------------------
1 | get('/register');
15 |
16 | $response->assertStatus(200);
17 | }
18 |
19 | public function test_new_users_can_register(): void
20 | {
21 | $response = $this->post('/register', [
22 | 'name' => 'Test User',
23 | 'email' => 'test@example.com',
24 | 'password' => 'password',
25 | 'password_confirmation' => 'password',
26 | ]);
27 |
28 | $this->assertAuthenticated();
29 | $response->assertRedirect(route('dashboard', absolute: false));
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/tests/Feature/ExampleTest.php:
--------------------------------------------------------------------------------
1 | get('/');
16 |
17 | $response->assertStatus(200);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/tests/Feature/ProfileTest.php:
--------------------------------------------------------------------------------
1 | create();
16 |
17 | $response = $this
18 | ->actingAs($user)
19 | ->get('/profile');
20 |
21 | $response->assertOk();
22 | }
23 |
24 | public function test_profile_information_can_be_updated(): void
25 | {
26 | $user = User::factory()->create();
27 |
28 | $response = $this
29 | ->actingAs($user)
30 | ->patch('/profile', [
31 | 'name' => 'Test User',
32 | 'email' => 'test@example.com',
33 | ]);
34 |
35 | $response
36 | ->assertSessionHasNoErrors()
37 | ->assertRedirect('/profile');
38 |
39 | $user->refresh();
40 |
41 | $this->assertSame('Test User', $user->name);
42 | $this->assertSame('test@example.com', $user->email);
43 | $this->assertNull($user->email_verified_at);
44 | }
45 |
46 | public function test_email_verification_status_is_unchanged_when_the_email_address_is_unchanged(): void
47 | {
48 | $user = User::factory()->create();
49 |
50 | $response = $this
51 | ->actingAs($user)
52 | ->patch('/profile', [
53 | 'name' => 'Test User',
54 | 'email' => $user->email,
55 | ]);
56 |
57 | $response
58 | ->assertSessionHasNoErrors()
59 | ->assertRedirect('/profile');
60 |
61 | $this->assertNotNull($user->refresh()->email_verified_at);
62 | }
63 |
64 | public function test_user_can_delete_their_account(): void
65 | {
66 | $user = User::factory()->create();
67 |
68 | $response = $this
69 | ->actingAs($user)
70 | ->delete('/profile', [
71 | 'password' => 'password',
72 | ]);
73 |
74 | $response
75 | ->assertSessionHasNoErrors()
76 | ->assertRedirect('/');
77 |
78 | $this->assertGuest();
79 | $this->assertNull($user->fresh());
80 | }
81 |
82 | public function test_correct_password_must_be_provided_to_delete_account(): void
83 | {
84 | $user = User::factory()->create();
85 |
86 | $response = $this
87 | ->actingAs($user)
88 | ->from('/profile')
89 | ->delete('/profile', [
90 | 'password' => 'wrong-password',
91 | ]);
92 |
93 | $response
94 | ->assertSessionHasErrors('password')
95 | ->assertRedirect('/profile');
96 |
97 | $this->assertNotNull($user->fresh());
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/tests/TestCase.php:
--------------------------------------------------------------------------------
1 | assertTrue(true);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite';
2 | import laravel from 'laravel-vite-plugin';
3 | import react from '@vitejs/plugin-react';
4 |
5 | export default defineConfig({
6 | plugins: [
7 | laravel({
8 | input: 'resources/js/app.jsx',
9 | refresh: true,
10 | }),
11 | react(),
12 | ],
13 | });
14 |
--------------------------------------------------------------------------------
34 |
35 | );
36 | }
37 |
--------------------------------------------------------------------------------
/resources/js/Pages/Profile/Partials/DeleteUserForm.jsx:
--------------------------------------------------------------------------------
1 | import { useRef, useState } from 'react';
2 | import DangerButton from '@/Components/DangerButton';
3 | import InputError from '@/Components/InputError';
4 | import InputLabel from '@/Components/InputLabel';
5 | import Modal from '@/Components/Modal';
6 | import SecondaryButton from '@/Components/SecondaryButton';
7 | import TextInput from '@/Components/TextInput';
8 | import { useForm } from '@inertiajs/react';
9 |
10 | export default function DeleteUserForm({ className = '' }) {
11 | const [confirmingUserDeletion, setConfirmingUserDeletion] = useState(false);
12 | const passwordInput = useRef();
13 |
14 | const {
15 | data,
16 | setData,
17 | delete: destroy,
18 | processing,
19 | reset,
20 | errors,
21 | } = useForm({
22 | password: '',
23 | });
24 |
25 | const confirmUserDeletion = () => {
26 | setConfirmingUserDeletion(true);
27 | };
28 |
29 | const deleteUser = (e) => {
30 | e.preventDefault();
31 |
32 | destroy(route('profile.destroy'), {
33 | preserveScroll: true,
34 | onSuccess: () => closeModal(),
35 | onError: () => passwordInput.current.focus(),
36 | onFinish: () => reset(),
37 | });
38 | };
39 |
40 | const closeModal = () => {
41 | setConfirmingUserDeletion(false);
42 |
43 | reset();
44 | };
45 |
46 | return (
47 |
48 |
49 | Delete Account
50 |
51 |
52 | Once your account is deleted, all of its resources and data will be permanently deleted. Before
53 | deleting your account, please download any data or information that you wish to retain.
54 |
55 |
56 |
57 | Delete Account
58 |
59 |
60 |
96 |
97 |
98 | );
99 | }
100 |
--------------------------------------------------------------------------------
/resources/js/Utils/Permission.jsx:
--------------------------------------------------------------------------------
1 | import { usePage } from "@inertiajs/react";
2 |
3 | export default function hasAnyPermission(permissions) {
4 |
5 | // destruct auth from usepage props
6 | const { auth } = usePage().props
7 |
8 | // get all permissions from props auth.permissions
9 | let allPermissions = auth.permissions;
10 |
11 | // define has permission is false
12 | let hasPermission = false;
13 |
14 | // loop permissions
15 | permissions.forEach(function (item) {
16 | // do it if permission is match with key
17 | if (allPermissions[item])
18 | // assign hasPermission to true
19 | hasPermission = true;
20 | });
21 |
22 | // return has permissions
23 | return hasPermission;
24 | }
25 |
--------------------------------------------------------------------------------
/resources/js/app.jsx:
--------------------------------------------------------------------------------
1 | import './bootstrap';
2 | import '../css/app.css';
3 |
4 | import { createRoot } from 'react-dom/client';
5 | import { createInertiaApp } from '@inertiajs/react';
6 | import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
7 | import { ThemeSwitcherProvider } from './Context/ThemeSwitcherContext';
8 | const appName = import.meta.env.VITE_APP_NAME || 'Laravel';
9 |
10 | createInertiaApp({
11 | title: (title) => `${title} - ${appName}`,
12 | resolve: (name) => resolvePageComponent(`./Pages/${name}.jsx`, import.meta.glob('./Pages/**/*.jsx')),
13 | setup({ el, App, props }) {
14 | const root = createRoot(el);
15 |
16 | root.render(
17 |
18 |
19 |
20 | );
21 | },
22 | progress: {
23 | color: '#4B5563',
24 | },
25 | });
26 |
--------------------------------------------------------------------------------
/resources/js/bootstrap.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | window.axios = axios;
3 |
4 | window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
5 |
--------------------------------------------------------------------------------
/resources/views/app.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |