├── public ├── favicon.ico ├── robots.txt ├── img │ ├── hero.jpg │ └── brand │ │ ├── logo-1.png │ │ └── logo-2.png ├── mix-manifest.json ├── .htaccess └── index.php ├── database ├── .gitignore ├── seeders │ ├── RoleSeeder.php │ ├── ReservationSeeder.php │ ├── DatabaseSeeder.php │ ├── GalerySeeder.php │ ├── AboutSeeder.php │ ├── UserSeeder.php │ ├── AdminSeeder.php │ ├── ReceptionistSeeder.php │ ├── FacilityReviewSeeder.php │ └── RoomReviewSeeder.php ├── migrations │ ├── 2014_10_12_100000_create_password_resets_table.php │ ├── 2022_02_21_035815_create_abouts_table.php │ ├── 2022_02_18_033313_create_galeries_table.php │ ├── 2019_08_19_000000_create_failed_jobs_table.php │ ├── 2019_12_14_000001_create_personal_access_tokens_table.php │ ├── 2022_02_18_032011_create_room_has_facilities_table.php │ ├── 2014_10_12_000000_create_users_table.php │ ├── 2022_02_18_031926_create_facilities_table.php │ ├── 2022_02_18_033115_create_room_reviews_table.php │ ├── 2022_02_18_031827_create_rooms_table.php │ ├── 2022_02_18_033227_create_facility_reviews_table.php │ └── 2022_02_18_032108_create_reservations_table.php └── factories │ └── ReservationFactory.php ├── bootstrap ├── cache │ └── .gitignore └── app.php ├── storage ├── logs │ └── .gitignore ├── app │ ├── public │ │ └── .gitignore │ └── .gitignore ├── framework │ ├── testing │ │ └── .gitignore │ ├── views │ │ └── .gitignore │ ├── cache │ │ ├── data │ │ │ └── .gitignore │ │ └── .gitignore │ ├── sessions │ │ └── .gitignore │ └── .gitignore └── fonts │ ├── poppins_500_1321f10338f0e66434dee30474b658c6.ttf │ ├── poppins_600_b9eb07b2d5e0d6ec53e0c7454d6da738.ttf │ ├── poppins_800_a738af64a68a81d69351fd2852c747fa.ttf │ ├── poppins_900_9cefc0eae02097ad98f530c2cc1a3051.ttf │ ├── poppins_bold_539ab661c3f4224a64ba7fb9f8385177.ttf │ ├── poppins_italic_8e519e1cd8995931e6b5447247f375f3.ttf │ ├── poppins_normal_a7d2460db3514249ea0808da6101365c.ttf │ ├── quicksand_300_17927a71ba5c5ac68488522d7ef3a372.ttf │ ├── quicksand_500_9b172ae5c8ff3d067dd6827b5b221d41.ttf │ ├── quicksand_600_bedce1e68f2c48681070bd30620ff411.ttf │ ├── quicksand_bold_f79ce65afeac35a6f4a687d626f8ceba.ttf │ ├── quicksand_normal_004b3e7d8a21eabe3a9b356b2ce788b8.ttf │ ├── poppins_500_italic_dbc1d05fdcb2e525d3b7bf59ea8e348b.ttf │ ├── poppins_600_italic_8cf665432d4a9aaeac4cc5ce36521613.ttf │ ├── poppins_800_italic_9c2bea87c3e399f4527f575ac7034380.ttf │ ├── poppins_900_italic_8251479c5063296fe468f2fd3fab720e.ttf │ └── poppins_bold_italic_89b1cde9b84ed81fef9dbbe6fa9405aa.ttf ├── resources ├── js │ ├── app.js │ └── bootstrap.js ├── views │ ├── components │ │ ├── label.blade.php │ │ ├── auth-session-status.blade.php │ │ ├── dropdown-link.blade.php │ │ ├── input.blade.php │ │ ├── auth-card.blade.php │ │ ├── table.blade.php │ │ ├── button.blade.php │ │ ├── auth-validation-errors.blade.php │ │ ├── nav-link.blade.php │ │ ├── responsive-nav-link.blade.php │ │ ├── dropdown.blade.php │ │ └── application-logo.blade.php │ ├── layouts │ │ └── guest.blade.php │ ├── livewire │ │ ├── about.blade.php │ │ ├── room │ │ │ ├── review │ │ │ │ └── edit.blade.php │ │ │ └── index.blade.php │ │ ├── facility │ │ │ └── review │ │ │ │ └── edit.blade.php │ │ └── dashboard │ │ │ ├── user │ │ │ └── index.blade.php │ │ │ └── admin │ │ │ └── galery │ │ │ └── create.blade.php │ └── auth │ │ ├── confirm-password.blade.php │ │ ├── forgot-password.blade.php │ │ ├── verify-email.blade.php │ │ ├── reset-password.blade.php │ │ ├── login.blade.php │ │ └── register.blade.php └── css │ └── app.css ├── .gitattributes ├── tests ├── TestCase.php ├── Unit │ └── ExampleTest.php ├── Feature │ ├── ExampleTest.php │ └── Auth │ │ ├── RegistrationTest.php │ │ ├── AuthenticationTest.php │ │ ├── PasswordConfirmationTest.php │ │ ├── EmailVerificationTest.php │ │ └── PasswordResetTest.php └── CreatesApplication.php ├── .styleci.yml ├── .gitignore ├── app ├── Models │ ├── About.php │ ├── Galery.php │ ├── RoomHasFacility.php │ ├── RoomReview.php │ ├── FacilityReview.php │ ├── Facility.php │ ├── Room.php │ ├── Reservation.php │ └── User.php ├── Http │ ├── Middleware │ │ ├── EncryptCookies.php │ │ ├── VerifyCsrfToken.php │ │ ├── PreventRequestsDuringMaintenance.php │ │ ├── TrustHosts.php │ │ ├── TrimStrings.php │ │ ├── Authenticate.php │ │ ├── TrustProxies.php │ │ └── RedirectIfAuthenticated.php │ ├── Livewire │ │ ├── About.php │ │ ├── Room │ │ │ ├── Index.php │ │ │ └── Review │ │ │ │ ├── Edit.php │ │ │ │ └── Create.php │ │ ├── Dashboard │ │ │ ├── User │ │ │ │ ├── Reservation │ │ │ │ │ ├── Proof.php │ │ │ │ │ └── Index.php │ │ │ │ ├── Index.php │ │ │ │ └── Review │ │ │ │ │ ├── Room │ │ │ │ │ └── Index.php │ │ │ │ │ └── Facility │ │ │ │ │ └── Index.php │ │ │ ├── Receptionist │ │ │ │ ├── Reservation │ │ │ │ │ ├── Proof.php │ │ │ │ │ └── Index.php │ │ │ │ └── Index.php │ │ │ └── Admin │ │ │ │ ├── Galery │ │ │ │ ├── Create.php │ │ │ │ └── Index.php │ │ │ │ ├── Facility │ │ │ │ ├── Create.php │ │ │ │ ├── Index.php │ │ │ │ └── Edit.php │ │ │ │ ├── Room │ │ │ │ ├── Index.php │ │ │ │ ├── Create.php │ │ │ │ └── Edit.php │ │ │ │ ├── Index.php │ │ │ │ └── User │ │ │ │ └── Create.php │ │ └── Facility │ │ │ ├── Index.php │ │ │ └── Review │ │ │ ├── Edit.php │ │ │ └── Create.php │ ├── Controllers │ │ ├── Controller.php │ │ └── Auth │ │ │ ├── EmailVerificationPromptController.php │ │ │ ├── EmailVerificationNotificationController.php │ │ │ ├── PasswordResetLinkController.php │ │ │ ├── ConfirmablePasswordController.php │ │ │ ├── AuthenticatedSessionController.php │ │ │ ├── VerifyEmailController.php │ │ │ ├── RegisteredUserController.php │ │ │ └── NewPasswordController.php │ ├── Requests │ │ └── Auth │ │ │ └── LoginRequest.php │ └── Kernel.php ├── View │ └── Components │ │ ├── AppLayout.php │ │ ├── GuestLayout.php │ │ ├── LayoutMain.php │ │ └── Nav.php ├── Providers │ ├── BroadcastServiceProvider.php │ ├── AppServiceProvider.php │ ├── AuthServiceProvider.php │ ├── EventServiceProvider.php │ └── RouteServiceProvider.php ├── Console │ └── Kernel.php ├── Exceptions │ └── Handler.php └── Rules │ └── PhoneNumber.php ├── .editorconfig ├── lang ├── en │ ├── pagination.php │ ├── auth.php │ └── passwords.php └── en.json ├── routes ├── channels.php ├── api.php ├── console.php └── auth.php ├── tailwind.config.js ├── webpack.mix.js ├── package.json ├── config ├── cors.php ├── services.php ├── view.php ├── hashing.php ├── broadcasting.php ├── sanctum.php ├── filesystems.php └── queue.php ├── .env.example ├── phpunit.xml ├── artisan └── composer.json /public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /database/.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite* 2 | -------------------------------------------------------------------------------- /bootstrap/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /storage/app/public/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/app/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !public/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /storage/framework/testing/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/views/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/cache/data/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/sessions/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !data/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /public/img/hero.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kimmyxpow/hollux/HEAD/public/img/hero.jpg -------------------------------------------------------------------------------- /public/img/brand/logo-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kimmyxpow/hollux/HEAD/public/img/brand/logo-1.png -------------------------------------------------------------------------------- /public/img/brand/logo-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kimmyxpow/hollux/HEAD/public/img/brand/logo-2.png -------------------------------------------------------------------------------- /public/mix-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "/js/app.js": "/js/app.js", 3 | "/css/app.css": "/css/app.css" 4 | } 5 | -------------------------------------------------------------------------------- /resources/js/app.js: -------------------------------------------------------------------------------- 1 | require('./bootstrap'); 2 | 3 | import Alpine from 'alpinejs'; 4 | 5 | window.Alpine = Alpine; 6 | 7 | Alpine.start(); 8 | -------------------------------------------------------------------------------- /storage/fonts/poppins_500_1321f10338f0e66434dee30474b658c6.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kimmyxpow/hollux/HEAD/storage/fonts/poppins_500_1321f10338f0e66434dee30474b658c6.ttf -------------------------------------------------------------------------------- /storage/fonts/poppins_600_b9eb07b2d5e0d6ec53e0c7454d6da738.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kimmyxpow/hollux/HEAD/storage/fonts/poppins_600_b9eb07b2d5e0d6ec53e0c7454d6da738.ttf -------------------------------------------------------------------------------- /storage/fonts/poppins_800_a738af64a68a81d69351fd2852c747fa.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kimmyxpow/hollux/HEAD/storage/fonts/poppins_800_a738af64a68a81d69351fd2852c747fa.ttf -------------------------------------------------------------------------------- /storage/fonts/poppins_900_9cefc0eae02097ad98f530c2cc1a3051.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kimmyxpow/hollux/HEAD/storage/fonts/poppins_900_9cefc0eae02097ad98f530c2cc1a3051.ttf -------------------------------------------------------------------------------- /storage/fonts/poppins_bold_539ab661c3f4224a64ba7fb9f8385177.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kimmyxpow/hollux/HEAD/storage/fonts/poppins_bold_539ab661c3f4224a64ba7fb9f8385177.ttf -------------------------------------------------------------------------------- /storage/fonts/poppins_italic_8e519e1cd8995931e6b5447247f375f3.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kimmyxpow/hollux/HEAD/storage/fonts/poppins_italic_8e519e1cd8995931e6b5447247f375f3.ttf -------------------------------------------------------------------------------- /storage/fonts/poppins_normal_a7d2460db3514249ea0808da6101365c.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kimmyxpow/hollux/HEAD/storage/fonts/poppins_normal_a7d2460db3514249ea0808da6101365c.ttf -------------------------------------------------------------------------------- /storage/fonts/quicksand_300_17927a71ba5c5ac68488522d7ef3a372.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kimmyxpow/hollux/HEAD/storage/fonts/quicksand_300_17927a71ba5c5ac68488522d7ef3a372.ttf -------------------------------------------------------------------------------- /storage/fonts/quicksand_500_9b172ae5c8ff3d067dd6827b5b221d41.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kimmyxpow/hollux/HEAD/storage/fonts/quicksand_500_9b172ae5c8ff3d067dd6827b5b221d41.ttf -------------------------------------------------------------------------------- /storage/fonts/quicksand_600_bedce1e68f2c48681070bd30620ff411.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kimmyxpow/hollux/HEAD/storage/fonts/quicksand_600_bedce1e68f2c48681070bd30620ff411.ttf -------------------------------------------------------------------------------- /storage/fonts/quicksand_bold_f79ce65afeac35a6f4a687d626f8ceba.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kimmyxpow/hollux/HEAD/storage/fonts/quicksand_bold_f79ce65afeac35a6f4a687d626f8ceba.ttf -------------------------------------------------------------------------------- /storage/fonts/quicksand_normal_004b3e7d8a21eabe3a9b356b2ce788b8.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kimmyxpow/hollux/HEAD/storage/fonts/quicksand_normal_004b3e7d8a21eabe3a9b356b2ce788b8.ttf -------------------------------------------------------------------------------- /storage/fonts/poppins_500_italic_dbc1d05fdcb2e525d3b7bf59ea8e348b.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kimmyxpow/hollux/HEAD/storage/fonts/poppins_500_italic_dbc1d05fdcb2e525d3b7bf59ea8e348b.ttf -------------------------------------------------------------------------------- /storage/fonts/poppins_600_italic_8cf665432d4a9aaeac4cc5ce36521613.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kimmyxpow/hollux/HEAD/storage/fonts/poppins_600_italic_8cf665432d4a9aaeac4cc5ce36521613.ttf -------------------------------------------------------------------------------- /storage/fonts/poppins_800_italic_9c2bea87c3e399f4527f575ac7034380.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kimmyxpow/hollux/HEAD/storage/fonts/poppins_800_italic_9c2bea87c3e399f4527f575ac7034380.ttf -------------------------------------------------------------------------------- /storage/fonts/poppins_900_italic_8251479c5063296fe468f2fd3fab720e.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kimmyxpow/hollux/HEAD/storage/fonts/poppins_900_italic_8251479c5063296fe468f2fd3fab720e.ttf -------------------------------------------------------------------------------- /storage/fonts/poppins_bold_italic_89b1cde9b84ed81fef9dbbe6fa9405aa.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kimmyxpow/hollux/HEAD/storage/fonts/poppins_bold_italic_89b1cde9b84ed81fef9dbbe6fa9405aa.ttf -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /resources/views/components/label.blade.php: -------------------------------------------------------------------------------- 1 | @props(['value']) 2 | 3 | 6 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 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 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | merge(['class' => 'font-medium text-sm text-green-600']) }}> 5 | {{ $status }} 6 | 7 | @endif 8 | -------------------------------------------------------------------------------- /.styleci.yml: -------------------------------------------------------------------------------- 1 | php: 2 | preset: laravel 3 | version: 8 4 | disabled: 5 | - no_unused_imports 6 | finder: 7 | not-name: 8 | - index.php 9 | js: 10 | finder: 11 | not-name: 12 | - webpack.mix.js 13 | css: true 14 | -------------------------------------------------------------------------------- /resources/views/components/dropdown-link.blade.php: -------------------------------------------------------------------------------- 1 | merge(['class' => 'block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 transition duration-150 ease-in-out']) }}>{{ $slot }} 2 | -------------------------------------------------------------------------------- /resources/views/components/input.blade.php: -------------------------------------------------------------------------------- 1 | @props(['disabled' => false]) 2 | 3 | merge(['class' => 'rounded-md shadow-sm border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50']) !!}> 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /public/hot 3 | /public/storage 4 | /storage/*.key 5 | /vendor 6 | .env 7 | .env.backup 8 | .phpunit.result.cache 9 | docker-compose.override.yml 10 | Homestead.json 11 | Homestead.yaml 12 | npm-debug.log 13 | yarn-error.log 14 | /.idea 15 | /.vscode 16 | -------------------------------------------------------------------------------- /app/Models/About.php: -------------------------------------------------------------------------------- 1 | 2 |
3 | {{ $logo }} 4 |
5 | 6 |
7 | {{ $slot }} 8 |
9 | 10 | -------------------------------------------------------------------------------- /tests/Unit/ExampleTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(true); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/Models/Galery.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 | 6 | {{ $slot }} 7 |
8 |
9 |
10 |
11 | -------------------------------------------------------------------------------- /app/Http/Middleware/EncryptCookies.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | protected $except = [ 15 | // 16 | ]; 17 | } 18 | -------------------------------------------------------------------------------- /app/View/Components/AppLayout.php: -------------------------------------------------------------------------------- 1 | ModelsAbout::first() 14 | ])->layout('layouts.main', ['title' => 'About Hollux | Hollux']); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/Http/Middleware/VerifyCsrfToken.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | protected $except = [ 15 | // 16 | ]; 17 | } 18 | -------------------------------------------------------------------------------- /resources/views/components/button.blade.php: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /app/Http/Controllers/Controller.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | protected $except = [ 15 | // 16 | ]; 17 | } 18 | -------------------------------------------------------------------------------- /app/Http/Middleware/TrustHosts.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | public function hosts() 15 | { 16 | return [ 17 | $this->allSubdomainsOfApplicationUrl(), 18 | ]; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/Http/Middleware/TrimStrings.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | protected $except = [ 15 | 'current_password', 16 | 'password', 17 | 'password_confirmation', 18 | ]; 19 | } 20 | -------------------------------------------------------------------------------- /tests/Feature/ExampleTest.php: -------------------------------------------------------------------------------- 1 | get('/'); 18 | 19 | $response->assertStatus(200); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/CreatesApplication.php: -------------------------------------------------------------------------------- 1 | make(Kernel::class)->bootstrap(); 19 | 20 | return $app; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/Providers/BroadcastServiceProvider.php: -------------------------------------------------------------------------------- 1 | any()) 4 |
5 |
6 | {{ __('Whoops! Something went wrong.') }} 7 |
8 | 9 | 14 |
15 | @endif 16 | -------------------------------------------------------------------------------- /app/Http/Livewire/Room/Index.php: -------------------------------------------------------------------------------- 1 | layout('layouts.main', ['title' => 'Rooms | Hollux']); 15 | } 16 | 17 | public function mount() 18 | { 19 | $this->fill([ 20 | 'rooms' => Room::orderBy('created_at')->get() 21 | ]); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/Providers/AppServiceProvider.php: -------------------------------------------------------------------------------- 1 | 'admin']); 19 | Role::create(['name' => 'user']); 20 | Role::create(['name' => 'receptionist']); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/Http/Middleware/Authenticate.php: -------------------------------------------------------------------------------- 1 | expectsJson()) { 18 | return route('login'); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /resources/views/components/nav-link.blade.php: -------------------------------------------------------------------------------- 1 | @props(['active']) 2 | 3 | @php 4 | $classes = ($active ?? false) 5 | ? "text-sm font-medium block after:content-[''] after:block after:h-0.5 after:bg-gray-800 text-gray-600 after:rounded-full after:w-4/5 after:transition-all after:duration-300" 6 | : "text-sm font-medium block after:content-[''] after:block after:h-0.5 after:bg-gray-800 text-gray-600 after:rounded-full after:w-0 hover:after:w-4/5 after:transition-all after:duration-300"; 7 | @endphp 8 | 9 | merge(['class' => $classes]) }}> 10 | {{ $slot }} 11 | 12 | -------------------------------------------------------------------------------- /app/View/Components/LayoutMain.php: -------------------------------------------------------------------------------- 1 | '« Previous', 17 | 'next' => 'Next »', 18 | 19 | ]; 20 | -------------------------------------------------------------------------------- /routes/channels.php: -------------------------------------------------------------------------------- 1 | id === (int) $id; 18 | }); 19 | -------------------------------------------------------------------------------- /routes/api.php: -------------------------------------------------------------------------------- 1 | get('/user', function (Request $request) { 18 | return $request->user(); 19 | }); 20 | -------------------------------------------------------------------------------- /routes/console.php: -------------------------------------------------------------------------------- 1 | comment(Inspiring::quote()); 19 | })->purpose('Display an inspiring quote'); 20 | -------------------------------------------------------------------------------- /lang/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "The :attribute must contain at least one letter.": "The :attribute must contain at least one letter.", 3 | "The :attribute must contain at least one number.": "The :attribute must contain at least one number.", 4 | "The :attribute must contain at least one symbol.": "The :attribute must contain at least one symbol.", 5 | "The :attribute must contain at least one uppercase and one lowercase letter.": "The :attribute must contain at least one uppercase and one lowercase letter.", 6 | "The given :attribute has appeared in a data leak. Please choose a different :attribute.": "The given :attribute has appeared in a data leak. Please choose a different :attribute." 7 | } 8 | -------------------------------------------------------------------------------- /app/Http/Livewire/Dashboard/User/Reservation/Proof.php: -------------------------------------------------------------------------------- 1 | check_in)->diffInDays($reservation->check_out); 15 | $pdf = Pdf::loadView('livewire.dashboard.user.reservation.proof', ['reservation' => $reservation, 'total_days' => $total_days]); 16 | return $pdf->stream($reservation->code . '.pdf'); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | const defaultTheme = require('tailwindcss/defaultTheme'); 2 | 3 | module.exports = { 4 | content: [ 5 | "./vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php", 6 | "./storage/framework/views/*.php", 7 | "./resources/views/**/*.blade.php", 8 | ], 9 | 10 | theme: { 11 | extend: { 12 | fontFamily: { 13 | sans: ["Quicksand", ...defaultTheme.fontFamily.sans], 14 | }, 15 | }, 16 | }, 17 | 18 | plugins: [ 19 | require("@tailwindcss/forms"), 20 | require("@tailwindcss/typography"), 21 | require("@tailwindcss/line-clamp") 22 | ], 23 | }; 24 | -------------------------------------------------------------------------------- /app/Http/Livewire/Dashboard/Receptionist/Reservation/Proof.php: -------------------------------------------------------------------------------- 1 | check_in)->diffInDays($reservation->check_out); 15 | $pdf = Pdf::loadView('livewire.dashboard.receptionist.reservation.proof', ['reservation' => $reservation, 'total_days' => $total_days]); 16 | return $pdf->stream($reservation->code . '.pdf'); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /webpack.mix.js: -------------------------------------------------------------------------------- 1 | const mix = require('laravel-mix'); 2 | 3 | /* 4 | |-------------------------------------------------------------------------- 5 | | Mix Asset Management 6 | |-------------------------------------------------------------------------- 7 | | 8 | | Mix provides a clean, fluent API for defining some Webpack build steps 9 | | for your Laravel applications. By default, we are compiling the CSS 10 | | file for the application as well as bundling up all the JS files. 11 | | 12 | */ 13 | 14 | mix.js('resources/js/app.js', 'public/js').postCss('resources/css/app.css', 'public/css', [ 15 | require('postcss-import'), 16 | require('tailwindcss'), 17 | require('autoprefixer'), 18 | ]); 19 | -------------------------------------------------------------------------------- /resources/views/components/responsive-nav-link.blade.php: -------------------------------------------------------------------------------- 1 | @props(['active']) 2 | 3 | @php 4 | $classes = ($active ?? false) 5 | ? 'block pl-3 pr-4 py-2 border-l-4 border-indigo-400 text-base font-medium text-indigo-700 bg-indigo-50 focus:outline-none focus:text-indigo-800 focus:bg-indigo-100 focus:border-indigo-700 transition duration-150 ease-in-out' 6 | : 'block pl-3 pr-4 py-2 border-l-4 border-transparent text-base font-medium text-gray-600 hover:text-gray-800 hover:bg-gray-50 hover:border-gray-300 focus:outline-none focus:text-gray-800 focus:bg-gray-50 focus:border-gray-300 transition duration-150 ease-in-out'; 7 | @endphp 8 | 9 | merge(['class' => $classes]) }}> 10 | {{ $slot }} 11 | 12 | -------------------------------------------------------------------------------- /lang/en/auth.php: -------------------------------------------------------------------------------- 1 | 'These credentials do not match our records.', 17 | 'password' => 'The provided password is incorrect.', 18 | 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', 19 | 20 | ]; 21 | -------------------------------------------------------------------------------- /app/Providers/AuthServiceProvider.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | protected $policies = [ 16 | // 'App\Models\Model' => 'App\Policies\ModelPolicy', 17 | ]; 18 | 19 | /** 20 | * Register any authentication / authorization services. 21 | * 22 | * @return void 23 | */ 24 | public function boot() 25 | { 26 | $this->registerPolicies(); 27 | 28 | // 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/Http/Middleware/TrustProxies.php: -------------------------------------------------------------------------------- 1 | |string|null 14 | */ 15 | protected $proxies; 16 | 17 | /** 18 | * The headers that should be used to detect proxies. 19 | * 20 | * @var int 21 | */ 22 | protected $headers = 23 | Request::HEADER_X_FORWARDED_FOR | 24 | Request::HEADER_X_FORWARDED_HOST | 25 | Request::HEADER_X_FORWARDED_PORT | 26 | Request::HEADER_X_FORWARDED_PROTO | 27 | Request::HEADER_X_FORWARDED_AWS_ELB; 28 | } 29 | -------------------------------------------------------------------------------- /app/Console/Kernel.php: -------------------------------------------------------------------------------- 1 | command('inspire')->hourly(); 19 | } 20 | 21 | /** 22 | * Register the commands for the application. 23 | * 24 | * @return void 25 | */ 26 | protected function commands() 27 | { 28 | $this->load(__DIR__.'/Commands'); 29 | 30 | require base_path('routes/console.php'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lang/en/passwords.php: -------------------------------------------------------------------------------- 1 | 'Your password has been reset!', 17 | 'sent' => 'We have emailed your password reset link!', 18 | 'throttled' => 'Please wait before retrying.', 19 | 'token' => 'This password reset token is invalid.', 20 | 'user' => "We can't find a user with that email address.", 21 | 22 | ]; 23 | -------------------------------------------------------------------------------- /app/Http/Livewire/Dashboard/User/Index.php: -------------------------------------------------------------------------------- 1 | layoutData(['title' => 'User Dashboard | Hollux']); 18 | } 19 | 20 | public function mount() 21 | { 22 | $this->fill([ 23 | 'totalFacilityReviews' => auth()->user()->facility_reviews->count(), 24 | 'totalRoomReviews' => auth()->user()->room_reviews->count(), 25 | 'totalReservations' => auth()->user()->reservations->count(), 26 | ]); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /database/migrations/2014_10_12_100000_create_password_resets_table.php: -------------------------------------------------------------------------------- 1 | string('email')->index(); 18 | $table->string('token'); 19 | $table->timestamp('created_at')->nullable(); 20 | }); 21 | } 22 | 23 | /** 24 | * Reverse the migrations. 25 | * 26 | * @return void 27 | */ 28 | public function down() 29 | { 30 | Schema::dropIfExists('password_resets'); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /database/seeders/ReservationSeeder.php: -------------------------------------------------------------------------------- 1 | create(); 20 | 21 | for ($i=1; $i <= 14; $i++) { 22 | $room = Room::find($i); 23 | 24 | $available = (int) $room->total_rooms - (int) array_sum($room->reservations->where('status', '<>', 'canceled')->where('status', '<>', 'check out')->pluck('total_rooms')->toArray()); 25 | 26 | $room->update(['available' => $available]); 27 | } 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /database/migrations/2022_02_21_035815_create_abouts_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->string('title'); 19 | $table->text('text'); 20 | $table->string('image'); 21 | $table->timestamps(); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | * 28 | * @return void 29 | */ 30 | public function down() 31 | { 32 | Schema::dropIfExists('abouts'); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev": "npm run development", 5 | "development": "mix", 6 | "watch": "mix watch", 7 | "watch-poll": "mix watch -- --watch-options-poll=1000", 8 | "hot": "mix watch --hot", 9 | "prod": "npm run production", 10 | "production": "mix --production" 11 | }, 12 | "devDependencies": { 13 | "@tailwindcss/forms": "^0.4.0", 14 | "@tailwindcss/typography": "^0.5.2", 15 | "alpinejs": "^3.4.2", 16 | "autoprefixer": "^10.4.2", 17 | "axios": "^0.25", 18 | "laravel-mix": "^6.0.6", 19 | "lodash": "^4.17.19", 20 | "postcss": "^8.4.6", 21 | "postcss-import": "^14.0.2", 22 | "tailwindcss": "^3.0.18" 23 | }, 24 | "dependencies": { 25 | "@tailwindcss/line-clamp": "^0.3.1" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /database/migrations/2022_02_18_033313_create_galeries_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->string('code'); 19 | $table->string('image'); 20 | $table->string('title'); 21 | $table->timestamps(); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | * 28 | * @return void 29 | */ 30 | public function down() 31 | { 32 | Schema::dropIfExists('galeries'); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /resources/views/layouts/guest.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {{ config('app.name', 'Laravel') }} 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | {{ $slot }} 22 |
23 | 24 | 25 | -------------------------------------------------------------------------------- /app/Models/RoomHasFacility.php: -------------------------------------------------------------------------------- 1 | belongsTo(Room::class); 23 | } 24 | 25 | /** 26 | * Get the facility that owns the RoomHasFacility 27 | * 28 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo 29 | */ 30 | public function facility(): BelongsTo 31 | { 32 | return $this->belongsTo(Facility::class, 'facility_code', 'code'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /database/seeders/DatabaseSeeder.php: -------------------------------------------------------------------------------- 1 | call([ 19 | GalerySeeder::class, 20 | FacilitySeeder::class, 21 | RoomSeeder::class, 22 | RoomHasFacilitySeeder::class, 23 | AboutSeeder::class, 24 | RoleSeeder::class, 25 | UserSeeder::class, 26 | AdminSeeder::class, 27 | ReceptionistSeeder::class, 28 | RoomReviewSeeder::class, 29 | FacilityReviewSeeder::class, 30 | ReservationSeeder::class 31 | ]); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/Feature/Auth/RegistrationTest.php: -------------------------------------------------------------------------------- 1 | get('/register'); 16 | 17 | $response->assertStatus(200); 18 | } 19 | 20 | public function test_new_users_can_register() 21 | { 22 | $response = $this->post('/register', [ 23 | 'name' => 'Test User', 24 | 'email' => 'test@example.com', 25 | 'password' => 'password', 26 | 'password_confirmation' => 'password', 27 | ]); 28 | 29 | $this->assertAuthenticated(); 30 | $response->assertRedirect(RouteServiceProvider::HOME); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /database/seeders/GalerySeeder.php: -------------------------------------------------------------------------------- 1 | bin2hex(random_bytes(20)), 23 | 'title' => $galeries[$i - 1], 24 | 'image' => 'img/galeries/g-' . $i . '.jpg' 25 | ]); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /config/cors.php: -------------------------------------------------------------------------------- 1 | ['api/*', 'sanctum/csrf-cookie'], 19 | 20 | 'allowed_methods' => ['*'], 21 | 22 | 'allowed_origins' => ['*'], 23 | 24 | 'allowed_origins_patterns' => [], 25 | 26 | 'allowed_headers' => ['*'], 27 | 28 | 'exposed_headers' => [], 29 | 30 | 'max_age' => 0, 31 | 32 | 'supports_credentials' => false, 33 | 34 | ]; 35 | -------------------------------------------------------------------------------- /resources/js/bootstrap.js: -------------------------------------------------------------------------------- 1 | window._ = require('lodash'); 2 | 3 | /** 4 | * We'll load the axios HTTP library which allows us to easily issue requests 5 | * to our Laravel back-end. This library automatically handles sending the 6 | * CSRF token as a header based on the value of the "XSRF" token cookie. 7 | */ 8 | 9 | window.axios = require('axios'); 10 | 11 | window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; 12 | 13 | /** 14 | * Echo exposes an expressive API for subscribing to channels and listening 15 | * for events that are broadcast by Laravel. Echo and event broadcasting 16 | * allows your team to easily build robust real-time web applications. 17 | */ 18 | 19 | // import Echo from 'laravel-echo'; 20 | 21 | // window.Pusher = require('pusher-js'); 22 | 23 | // window.Echo = new Echo({ 24 | // broadcaster: 'pusher', 25 | // key: process.env.MIX_PUSHER_APP_KEY, 26 | // cluster: process.env.MIX_PUSHER_APP_CLUSTER, 27 | // forceTLS: true 28 | // }); 29 | -------------------------------------------------------------------------------- /app/Exceptions/Handler.php: -------------------------------------------------------------------------------- 1 | > 14 | */ 15 | protected $dontReport = [ 16 | // 17 | ]; 18 | 19 | /** 20 | * A list of the inputs that are never flashed for validation exceptions. 21 | * 22 | * @var array 23 | */ 24 | protected $dontFlash = [ 25 | 'current_password', 26 | 'password', 27 | 'password_confirmation', 28 | ]; 29 | 30 | /** 31 | * Register the exception handling callbacks for the application. 32 | * 33 | * @return void 34 | */ 35 | public function register() 36 | { 37 | $this->reportable(function (Throwable $e) { 38 | // 39 | }); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /database/migrations/2019_08_19_000000_create_failed_jobs_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->string('uuid')->unique(); 19 | $table->text('connection'); 20 | $table->text('queue'); 21 | $table->longText('payload'); 22 | $table->longText('exception'); 23 | $table->timestamp('failed_at')->useCurrent(); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | * 30 | * @return void 31 | */ 32 | public function down() 33 | { 34 | Schema::dropIfExists('failed_jobs'); 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /resources/views/livewire/about.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

{{ $about->title }}

5 |

6 | {{ $about->text }} 7 |

8 |
9 |
10 | 11 |
12 | About Hollux 13 |
14 |
15 |
16 |
-------------------------------------------------------------------------------- /database/seeders/AboutSeeder.php: -------------------------------------------------------------------------------- 1 | 'Starting From the Travelers', 20 | 'text' => 'Berawal dari kami para traveler bernama lala, lili, lele, lolo, lulu yang sedang menjelajah antartika menggunakan sepeda mobil dan dibakar ke hutan amazon oleh kucing oren dan akhirnya bertemu dengan hotel transilvania yang berada di afrika tapi ternyata yang punya orang utan, bukan orang cina. Dari situ kami terus bersama sampai alien datang ke merkurius dan mencuri dragon ball untuk dijadikan sayur sop kaum milenial. Akhirnya jadilah hotel ini.', 21 | 'image' => 'img/about/about.jpg' 22 | ]); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/Http/Middleware/RedirectIfAuthenticated.php: -------------------------------------------------------------------------------- 1 | check()) { 26 | return redirect(RouteServiceProvider::HOME); 27 | } 28 | } 29 | 30 | return $next($request); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/Rules/PhoneNumber.php: -------------------------------------------------------------------------------- 1 | = 10; 29 | } 30 | 31 | /** 32 | * Get the validation error message. 33 | * 34 | * @return string 35 | */ 36 | public function message() 37 | { 38 | return 'The :attribute field contains an invalid number'; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->morphs('tokenable'); 19 | $table->string('name'); 20 | $table->string('token', 64)->unique(); 21 | $table->text('abilities')->nullable(); 22 | $table->timestamp('last_used_at')->nullable(); 23 | $table->timestamps(); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | * 30 | * @return void 31 | */ 32 | public function down() 33 | { 34 | Schema::dropIfExists('personal_access_tokens'); 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /database/migrations/2022_02_18_032011_create_room_has_facilities_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->foreignId('room_id')->constrained()->cascadeOnDelete()->cascadeOnUpdate(); 19 | $table->string('facility_code'); 20 | $table->foreign('facility_code')->references('code')->on('facilities')->cascadeOnDelete()->cascadeOnUpdate(); 21 | $table->timestamps(); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | * 28 | * @return void 29 | */ 30 | public function down() 31 | { 32 | Schema::dropIfExists('room_has_facilities'); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /config/services.php: -------------------------------------------------------------------------------- 1 | [ 18 | 'domain' => env('MAILGUN_DOMAIN'), 19 | 'secret' => env('MAILGUN_SECRET'), 20 | 'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'), 21 | ], 22 | 23 | 'postmark' => [ 24 | 'token' => env('POSTMARK_TOKEN'), 25 | ], 26 | 27 | 'ses' => [ 28 | 'key' => env('AWS_ACCESS_KEY_ID'), 29 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 30 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 31 | ], 32 | 33 | ]; 34 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/EmailVerificationPromptController.php: -------------------------------------------------------------------------------- 1 | user()->hasVerifiedEmail()) { 20 | if (auth()->user()->hasRole('admin')) { 21 | return to_route('dashboard.admin.index'); 22 | } 23 | 24 | if (auth()->user()->hasRole('receptionist')) { 25 | return to_route('dashboard.receptionist.index'); 26 | } 27 | 28 | if (auth()->user()->hasRole('user')) { 29 | return to_route('dashboard.user.index'); 30 | } 31 | } 32 | 33 | return view('auth.verify-email'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/Models/RoomReview.php: -------------------------------------------------------------------------------- 1 | belongsTo(User::class); 33 | } 34 | 35 | /** 36 | * Get the room that owns the RoomReview 37 | * 38 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo 39 | */ 40 | public function room(): BelongsTo 41 | { 42 | return $this->belongsTo(Room::class, 'room_code', 'code'); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /database/migrations/2014_10_12_000000_create_users_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->string('code'); 19 | $table->string('name'); 20 | $table->string('email')->unique(); 21 | $table->timestamp('email_verified_at')->nullable(); 22 | $table->string('phone_number'); 23 | $table->string('password'); 24 | $table->string('avatar'); 25 | $table->rememberToken(); 26 | $table->timestamps(); 27 | }); 28 | } 29 | 30 | /** 31 | * Reverse the migrations. 32 | * 33 | * @return void 34 | */ 35 | public function down() 36 | { 37 | Schema::dropIfExists('users'); 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /app/Providers/EventServiceProvider.php: -------------------------------------------------------------------------------- 1 | > 16 | */ 17 | protected $listen = [ 18 | Registered::class => [ 19 | SendEmailVerificationNotification::class, 20 | ], 21 | ]; 22 | 23 | /** 24 | * Register any events for your application. 25 | * 26 | * @return void 27 | */ 28 | public function boot() 29 | { 30 | // 31 | } 32 | 33 | /** 34 | * Determine if events and listeners should be automatically discovered. 35 | * 36 | * @return bool 37 | */ 38 | public function shouldDiscoverEvents() 39 | { 40 | return false; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | APP_NAME=Laravel 2 | APP_ENV=local 3 | APP_KEY= 4 | APP_DEBUG=true 5 | APP_URL=http://localhost 6 | 7 | LOG_CHANNEL=stack 8 | LOG_DEPRECATIONS_CHANNEL=null 9 | LOG_LEVEL=debug 10 | 11 | DB_CONNECTION=mysql 12 | DB_HOST=127.0.0.1 13 | DB_PORT=3306 14 | DB_DATABASE=new_hollux 15 | DB_USERNAME=root 16 | DB_PASSWORD= 17 | 18 | BROADCAST_DRIVER=log 19 | CACHE_DRIVER=file 20 | FILESYSTEM_DISK=local 21 | QUEUE_CONNECTION=sync 22 | SESSION_DRIVER=file 23 | SESSION_LIFETIME=120 24 | 25 | MEMCACHED_HOST=127.0.0.1 26 | 27 | REDIS_HOST=127.0.0.1 28 | REDIS_PASSWORD=null 29 | REDIS_PORT=6379 30 | 31 | MAIL_MAILER=smtp 32 | MAIL_HOST=mailhog 33 | MAIL_PORT=1025 34 | MAIL_USERNAME=null 35 | MAIL_PASSWORD=null 36 | MAIL_ENCRYPTION=null 37 | MAIL_FROM_ADDRESS="hello@example.com" 38 | MAIL_FROM_NAME="${APP_NAME}" 39 | 40 | AWS_ACCESS_KEY_ID= 41 | AWS_SECRET_ACCESS_KEY= 42 | AWS_DEFAULT_REGION=us-east-1 43 | AWS_BUCKET= 44 | AWS_USE_PATH_STYLE_ENDPOINT=false 45 | 46 | PUSHER_APP_ID= 47 | PUSHER_APP_KEY= 48 | PUSHER_APP_SECRET= 49 | PUSHER_APP_CLUSTER=mt1 50 | 51 | MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" 52 | MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" 53 | -------------------------------------------------------------------------------- /app/Models/FacilityReview.php: -------------------------------------------------------------------------------- 1 | belongsTo(Facility::class, 'facility_code', 'code'); 33 | } 34 | 35 | /** 36 | * Get the user that owns the FacilityReview 37 | * 38 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo 39 | */ 40 | public function user(): BelongsTo 41 | { 42 | return $this->belongsTo(User::class); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/Http/Livewire/Dashboard/Admin/Galery/Create.php: -------------------------------------------------------------------------------- 1 | validate([ 24 | 'image' => ['required', 'image', 'max:2048'], 25 | 'title' => ['required'], 26 | ]); 27 | 28 | $validatedData['image'] = $this->image->store('img/galeries'); 29 | $validatedData['code'] = bin2hex(random_bytes(20)); 30 | 31 | Galery::create($validatedData); 32 | 33 | $this->dispatchBrowserEvent('galery:created'); 34 | $this->emit('galery:created'); 35 | $this->resetAll(); 36 | } 37 | 38 | public function resetAll() 39 | { 40 | $this->image = null; 41 | $this->title = null; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /database/migrations/2022_02_18_031926_create_facilities_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->string('code')->unique(); 19 | $table->string('name'); 20 | $table->enum('type', ['room', 'public']); 21 | $table->string('image'); 22 | $table->string('description'); 23 | $table->text('explanation'); 24 | $table->string('rate')->default(0); 25 | $table->string('views')->default(0); 26 | $table->timestamps(); 27 | }); 28 | } 29 | 30 | /** 31 | * Reverse the migrations. 32 | * 33 | * @return void 34 | */ 35 | public function down() 36 | { 37 | Schema::dropIfExists('facilities'); 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /database/migrations/2022_02_18_033115_create_room_reviews_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->string('code'); 19 | $table->foreignId('user_id')->constrained()->cascadeOnDelete()->cascadeOnUpdate(); 20 | $table->string('room_code'); 21 | $table->foreign('room_code')->references('code')->on('rooms')->cascadeOnDelete()->cascadeOnUpdate(); 22 | $table->text('message'); 23 | $table->string('star'); 24 | $table->date('date'); 25 | $table->timestamps(); 26 | }); 27 | } 28 | 29 | /** 30 | * Reverse the migrations. 31 | * 32 | * @return void 33 | */ 34 | public function down() 35 | { 36 | Schema::dropIfExists('room_reviews'); 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /config/view.php: -------------------------------------------------------------------------------- 1 | [ 17 | resource_path('views'), 18 | ], 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Compiled View Path 23 | |-------------------------------------------------------------------------- 24 | | 25 | | This option determines where all the compiled Blade templates will be 26 | | stored for your application. Typically, this is within the storage 27 | | directory. However, as usual, you are free to change this value. 28 | | 29 | */ 30 | 31 | 'compiled' => env( 32 | 'VIEW_COMPILED_PATH', 33 | realpath(storage_path('framework/views')) 34 | ), 35 | 36 | ]; 37 | -------------------------------------------------------------------------------- /database/migrations/2022_02_18_031827_create_rooms_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->string('code')->unique(); 19 | $table->string('name'); 20 | $table->string('total_rooms'); 21 | $table->string('image'); 22 | $table->string('description'); 23 | $table->text('explanation'); 24 | $table->string('rate')->default(0); 25 | $table->string('price'); 26 | $table->string('available'); 27 | $table->string('views')->default(0); 28 | $table->timestamps(); 29 | }); 30 | } 31 | 32 | /** 33 | * Reverse the migrations. 34 | * 35 | * @return void 36 | */ 37 | public function down() 38 | { 39 | Schema::dropIfExists('rooms'); 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /database/migrations/2022_02_18_033227_create_facility_reviews_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->string('code'); 19 | $table->foreignId('user_id')->constrained()->cascadeOnDelete()->cascadeOnUpdate(); 20 | $table->string('facility_code'); 21 | $table->foreign('facility_code')->references('code')->on('facilities')->cascadeOnDelete()->cascadeOnUpdate(); 22 | $table->text('message'); 23 | $table->string('star'); 24 | $table->date('date'); 25 | $table->timestamps(); 26 | }); 27 | } 28 | 29 | /** 30 | * Reverse the migrations. 31 | * 32 | * @return void 33 | */ 34 | public function down() 35 | { 36 | Schema::dropIfExists('facility_reviews'); 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/EmailVerificationNotificationController.php: -------------------------------------------------------------------------------- 1 | user()->hasVerifiedEmail()) { 20 | if (auth()->user()->hasRole('admin')) { 21 | return to_route('dashboard.admin.index'); 22 | } 23 | 24 | if (auth()->user()->hasRole('receptionist')) { 25 | return to_route('dashboard.receptionist.index'); 26 | } 27 | 28 | if (auth()->user()->hasRole('user')) { 29 | return to_route('dashboard.user.index'); 30 | } 31 | } 32 | 33 | $request->user()->sendEmailVerificationNotification(); 34 | 35 | return back()->with('status', 'verification-link-sent'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/Feature/Auth/AuthenticationTest.php: -------------------------------------------------------------------------------- 1 | get('/login'); 17 | 18 | $response->assertStatus(200); 19 | } 20 | 21 | public function test_users_can_authenticate_using_the_login_screen() 22 | { 23 | $user = User::factory()->create(); 24 | 25 | $response = $this->post('/login', [ 26 | 'email' => $user->email, 27 | 'password' => 'password', 28 | ]); 29 | 30 | $this->assertAuthenticated(); 31 | $response->assertRedirect(RouteServiceProvider::HOME); 32 | } 33 | 34 | public function test_users_can_not_authenticate_with_invalid_password() 35 | { 36 | $user = User::factory()->create(); 37 | 38 | $this->post('/login', [ 39 | 'email' => $user->email, 40 | 'password' => 'wrong-password', 41 | ]); 42 | 43 | $this->assertGuest(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /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() 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() 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /database/factories/ReservationFactory.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class ReservationFactory extends Factory 12 | { 13 | /** 14 | * Define the model's default state. 15 | * 16 | * @return array 17 | */ 18 | public function definition() 19 | { 20 | $statuses = ['waiting', 'confirmed', 'check in', 'check out', 'canceled']; 21 | 22 | return [ 23 | 'code' => str(uniqid('HLX-') . date('Ymd'))->upper(), 24 | 'user_id' => $this->faker->numberBetween(1, 200), 25 | 'room_id' => $this->faker->numberBetween(1, 14), 26 | 'date' => $this->faker->dateTimeBetween('-2 months'), 27 | 'check_in' => $this->faker->dateTimeBetween('-2 months'), 28 | 'check_out' => $this->faker->dateTimeBetween('-2 months'), 29 | 'status' => Arr::random($statuses), 30 | 'total_rooms' => $this->faker->numberBetween(1, 5), 31 | 'total_price' => $this->faker->numberBetween(400, 100000), 32 | 'message' => $this->faker->sentence() 33 | ]; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/View/Components/Nav.php: -------------------------------------------------------------------------------- 1 | facilities = Facility::latest()->orderBy('type')->orderBy('name')->get(); 21 | 22 | if (auth()->check()) { 23 | if (auth()->user()->hasRole('user')) { 24 | $this->dashboardLink = route('dashboard.user.index'); 25 | } 26 | 27 | if (auth()->user()->hasRole('receptionist')) { 28 | $this->dashboardLink = route('dashboard.receptionist.index'); 29 | } 30 | 31 | if (auth()->user()->hasRole('admin')) { 32 | $this->dashboardLink = route('dashboard.admin.index'); 33 | } 34 | } 35 | } 36 | 37 | /** 38 | * Get the view / contents that represent the component. 39 | * 40 | * @return \Illuminate\Contracts\View\View|\Closure|string 41 | */ 42 | public function render() 43 | { 44 | return view('components.nav'); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/Http/Livewire/Dashboard/Receptionist/Index.php: -------------------------------------------------------------------------------- 1 | layoutData(['title' => 'Receptionist Dashboard | Hollux']); 20 | } 21 | 22 | public function mount() 23 | { 24 | $this->fill([ 25 | 'totalReservations' => Reservation::all()->count(), 26 | 'totalReservationsWaiting' => Reservation::where('status', 'waiting')->count(), 27 | 'totalReservationsCanceled' => Reservation::where('status', 'canceled')->count(), 28 | 'totalReservationsConfirmed' => Reservation::where('status', 'confirmed')->count(), 29 | 'totalReservationsCheckIn' => Reservation::where('status', 'check in')->count(), 30 | 'totalReservationsCheckOut' => Reservation::where('status', 'check out')->count(), 31 | ]); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /database/migrations/2022_02_18_032108_create_reservations_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->string('code'); 19 | $table->foreignId('user_id')->constrained()->cascadeOnDelete()->cascadeOnUpdate(); 20 | $table->foreignId('room_id')->constrained()->cascadeOnDelete()->cascadeOnUpdate(); 21 | $table->date('date'); 22 | $table->date('check_in'); 23 | $table->date('check_out'); 24 | $table->string('status'); 25 | $table->string('total_rooms'); 26 | $table->string('total_price'); 27 | $table->text('message')->nullable(); 28 | $table->timestamps(); 29 | }); 30 | } 31 | 32 | /** 33 | * Reverse the migrations. 34 | * 35 | * @return void 36 | */ 37 | public function down() 38 | { 39 | Schema::dropIfExists('reservations'); 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /resources/views/auth/confirm-password.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | {{ __('This is a secure area of the application. Please confirm your password before continuing.') }} 11 |
12 | 13 | 14 | 15 | 16 |
17 | @csrf 18 | 19 | 20 |
21 | 22 | 23 | 27 |
28 | 29 |
30 | 31 | {{ __('Confirm') }} 32 | 33 |
34 |
35 |
36 |
37 | -------------------------------------------------------------------------------- /app/Models/Facility.php: -------------------------------------------------------------------------------- 1 | when($filters['search'], function ($query, $search) { 18 | return $query->where('name', 'like', "%$search%"); 19 | }); 20 | } 21 | 22 | /** 23 | * Get the route key for the model. 24 | * 25 | * @return string 26 | */ 27 | public function getRouteKeyName() 28 | { 29 | return 'code'; 30 | } 31 | 32 | /** 33 | * Get all of the reviews for the Facility 34 | * 35 | * @return \Illuminate\Database\Eloquent\Relations\HasMany 36 | */ 37 | public function reviews(): HasMany 38 | { 39 | return $this->hasMany(FacilityReview::class, 'facility_code', 'code'); 40 | } 41 | 42 | /** 43 | * Get all of the rooms for the Facility 44 | * 45 | * @return \Illuminate\Database\Eloquent\Relations\HasMany 46 | */ 47 | public function rooms(): HasMany 48 | { 49 | return $this->hasMany(RoomHasFacility::class, 'facility_code', 'code'); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /resources/views/livewire/room/review/edit.blade.php: -------------------------------------------------------------------------------- 1 |
2 |

Your Review

3 |
4 |
5 | 6 |
7 | @for ($i = 0; $i < $star; $i++) 8 | 9 | @endfor 10 | 11 | @for ($i = $star; $i < 5; $i++) 12 | 13 | @endfor 14 |
15 | @error('star') 16 | {{ $message }} 17 | @enderror 18 |
19 |
20 | 21 | 22 | @error('message') 23 | {{ $message }} 24 | @enderror 25 |
26 |
27 | 28 |
-------------------------------------------------------------------------------- /resources/views/livewire/facility/review/edit.blade.php: -------------------------------------------------------------------------------- 1 |
2 |

Your Review

3 |
4 |
5 | 6 |
7 | @for ($i = 0; $i < $star; $i++) 8 | 9 | @endfor 10 | 11 | @for ($i = $star; $i < 5; $i++) 12 | 13 | @endfor 14 |
15 | @error('star') 16 | {{ $message }} 17 | @enderror 18 |
19 |
20 | 21 | 22 | @error('message') 23 | {{ $message }} 24 | @enderror 25 |
26 |
27 | 28 |
-------------------------------------------------------------------------------- /app/Http/Livewire/Dashboard/Admin/Facility/Create.php: -------------------------------------------------------------------------------- 1 | layoutData(['title' => 'New Facility | Hollux']); 22 | } 23 | 24 | public function store() { 25 | $validatedData = $this->validate([ 26 | 'name' => ['required'], 27 | 'description' => ['required'], 28 | 'type' => ['required'], 29 | 'explanation' => ['required'], 30 | 'image' => ['required', 'image', 'max:2084'], 31 | ]); 32 | 33 | $validatedData['image'] = $this->image->store('img/facilities'); 34 | $validatedData['code'] = bin2hex(random_bytes(20)); 35 | 36 | Facility::create($validatedData); 37 | 38 | $this->dispatchBrowserEvent('facility:created'); 39 | $this->resetAll(); 40 | } 41 | 42 | public function resetAll() 43 | { 44 | $this->reset(['name', 'description', 'explanation', 'image']); 45 | $this->fill(['type' => 'public']); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /resources/views/auth/forgot-password.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | {{ __('Forgot your password? No problem. Just let us know your email address and we will email you a password reset link that will allow you to choose a new one.') }} 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | @csrf 21 | 22 | 23 |
24 | 25 | 26 | 27 |
28 | 29 |
30 | 31 | {{ __('Email Password Reset Link') }} 32 | 33 |
34 |
35 |
36 |
37 | -------------------------------------------------------------------------------- /app/Http/Livewire/Facility/Index.php: -------------------------------------------------------------------------------- 1 | 'reviewCreated', 'review:edited' => 'reviewEdited']; 15 | 16 | public function reviewCreated() 17 | { 18 | $this->dispatchBrowserEvent('review:created'); 19 | $this->fill([ 20 | 'facility' => $this->facility, 21 | 'reviews' => $this->facility->reviews 22 | ]); 23 | } 24 | 25 | public function reviewEdited() 26 | { 27 | $this->dispatchBrowserEvent('review:edited'); 28 | $this->fill([ 29 | 'facility' => $this->facility, 30 | 'reviews' => $this->facility->reviews 31 | ]); 32 | } 33 | 34 | public function render() 35 | { 36 | return view('livewire.facility.index')->layout('layouts.main', ['title' => 'Facilities | Hollux']); 37 | } 38 | 39 | public function mount(Facility $facility) 40 | { 41 | $this->fill([ 42 | 'facility' => $facility, 43 | 'facilities' => Facility::orderBy('type')->get(), 44 | 'reviews' => $facility->reviews 45 | ]); 46 | 47 | $this->facility->views += 1; 48 | $this->facility->save(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/Http/Livewire/Dashboard/Admin/Room/Index.php: -------------------------------------------------------------------------------- 1 | ['except' => ''], 19 | ]; 20 | 21 | public function render() 22 | { 23 | return view('livewire.dashboard.admin.room.index', [ 24 | 'rooms' => Room::filter(['search' => $this->search])->latest()->paginate(10) 25 | ])->layoutData(['title' => 'Room Dashboard | Hollux']); 26 | } 27 | 28 | public function mount() 29 | { 30 | 31 | $this->fill(['selectedRoom' => Room::first()]); 32 | } 33 | 34 | public function show(Room $room) 35 | { 36 | $this->dispatchBrowserEvent('room:show'); 37 | $this->selectedRoom = $room; 38 | } 39 | 40 | public function delete(Room $room) 41 | { 42 | $this->dispatchBrowserEvent('room:delete'); 43 | $this->selectedRoom = $room; 44 | } 45 | 46 | public function destroy() 47 | { 48 | Storage::delete($this->selectedRoom->image); 49 | $this->selectedRoom->delete(); 50 | $this->dispatchBrowserEvent('room:deleted'); 51 | } 52 | 53 | public function cancel() 54 | { 55 | $this->fill(['selectedRoom' => Room::first()]); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /resources/views/components/dropdown.blade.php: -------------------------------------------------------------------------------- 1 | @props(['align' => 'right', 'width' => '48', 'contentClasses' => 'py-1 bg-white']) 2 | 3 | @php 4 | switch ($align) { 5 | case 'left': 6 | $alignmentClasses = 'origin-top-left left-0'; 7 | break; 8 | case 'top': 9 | $alignmentClasses = 'origin-top'; 10 | break; 11 | case 'right': 12 | default: 13 | $alignmentClasses = 'origin-top-right right-0'; 14 | break; 15 | } 16 | 17 | switch ($width) { 18 | case '48': 19 | $width = 'w-48'; 20 | break; 21 | } 22 | @endphp 23 | 24 |
25 |
26 | {{ $trigger }} 27 |
28 | 29 | 43 |
44 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/PasswordResetLinkController.php: -------------------------------------------------------------------------------- 1 | validate([ 32 | 'email' => ['required', 'email'], 33 | ]); 34 | 35 | // We will send the password reset link to this user. Once we have attempted 36 | // to send the link, we will examine the response then see the message we 37 | // need to show to the user. Finally, we'll send out a proper response. 38 | $status = Password::sendResetLink( 39 | $request->only('email') 40 | ); 41 | 42 | return $status == Password::RESET_LINK_SENT 43 | ? back()->with('status', __($status)) 44 | : back()->withInput($request->only('email')) 45 | ->withErrors(['email' => __($status)]); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /resources/views/auth/verify-email.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | {{ __('Thanks for signing up! Before getting started, could you verify your email address by clicking on the link we just emailed to you? If you didn\'t receive the email, we will gladly send you another.') }} 11 |
12 | 13 | @if (session('status') == 'verification-link-sent') 14 |
15 | {{ __('A new verification link has been sent to the email address you provided during registration.') }} 16 |
17 | @endif 18 | 19 |
20 |
21 | @csrf 22 | 23 |
24 | 25 | {{ __('Resend Verification Email') }} 26 | 27 |
28 |
29 | 30 |
31 | @csrf 32 | 33 | 36 |
37 |
38 |
39 |
40 | -------------------------------------------------------------------------------- /app/Models/Room.php: -------------------------------------------------------------------------------- 1 | when($filters['search'], function ($query, $search) { 18 | return $query->where('name', 'like', "%$search%"); 19 | }); 20 | } 21 | 22 | /** 23 | * Get the route key for the model. 24 | * 25 | * @return string 26 | */ 27 | public function getRouteKeyName() 28 | { 29 | return 'code'; 30 | } 31 | 32 | /** 33 | * Get all of the reservations for the Room 34 | * 35 | * @return \Illuminate\Database\Eloquent\Relations\HasMany 36 | */ 37 | public function reservations(): HasMany 38 | { 39 | return $this->hasMany(Reservation::class); 40 | } 41 | 42 | /** 43 | * Get all of the facilities for the Room 44 | * 45 | * @return \Illuminate\Database\Eloquent\Relations\HasMany 46 | */ 47 | public function facilities(): HasMany 48 | { 49 | return $this->hasMany(RoomHasFacility::class); 50 | } 51 | 52 | /** 53 | * Get all of the reviews for the Room 54 | * 55 | * @return \Illuminate\Database\Eloquent\Relations\HasMany 56 | */ 57 | public function reviews(): HasMany 58 | { 59 | return $this->hasMany(RoomReview::class, 'room_code', 'code'); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/Http/Livewire/Dashboard/Admin/Facility/Index.php: -------------------------------------------------------------------------------- 1 | ['except' => ''], 19 | ]; 20 | 21 | public function render() 22 | { 23 | return view('livewire.dashboard.admin.facility.index', [ 24 | 'facilities' => Facility::filter(['search' => $this->search])->latest()->paginate(10) 25 | ])->layoutData(['title' => 'Facility Dashboard | Hollux']); 26 | } 27 | 28 | public function mount() 29 | { 30 | $this->fill(['selectedFacility' => Facility::first()]); 31 | } 32 | 33 | public function delete(Facility $facility) 34 | { 35 | $this->dispatchBrowserEvent('facility:delete'); 36 | $this->selectedFacility = $facility; 37 | } 38 | 39 | public function destroy() 40 | { 41 | Storage::delete($this->selectedFacility->image); 42 | $this->selectedFacility->delete(); 43 | $this->dispatchBrowserEvent('facility:deleted'); 44 | } 45 | 46 | public function show(Facility $facility) 47 | { 48 | $this->dispatchBrowserEvent('facility:show'); 49 | $this->selectedFacility = $facility; 50 | } 51 | 52 | public function cancel() 53 | { 54 | $this->fill(['selectedFacility' => Facility::first()]); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/ConfirmablePasswordController.php: -------------------------------------------------------------------------------- 1 | validate([ 32 | 'email' => $request->user()->email, 33 | 'password' => $request->password, 34 | ])) { 35 | throw ValidationException::withMessages([ 36 | 'password' => __('auth.password'), 37 | ]); 38 | } 39 | 40 | $request->session()->put('auth.password_confirmed_at', time()); 41 | 42 | if ($request->user()->hasRole('admin')) { 43 | return to_route('dashboard.admin.index'); 44 | } 45 | 46 | if ($request->user()->hasRole('receptionist')) { 47 | return to_route('dashboard.receptionist.index'); 48 | } 49 | 50 | if ($request->user()->hasRole('user')) { 51 | return to_route('dashboard.user.index'); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/Providers/RouteServiceProvider.php: -------------------------------------------------------------------------------- 1 | configureRateLimiting(); 30 | 31 | $this->routes(function () { 32 | Route::prefix('api') 33 | ->middleware('api') 34 | ->namespace($this->namespace) 35 | ->group(base_path('routes/api.php')); 36 | 37 | Route::middleware('web') 38 | ->namespace($this->namespace) 39 | ->group(base_path('routes/web.php')); 40 | }); 41 | } 42 | 43 | /** 44 | * Configure the rate limiters for the application. 45 | * 46 | * @return void 47 | */ 48 | protected function configureRateLimiting() 49 | { 50 | RateLimiter::for('api', function (Request $request) { 51 | return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip()); 52 | }); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/Models/Reservation.php: -------------------------------------------------------------------------------- 1 | when($filters['search'], fn ($query, $search) => 18 | $query->whereHas('user', fn ($query) => $query->where('name', 'like', "%$search%"))->orWhere('code', $search) 19 | ); 20 | 21 | $query->when($filters['check_in'], fn ($query, $check_in) => 22 | $query->whereDate('check_in', $check_in) 23 | ); 24 | 25 | $query->when($filters['status'], fn ($query, $status) => 26 | $query->where('status', $status) 27 | ); 28 | } 29 | 30 | /** 31 | * Get the route key for the model. 32 | * 33 | * @return string 34 | */ 35 | public function getRouteKeyName() 36 | { 37 | return 'code'; 38 | } 39 | 40 | /** 41 | * Get the user that owns the Reservation 42 | * 43 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo 44 | */ 45 | public function user(): BelongsTo 46 | { 47 | return $this->belongsTo(User::class); 48 | } 49 | 50 | /** 51 | * Get the rooms that owns the Reservation 52 | * 53 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo 54 | */ 55 | public function room(): BelongsTo 56 | { 57 | return $this->belongsTo(Room::class); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/Http/Livewire/Room/Review/Edit.php: -------------------------------------------------------------------------------- 1 | review = $review; 23 | $this->room = $room; 24 | $this->message = $review['message']; 25 | $this->star = $review['star']; 26 | } 27 | 28 | public function update() 29 | { 30 | if (!auth()->check()) { 31 | return to_route('login'); 32 | } 33 | 34 | $validatedData = $this->validate([ 35 | 'message' => ['required'], 36 | 'star' => ['required'] 37 | ]); 38 | 39 | $this->review->update($validatedData); 40 | 41 | $allReviews = RoomReview::where('room_code', $this->room->code)->get(); 42 | 43 | if (count($allReviews) > 0) { 44 | $rate = 0; 45 | 46 | foreach ($allReviews as $review) { 47 | $rate += $review->star; 48 | } 49 | 50 | $rate /= $allReviews->count(); 51 | } else { 52 | $rate = $this->star; 53 | } 54 | 55 | $this->room->update(['rate' => $rate]); 56 | 57 | $this->emit('review:edited'); 58 | } 59 | 60 | public function setRating($val) 61 | { 62 | if ($this->star == $val) { 63 | $this->star = null; 64 | } else { 65 | $this->star = $val; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /database/seeders/UserSeeder.php: -------------------------------------------------------------------------------- 1 | 'User Abi Noval Fauzi', 21 | 'code' => bin2hex(random_bytes(20)), 22 | 'email' => 'usernovalabi612@gmail.com', 23 | 'email_verified_at' => now(), 24 | 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 25 | 'phone_number' => '08174835153', // password 26 | 'remember_token' => Str::random(10), 27 | 'avatar' => 'img/avatar/a.png' 28 | ]); 29 | 30 | $user->syncRoles('user'); 31 | 32 | $faker = \Faker\Factory::create(); 33 | 34 | for ($i=1; $i < 200; $i++) { 35 | $user = User::create([ 36 | 'name' => $faker->name(), 37 | 'code' => bin2hex(random_bytes(20)), 38 | 'email' => $faker->unique()->safeEmail(), 39 | 'email_verified_at' => now(), 40 | 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 41 | 'phone_number' => $faker->phoneNumber(), // password 42 | 'remember_token' => Str::random(10), 43 | 'avatar' => 'img/avatar/' . substr($faker->name(), 0, 1) . '.png' 44 | ]); 45 | 46 | $user->syncRoles('user'); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /database/seeders/AdminSeeder.php: -------------------------------------------------------------------------------- 1 | 'Admin Abi Noval Fauzi', 21 | 'code' => bin2hex(random_bytes(20)), 22 | 'email' => 'adminnovalabi612@gmail.com', 23 | 'email_verified_at' => now(), 24 | 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 25 | 'phone_number' => '08174835153', // password 26 | 'remember_token' => Str::random(10), 27 | 'avatar' => 'img/avatar/a.png' 28 | ]); 29 | 30 | $admin->syncRoles('admin'); 31 | 32 | $faker = \Faker\Factory::create(); 33 | 34 | for ($i = 1; $i < 10; $i++) { 35 | $admin = User::create([ 36 | 'name' => $faker->name(), 37 | 'code' => bin2hex(random_bytes(20)), 38 | 'email' => $faker->unique()->safeEmail(), 39 | 'email_verified_at' => now(), 40 | 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 41 | 'phone_number' => $faker->phoneNumber(), // password 42 | 'remember_token' => Str::random(10), 43 | 'avatar' => 'img/avatar/' . substr($faker->name(), 0, 1) . '.png' 44 | ]); 45 | 46 | $admin->syncRoles('admin'); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/AuthenticatedSessionController.php: -------------------------------------------------------------------------------- 1 | authenticate(); 32 | 33 | $request->session()->regenerate(); 34 | 35 | if (auth()->user()->hasRole('admin')) { 36 | return to_route('dashboard.admin.index'); 37 | } 38 | 39 | if (auth()->user()->hasRole('receptionist')) { 40 | return to_route('dashboard.receptionist.index'); 41 | } 42 | 43 | return to_route('dashboard.user.index'); 44 | } 45 | 46 | /** 47 | * Destroy an authenticated session. 48 | * 49 | * @param \Illuminate\Http\Request $request 50 | * @return \Illuminate\Http\RedirectResponse 51 | */ 52 | public function destroy(Request $request) 53 | { 54 | Auth::guard('web')->logout(); 55 | 56 | $request->session()->invalidate(); 57 | 58 | $request->session()->regenerateToken(); 59 | 60 | return redirect('/'); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /app/Http/Livewire/Facility/Review/Edit.php: -------------------------------------------------------------------------------- 1 | review = $review; 23 | $this->facility = $facility; 24 | $this->message = $review['message']; 25 | $this->star = $review['star']; 26 | } 27 | 28 | public function update() 29 | { 30 | if (!auth()->check()) { 31 | return to_route('login'); 32 | } 33 | 34 | $validatedData = $this->validate([ 35 | 'message' => ['required'], 36 | 'star' => ['required'] 37 | ]); 38 | 39 | $this->review->update($validatedData); 40 | 41 | $allReviews = FacilityReview::where('facility_code', $this->facility->code)->get(); 42 | 43 | if (count($allReviews) > 0) { 44 | $rate = 0; 45 | 46 | foreach ($allReviews as $review) { 47 | $rate += $review->star; 48 | } 49 | 50 | $rate /= $allReviews->count(); 51 | } else { 52 | $rate = $this->star; 53 | } 54 | 55 | $this->facility->update(['rate' => $rate]); 56 | 57 | $this->emit('review:edited'); 58 | } 59 | 60 | public function setRating($val) 61 | { 62 | if ($this->star == $val) { 63 | $this->star = null; 64 | } else { 65 | $this->star = $val; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /config/hashing.php: -------------------------------------------------------------------------------- 1 | 'bcrypt', 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Bcrypt Options 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may specify the configuration options that should be used when 26 | | passwords are hashed using the Bcrypt algorithm. This will allow you 27 | | to control the amount of time it takes to hash the given password. 28 | | 29 | */ 30 | 31 | 'bcrypt' => [ 32 | 'rounds' => env('BCRYPT_ROUNDS', 10), 33 | ], 34 | 35 | /* 36 | |-------------------------------------------------------------------------- 37 | | Argon Options 38 | |-------------------------------------------------------------------------- 39 | | 40 | | Here you may specify the configuration options that should be used when 41 | | passwords are hashed using the Argon algorithm. These will allow you 42 | | to control the amount of time it takes to hash the given password. 43 | | 44 | */ 45 | 46 | 'argon' => [ 47 | 'memory' => 65536, 48 | 'threads' => 1, 49 | 'time' => 4, 50 | ], 51 | 52 | ]; 53 | -------------------------------------------------------------------------------- /app/Http/Livewire/Dashboard/User/Reservation/Index.php: -------------------------------------------------------------------------------- 1 | 'reservationcanceled']; 20 | 21 | public function reservationcanceled() 22 | { 23 | $this->dispatchBrowserEvent('reservation:canceled'); 24 | } 25 | 26 | public function render() 27 | { 28 | return view('livewire.dashboard.user.reservation.index', [ 29 | 'reservations' => Reservation::where('user_id', auth()->id())->latest()->paginate(5) 30 | ])->layoutData(['title' => 'Reservation Dashboard | Hollux']); 31 | } 32 | 33 | public function cancel($code) 34 | { 35 | $this->selected_reservation = $code; 36 | } 37 | 38 | public function canceled() 39 | { 40 | $this->validate(['message' => ['required']]); 41 | 42 | $reservation = Reservation::firstWhere('code', $this->selected_reservation); 43 | $room = Room::firstWhere('code', $reservation->room->code); 44 | 45 | $reservation->update(['status' => 'canceled', 'message' => $this->message]); 46 | 47 | $available = $room->total_rooms - array_sum($room->reservations->where('status', '<>', 'canceled')->where('status', '<>', 'check out')->pluck('total_rooms')->toArray()); 48 | $room->update(['available' => $available]); 49 | 50 | $this->emitSelf('reservation:canceled'); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /database/seeders/ReceptionistSeeder.php: -------------------------------------------------------------------------------- 1 | 'Receptionist Abi Noval Fauzi', 21 | 'code' => bin2hex(random_bytes(20)), 22 | 'email' => 'receptionistnovalabi612@gmail.com', 23 | 'email_verified_at' => now(), 24 | 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 25 | 'phone_number' => '08174835153', // password 26 | 'remember_token' => Str::random(10), 27 | 'avatar' => 'img/avatar/a.png' 28 | ]); 29 | 30 | $receptionist->syncRoles('receptionist'); 31 | 32 | $faker = \Faker\Factory::create(); 33 | 34 | for ($i = 1; $i < 50; $i++) { 35 | $receptionist = User::create([ 36 | 'name' => $faker->name(), 37 | 'code' => bin2hex(random_bytes(20)), 38 | 'email' => $faker->unique()->safeEmail(), 39 | 'email_verified_at' => now(), 40 | 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 41 | 'phone_number' => $faker->phoneNumber(), // password 42 | 'remember_token' => Str::random(10), 43 | 'avatar' => 'img/avatar/' . substr($faker->name(), 0, 1) . '.png' 44 | ]); 45 | 46 | $receptionist->syncRoles('receptionist'); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/VerifyEmailController.php: -------------------------------------------------------------------------------- 1 | user()->hasVerifiedEmail()) { 21 | if ($request->user()->hasRole('admin')) { 22 | return to_route('dashboard.admin.index', ['verified' => 1]); 23 | } 24 | 25 | if ($request->user()->hasRole('receptionist')) { 26 | return to_route('dashboard.receptionist.index', ['verified' => 1]); 27 | } 28 | 29 | if ($request->user()->hasRole('user')) { 30 | return to_route('dashboard.user.index', ['verified' => 1]); 31 | } 32 | } 33 | 34 | if ($request->user()->markEmailAsVerified()) { 35 | event(new Verified($request->user())); 36 | } 37 | 38 | if ($request->user()->hasRole('admin')) { 39 | return to_route('dashboard.admin.index', ['verified' => 1]); 40 | } 41 | 42 | if ($request->user()->hasRole('receptionist')) { 43 | return to_route('dashboard.receptionist.index', ['verified' => 1]); 44 | } 45 | 46 | if ($request->user()->hasRole('user')) { 47 | return to_route('dashboard.user.index', ['verified' => 1]); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /bootstrap/app.php: -------------------------------------------------------------------------------- 1 | singleton( 30 | Illuminate\Contracts\Http\Kernel::class, 31 | App\Http\Kernel::class 32 | ); 33 | 34 | $app->singleton( 35 | Illuminate\Contracts\Console\Kernel::class, 36 | App\Console\Kernel::class 37 | ); 38 | 39 | $app->singleton( 40 | Illuminate\Contracts\Debug\ExceptionHandler::class, 41 | App\Exceptions\Handler::class 42 | ); 43 | 44 | /* 45 | |-------------------------------------------------------------------------- 46 | | Return The Application 47 | |-------------------------------------------------------------------------- 48 | | 49 | | This script returns the application instance. The instance is given to 50 | | the calling script so we can separate the building of the instances 51 | | from the actual running of the application and sending responses. 52 | | 53 | */ 54 | 55 | return $app; 56 | -------------------------------------------------------------------------------- /artisan: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | make(Illuminate\Contracts\Console\Kernel::class); 34 | 35 | $status = $kernel->handle( 36 | $input = new Symfony\Component\Console\Input\ArgvInput, 37 | new Symfony\Component\Console\Output\ConsoleOutput 38 | ); 39 | 40 | /* 41 | |-------------------------------------------------------------------------- 42 | | Shutdown The Application 43 | |-------------------------------------------------------------------------- 44 | | 45 | | Once Artisan has finished running, we will fire off the shutdown events 46 | | so that any final work may be done by the application before we shut 47 | | down the process. This is the last thing to happen to the request. 48 | | 49 | */ 50 | 51 | $kernel->terminate($input, $status); 52 | 53 | exit($status); 54 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/RegisteredUserController.php: -------------------------------------------------------------------------------- 1 | validate([ 38 | 'name' => ['required', 'string', 'max:255'], 39 | 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'], 40 | 'phone_number' => ['required', new PhoneNumber], 41 | 'password' => ['required', 'confirmed', Rules\Password::defaults()], 42 | ]); 43 | 44 | $validatedData['password'] = bcrypt($request->password); 45 | $validatedData['avatar'] = 'img/avatar/' . substr($request->name, 0, 1) . '.png'; 46 | $validatedData['code'] = bin2hex(random_bytes(20)); 47 | 48 | $user = User::create($validatedData); 49 | 50 | $user->syncRoles('user'); 51 | 52 | event(new Registered($user)); 53 | 54 | Auth::login($user); 55 | 56 | return to_route('dashboard.user.index'); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /resources/views/auth/reset-password.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | @csrf 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 |
24 | 25 | 26 |
27 | 28 | 29 | 30 |
31 | 32 | 33 |
34 | 35 | 36 | 39 |
40 | 41 |
42 | 43 | {{ __('Reset Password') }} 44 | 45 |
46 |
47 |
48 |
49 | -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | make(Kernel::class); 50 | 51 | $response = $kernel->handle( 52 | $request = Request::capture() 53 | )->send(); 54 | 55 | $kernel->terminate($request, $response); 56 | -------------------------------------------------------------------------------- /app/Http/Livewire/Dashboard/Admin/Facility/Edit.php: -------------------------------------------------------------------------------- 1 | 'facilityEdited']; 23 | 24 | public function render() 25 | { 26 | return view('livewire.dashboard.admin.facility.edit')->layoutData(['title' => 'Edit Fasilitas | Hollux']); 27 | } 28 | 29 | public function mount(Facility $facility) 30 | { 31 | $this->facility = $facility; 32 | $this->name = $facility->name; 33 | $this->description = $facility->description; 34 | $this->type = $facility->type; 35 | $this->explanation = $facility->explanation; 36 | $this->oldImage = $facility->image; 37 | } 38 | 39 | public function update() 40 | { 41 | $rules = [ 42 | 'name' => ['required'], 43 | 'description' => ['required'], 44 | 'type' => ['required'], 45 | 'explanation' => ['required'], 46 | ]; 47 | 48 | if ($this->image) { 49 | $rules['image'] = ['image', 'max:2084']; 50 | } 51 | 52 | $validatedData = $this->validate($rules); 53 | 54 | if ($this->image) { 55 | $validatedData['image'] = $this->image->store('img/facilities'); 56 | Storage::delete($this->facility->image); 57 | } 58 | 59 | $this->facility->update($validatedData); 60 | 61 | $this->dispatchBrowserEvent('facility:edited'); 62 | $this->emitSelf('facility:edited'); 63 | } 64 | 65 | public function facilityEdited() 66 | { 67 | // 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /tests/Feature/Auth/EmailVerificationTest.php: -------------------------------------------------------------------------------- 1 | create([ 20 | 'email_verified_at' => null, 21 | ]); 22 | 23 | $response = $this->actingAs($user)->get('/verify-email'); 24 | 25 | $response->assertStatus(200); 26 | } 27 | 28 | public function test_email_can_be_verified() 29 | { 30 | $user = User::factory()->create([ 31 | 'email_verified_at' => null, 32 | ]); 33 | 34 | Event::fake(); 35 | 36 | $verificationUrl = URL::temporarySignedRoute( 37 | 'verification.verify', 38 | now()->addMinutes(60), 39 | ['id' => $user->id, 'hash' => sha1($user->email)] 40 | ); 41 | 42 | $response = $this->actingAs($user)->get($verificationUrl); 43 | 44 | Event::assertDispatched(Verified::class); 45 | $this->assertTrue($user->fresh()->hasVerifiedEmail()); 46 | $response->assertRedirect(RouteServiceProvider::HOME.'?verified=1'); 47 | } 48 | 49 | public function test_email_is_not_verified_with_invalid_hash() 50 | { 51 | $user = User::factory()->create([ 52 | 'email_verified_at' => null, 53 | ]); 54 | 55 | $verificationUrl = URL::temporarySignedRoute( 56 | 'verification.verify', 57 | now()->addMinutes(60), 58 | ['id' => $user->id, 'hash' => sha1('wrong-email')] 59 | ); 60 | 61 | $this->actingAs($user)->get($verificationUrl); 62 | 63 | $this->assertFalse($user->fresh()->hasVerifiedEmail()); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /resources/css/app.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | html { 7 | scroll-behavior: smooth; 8 | } 9 | 10 | h1, 11 | h2, 12 | h3, 13 | h4, 14 | h5, 15 | h6 { 16 | @apply font-['poppins']; 17 | } 18 | } 19 | 20 | @layer components { 21 | /* Button */ 22 | .btn { 23 | @apply inline-block bg-gray-800 border border-gray-800 text-white py-2 px-8 font-semibold hover:bg-gray-600 ring ring-offset-2 ring-transparent focus:ring-gray-800 focus:bg-gray-800 transition-all duration-300 rounded-tr-lg rounded-bl-lg text-center cursor-pointer; 24 | } 25 | 26 | .btn-sm { 27 | @apply text-sm; 28 | } 29 | 30 | .btn-outline { 31 | @apply bg-white text-gray-800 hover:bg-gray-200 focus:bg-gray-800 focus:text-white; 32 | } 33 | 34 | .btn-icon { 35 | @apply h-10 w-10 grid place-items-center text-sm p-0 rounded-tr-xl rounded-bl-xl; 36 | } 37 | /* End Button */ 38 | 39 | /* Input */ 40 | .select, .input, .textarea { 41 | @apply rounded-tr-lg rounded-bl-lg focus:ring focus:ring-offset-2 focus:ring-gray-800 focus:border-gray-800 transition-all duration-300; 42 | } 43 | 44 | .checkbox { 45 | @apply rounded-tr rounded-bl focus:ring-gray-800 focus:border-gray-800 focus:text-gray-800 checked:text-gray-800 transition-all duration-300; 46 | } 47 | 48 | .form-control { 49 | @apply grid gap-1; 50 | } 51 | 52 | .form-control .label { 53 | @apply font-bold text-gray-800; 54 | } 55 | 56 | .form-control .invalid { 57 | @apply text-red-600 text-sm italic font-medium; 58 | } 59 | /* End Input */ 60 | 61 | /* Table */ 62 | .thead { 63 | @apply bg-gray-50; 64 | } 65 | 66 | .th { 67 | @apply py-3 px-6 sm:text-sm text-xs font-medium tracking-wider text-left text-gray-800 uppercase whitespace-nowrap; 68 | } 69 | 70 | .td { 71 | @apply py-4 px-6 sm:text-base text-sm text-gray-500 whitespace-nowrap; 72 | } 73 | /* End Table */ 74 | } -------------------------------------------------------------------------------- /app/Http/Livewire/Dashboard/Admin/Index.php: -------------------------------------------------------------------------------- 1 | layoutData(['title' => 'Admin Dashboard | Hollux']); 28 | } 29 | 30 | public function mount() 31 | { 32 | $this->fill([ 33 | 'totalRooms' => Room::latest()->get()->count(), 34 | 'totalFacilities' => Facility::latest()->get()->count(), 35 | 'totalGalery' => Galery::latest()->get()->count(), 36 | 'about' => About::first(), 37 | ]); 38 | 39 | $this->fill([ 40 | 'title' => $this->about->title, 41 | 'text' => $this->about->text, 42 | ]); 43 | } 44 | 45 | public function update() 46 | { 47 | $rules = [ 48 | 'title' => ['required'], 49 | 'text' => ['required'], 50 | ]; 51 | 52 | if ($this->image) { 53 | $rules['image'] = ['required', 'image', 'max:2048']; 54 | } 55 | 56 | $validatedData = $this->validate($rules); 57 | 58 | if ($this->image) { 59 | $validatedData['image'] = $this->image->store('img/about'); 60 | Storage::delete($this->about->image); 61 | } 62 | 63 | $this->about->update($validatedData); 64 | $this->dispatchBrowserEvent('about:updated'); 65 | } 66 | 67 | public function resetAll() 68 | { 69 | $this->fill([ 70 | 'title' => $this->about->title, 71 | 'text' => $this->about->text, 72 | ]); 73 | 74 | $this->reset('image'); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /config/broadcasting.php: -------------------------------------------------------------------------------- 1 | env('BROADCAST_DRIVER', 'null'), 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Broadcast Connections 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may define all of the broadcast connections that will be used 26 | | to broadcast events to other systems or over websockets. Samples of 27 | | each available type of connection are provided inside this array. 28 | | 29 | */ 30 | 31 | 'connections' => [ 32 | 33 | 'pusher' => [ 34 | 'driver' => 'pusher', 35 | 'key' => env('PUSHER_APP_KEY'), 36 | 'secret' => env('PUSHER_APP_SECRET'), 37 | 'app_id' => env('PUSHER_APP_ID'), 38 | 'options' => [ 39 | 'cluster' => env('PUSHER_APP_CLUSTER'), 40 | 'useTLS' => true, 41 | ], 42 | 'client_options' => [ 43 | // Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html 44 | ], 45 | ], 46 | 47 | 'ably' => [ 48 | 'driver' => 'ably', 49 | 'key' => env('ABLY_KEY'), 50 | ], 51 | 52 | 'redis' => [ 53 | 'driver' => 'redis', 54 | 'connection' => 'default', 55 | ], 56 | 57 | 'log' => [ 58 | 'driver' => 'log', 59 | ], 60 | 61 | 'null' => [ 62 | 'driver' => 'null', 63 | ], 64 | 65 | ], 66 | 67 | ]; 68 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "laravel/laravel", 3 | "type": "project", 4 | "description": "The Laravel Framework.", 5 | "keywords": ["framework", "laravel"], 6 | "license": "MIT", 7 | "require": { 8 | "php": "^8.0.2", 9 | "barryvdh/laravel-dompdf": "^1.0", 10 | "fruitcake/laravel-cors": "^2.0.5", 11 | "guzzlehttp/guzzle": "^7.2", 12 | "laravel/framework": "^9.0", 13 | "laravel/sanctum": "^2.14", 14 | "laravel/tinker": "^2.7", 15 | "livewire/livewire": "^2.10", 16 | "spatie/laravel-permission": "^5.5" 17 | }, 18 | "require-dev": { 19 | "fakerphp/faker": "^1.9.1", 20 | "laravel/breeze": "^1.8", 21 | "laravel/sail": "^1.0.1", 22 | "mockery/mockery": "^1.4.4", 23 | "nunomaduro/collision": "^6.1", 24 | "phpunit/phpunit": "^9.5.10", 25 | "spatie/laravel-ignition": "^1.0" 26 | }, 27 | "autoload": { 28 | "psr-4": { 29 | "App\\": "app/", 30 | "Database\\Factories\\": "database/factories/", 31 | "Database\\Seeders\\": "database/seeders/" 32 | } 33 | }, 34 | "autoload-dev": { 35 | "psr-4": { 36 | "Tests\\": "tests/" 37 | } 38 | }, 39 | "scripts": { 40 | "post-autoload-dump": [ 41 | "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", 42 | "@php artisan package:discover --ansi" 43 | ], 44 | "post-update-cmd": [ 45 | "@php artisan vendor:publish --tag=laravel-assets --ansi --force" 46 | ], 47 | "post-root-package-install": [ 48 | "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" 49 | ], 50 | "post-create-project-cmd": [ 51 | "@php artisan key:generate --ansi" 52 | ] 53 | }, 54 | "extra": { 55 | "laravel": { 56 | "dont-discover": [] 57 | } 58 | }, 59 | "config": { 60 | "optimize-autoloader": true, 61 | "preferred-install": "dist", 62 | "sort-packages": true 63 | }, 64 | "minimum-stability": "dev", 65 | "prefer-stable": true 66 | } 67 | -------------------------------------------------------------------------------- /app/Http/Livewire/Room/Review/Create.php: -------------------------------------------------------------------------------- 1 | room = $room; 23 | } 24 | 25 | public function store() 26 | { 27 | if (!auth()->check()) { 28 | return $this->dispatchBrowserEvent('review:login'); 29 | } 30 | 31 | if (!auth()->user()->hasVerifiedEmail()) { 32 | return $this->dispatchBrowserEvent('review:verified'); 33 | } 34 | 35 | if (!auth()->user()->hasRole('user')) { 36 | return $this->dispatchBrowserEvent('review:forbidden'); 37 | } 38 | 39 | $validatedData = $this->validate([ 40 | 'message' => ['required'], 41 | 'star' => ['required'] 42 | ]); 43 | 44 | $validatedData['user_id'] = auth()->id(); 45 | $validatedData['room_code'] = $this->room->code; 46 | $validatedData['date'] = date('Y-m-d'); 47 | $validatedData['code'] = bin2hex(random_bytes(20)); 48 | 49 | RoomReview::create($validatedData); 50 | 51 | $allReviews = RoomReview::where('room_code', $this->room->code)->get(); 52 | 53 | if (count($allReviews) > 0) { 54 | $rate = 0; 55 | 56 | foreach ($allReviews as $review) { 57 | $rate += $review->star; 58 | } 59 | 60 | $rate /= $allReviews->count(); 61 | } else { 62 | $rate = 0; 63 | } 64 | 65 | $this->room->update(['rate' => $rate]); 66 | 67 | $this->message = null; 68 | $this->star = null; 69 | 70 | $this->emit('review:created'); 71 | } 72 | 73 | public function setRating($val) 74 | { 75 | if ($this->star == $val) { 76 | $this->star = null; 77 | } else { 78 | $this->star = $val; 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /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() 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() 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() 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->assertSessionHasNoErrors(); 67 | 68 | return true; 69 | }); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /app/Http/Livewire/Facility/Review/Create.php: -------------------------------------------------------------------------------- 1 | facility = $facility; 22 | } 23 | 24 | public function store() 25 | { 26 | if (!auth()->check()) { 27 | return $this->dispatchBrowserEvent('review:login'); 28 | } 29 | 30 | if (!auth()->user()->hasVerifiedEmail()) { 31 | return $this->dispatchBrowserEvent('review:verified'); 32 | } 33 | 34 | if (!auth()->user()->hasRole('user')) { 35 | return $this->dispatchBrowserEvent('review:forbidden'); 36 | } 37 | 38 | $validatedData = $this->validate([ 39 | 'message' => ['required'], 40 | 'star' => ['required'] 41 | ]); 42 | 43 | $validatedData['user_id'] = auth()->id(); 44 | $validatedData['facility_code'] = $this->facility->code; 45 | $validatedData['date'] = date('Y-m-d'); 46 | $validatedData['code'] = bin2hex(random_bytes(20)); 47 | 48 | FacilityReview::create($validatedData); 49 | 50 | $allReviews = FacilityReview::where('facility_code', $this->facility->code)->get(); 51 | 52 | if (count($allReviews) > 0) { 53 | $rate = 0; 54 | 55 | foreach ($allReviews as $review) { 56 | $rate += $review->star; 57 | } 58 | 59 | $rate /= $allReviews->count(); 60 | } else { 61 | $rate = $this->star; 62 | } 63 | 64 | $this->facility->update(['rate' => $rate]); 65 | 66 | $this->message = null; 67 | $this->star = null; 68 | 69 | $this->emit('review:created'); 70 | } 71 | 72 | public function setRating($val) 73 | { 74 | if ($this->star == $val) { 75 | $this->star = null; 76 | } else { 77 | $this->star = $val; 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /database/seeders/FacilityReviewSeeder.php: -------------------------------------------------------------------------------- 1 | skip(30)->take(20)->get()->pluck('id'); 21 | $facilityCodes = [ '67b971a1466e3ffbf01a26fcf842bacc85feb7a2', '06ab599a280090150bbbdba527ece643855842c3', 'f0903398b6625e0d2c58a6ae6a2d626ca21c8fb1', 'd5f74d17b239ebd6a7f9accf369b0c017aae2811', '8350bb155dcf4cd92716cc2c3f93e1010c49e39e', '2b7563186c78f9a2a555c82b500f62dc9a616ee4', '7f99d296472f767a6e65bf088af047d37c0f5e52']; 22 | $faker = \Faker\Factory::create(); 23 | 24 | for ($i = 0; $i < count($users); $i++) { 25 | for ($j = 0; $j < count($facilityCodes); $j++) { 26 | FacilityReview::create([ 27 | 'code' => bin2hex(random_bytes(20)), 28 | 'user_id' => $users[$i], 29 | 'facility_code' => $facilityCodes[$j], 30 | 'message' => $faker->sentence(), 31 | 'star' => $faker->numberBetween(3, 5), 32 | 'date' => $faker->date() 33 | ]); 34 | } 35 | } 36 | 37 | for ($i = 0; $i < count($facilityCodes); $i++) { 38 | $facility = Facility::firstWhere('code', $facilityCodes[$i]); 39 | $allReviews = FacilityReview::where('facility_code', $facility->code)->get(); 40 | 41 | if (count($allReviews) > 0) { 42 | $rate = 0; 43 | 44 | foreach ($allReviews as $review) { 45 | $rate += $review->star; 46 | } 47 | 48 | $rate /= $allReviews->count(); 49 | } else { 50 | $rate = 0; 51 | } 52 | 53 | $facility->update([ 54 | 'rate' => $rate, 55 | 'views' => $faker->numberBetween(1000, 100000) 56 | ]); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/Http/Livewire/Dashboard/Admin/User/Create.php: -------------------------------------------------------------------------------- 1 | 'userCreated']; 26 | 27 | public function userCreated() 28 | { 29 | $this->resetAll(); 30 | } 31 | 32 | public function render() 33 | { 34 | return view('livewire.dashboard.admin.user.create')->layoutData(['title' => 'New User']); 35 | } 36 | 37 | public function mount() 38 | { 39 | $this->fill(['roles' => Role::all()->pluck('name')]); 40 | } 41 | 42 | public function store() 43 | { 44 | $rules = [ 45 | 'name' => ['required', 'string', 'max:255'], 46 | 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'], 47 | 'phone_number' => ['required', new PhoneNumber], 48 | 'password' => ['required', 'confirmed', Rules\Password::defaults()], 49 | ]; 50 | 51 | if ($this->avatar) { 52 | $rules['avatar'] = ['required', 'image']; 53 | } 54 | 55 | $validatedData = $this->validate($rules); 56 | 57 | $validatedData['password'] = bcrypt($this->password); 58 | 59 | if ($this->avatar) { 60 | $validatedData['avatar'] = $this->avatar->store('img/avatar/upload'); 61 | } else { 62 | $validatedData['avatar'] = 'img/avatar/' . substr($this->name, 0, 1) . '.png'; 63 | } 64 | 65 | $validatedData['code'] = bin2hex(random_bytes(20)); 66 | 67 | $user = User::create($validatedData); 68 | 69 | $user->syncRoles($this->role); 70 | $this->emit('user:created'); 71 | } 72 | 73 | public function resetAll() 74 | { 75 | $this->resetExcept('roles'); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /resources/views/livewire/dashboard/user/index.blade.php: -------------------------------------------------------------------------------- 1 |
2 |

Welcome!!

3 |
4 |

Statistic

5 |
6 |
7 |
8 | 9 |
10 |
11 | Reservation 12 | {{ $totalReservations }} 13 |
14 |
15 |
16 |
17 |
18 |
19 | 20 |
21 |
22 | Facility Reviews 23 | {{ $totalFacilityReviews }} 24 |
25 |
26 |
27 |
28 |
29 |
30 | 31 |
32 |
33 | Room Reviews 34 | {{ $totalRoomReviews }} 35 |
36 |
37 |
38 |
39 |
40 | -------------------------------------------------------------------------------- /resources/views/auth/login.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | @csrf 17 | 18 | 19 |
20 | 21 | 22 | 23 |
24 | 25 | 26 |
27 | 28 | 29 | 33 |
34 | 35 | 36 |
37 | 41 |
42 | 43 |
44 | @if (Route::has('password.request')) 45 | 46 | {{ __('Forgot your password?') }} 47 | 48 | @endif 49 | 50 | 51 | {{ __('Log in') }} 52 | 53 |
54 |
55 |
56 |
57 | -------------------------------------------------------------------------------- /app/Http/Livewire/Dashboard/Admin/Room/Create.php: -------------------------------------------------------------------------------- 1 | layoutData(['title' => 'New Room | Hollux']); 27 | } 28 | 29 | public function mount() 30 | { 31 | $this->fill(['facilities' => Facility::where('type', 'room')->get()]); 32 | $this->fill(['selectedFacilities' => array_fill_keys($this->facilities->pluck('code')->toArray(), false)]); 33 | } 34 | 35 | public function store() 36 | { 37 | $validatedData = $this->validate([ 38 | 'name' => ['required'], 39 | 'description' => ['required'], 40 | 'total_rooms' => ['required', 'numeric'], 41 | 'price' => ['required', 'numeric'], 42 | 'explanation' => ['required'], 43 | 'image' => ['required', 'image', 'max:2084'], 44 | ]); 45 | 46 | $validatedData['image'] = $this->image->store('img/rooms'); 47 | $validatedData['code'] = bin2hex(random_bytes(20)); 48 | $validatedData['available'] = $this->total_rooms; 49 | 50 | $roomId = Room::create($validatedData); 51 | 52 | if (count(array_filter($this->selectedFacilities))) { 53 | foreach (array_filter($this->selectedFacilities) as $facility) { 54 | RoomHasFacility::create([ 55 | 'room_id' => $roomId->id, 56 | 'facility_code' => $facility 57 | ]); 58 | } 59 | } 60 | 61 | $this->dispatchBrowserEvent('room:created'); 62 | $this->resetAll(); 63 | } 64 | 65 | public function resetAll() 66 | { 67 | $this->reset(['name', 'total_rooms', 'description', 'price', 'explanation', 'image']); 68 | $this->fill(['selectedFacilities' => array_fill_keys($this->facilities->pluck('code')->toArray(), false)]); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /database/seeders/RoomReviewSeeder.php: -------------------------------------------------------------------------------- 1 | skip(1)->take(20)->get()->pluck('id'); 21 | $roomCodes = ['6d0929022a3f483cee01a71c8bb07cd497e12a2a', 'a325d27311d54f329d8efc903fa29147c87cb474', 'b04ccf712c8bf355b8d6ffbd8677190c52e5e1af', 'c004fc694adae94c9915ce4908d331fee3ac2e16', '0d512be37d5472ffad8b38e631b1f6d4ac52406e', '7ecf1c829efcf26958228f456ea648f07407e4c6', 'ef96f87eb0697be3d8f8d3338528752542d0fe18', 'd2e35973667acbcc21bf806fa9c4816811f93480', 'e68df0c1d217535de4a78bfac1935024187dad2c', 'd38e58c1f31900d29e1f95f729bab3e78f96a366', '92a4173c7dfd3fddbf45506ddd46d9bfaf706299', 'd0694a03e5dd6aac9814f28bb14a90effea15b01', '85dfe0fe296ce37d036a48e28e1dffd7037d0fd3', 'd3535f13cb58a761833f745c69cee5d8d689125b']; 22 | $faker = \Faker\Factory::create(); 23 | 24 | for ($i=0; $i < count($users); $i++) { 25 | for ($j=0; $j < count($roomCodes); $j++) { 26 | RoomReview::create([ 27 | 'code' => bin2hex(random_bytes(20)), 28 | 'user_id' => $users[$i], 29 | 'room_code' => $roomCodes[$j], 30 | 'message' => $faker->sentence(), 31 | 'star' => $faker->numberBetween(3, 5), 32 | 'date' => $faker->date() 33 | ]); 34 | } 35 | } 36 | 37 | for ($i=0; $i < count($roomCodes); $i++) { 38 | $room = Room::firstWhere('code', $roomCodes[$i]); 39 | $allReviews = RoomReview::where('room_code', $room->code)->get(); 40 | 41 | if (count($allReviews) > 0) { 42 | $rate = 0; 43 | 44 | foreach ($allReviews as $review) { 45 | $rate += $review->star; 46 | } 47 | 48 | $rate /= $allReviews->count(); 49 | } else { 50 | $rate = 0; 51 | } 52 | 53 | $room->update([ 54 | 'rate' => $rate, 55 | 'views' => $faker->numberBetween(1000, 100000) 56 | ]); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/Http/Livewire/Dashboard/Receptionist/Reservation/Index.php: -------------------------------------------------------------------------------- 1 | ['except' => ''], 20 | 'check_in' => ['except' => ''], 21 | 'status' => ['except' => ''], 22 | ]; 23 | 24 | protected $listeners = ['status:confirmed' => 'statusConfirmed', 'status:checkin' => 'statusCheckIn', 'status:checkout' => 'statusCheckOut']; 25 | 26 | public function render() 27 | { 28 | return view('livewire.dashboard.receptionist.reservation.index', [ 29 | 'reservations' => Reservation::filter(['search' => $this->search, 'check_in' => $this->check_in, 'status' => $this->status])->latest()->paginate(50) 30 | ])->layoutData(['title' => 'Reservation | Hollux']); 31 | } 32 | 33 | public function confirm($code) 34 | { 35 | $reservation = Reservation::firstWhere('code', $code); 36 | $reservation->update(['status' => 'confirmed']); 37 | $this->emitSelf('status:confirmed'); 38 | } 39 | 40 | public function checkIn($code) 41 | { 42 | $reservation = Reservation::firstWhere('code', $code); 43 | $reservation->update(['status' => 'check in']); 44 | $this->emitSelf('status:checkin'); 45 | } 46 | 47 | public function checkOut($code) 48 | { 49 | $reservation = Reservation::firstWhere('code', $code); 50 | $room = Room::firstWhere('code', $reservation->room->code); 51 | $reservation->update(['status' => 'check out']); 52 | $room->available = $room->total_rooms - array_sum($room->reservations->where('status', '<>', 'canceled')->where('status', '<>', 'check out')->pluck('total_rooms')->toArray()); 53 | $room->save(); 54 | $this->emitSelf('status:checkout'); 55 | } 56 | 57 | public function statusConfirmed() 58 | { 59 | $this->dispatchBrowserEvent('status:confirmed'); 60 | } 61 | 62 | public function statusCheckIn() 63 | { 64 | $this->dispatchBrowserEvent('status:checkin'); 65 | } 66 | 67 | public function statusCheckOut() 68 | { 69 | $this->dispatchBrowserEvent('status:checkout'); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /app/Http/Livewire/Dashboard/Admin/Galery/Index.php: -------------------------------------------------------------------------------- 1 | 'galeryCreated']; 20 | 21 | public function render() 22 | { 23 | return view('livewire.dashboard.admin.galery.index', [ 24 | 'galeries' => Galery::latest()->get() 25 | ])->layoutData(['title' => 'Galery Dashboard | Hollux']); 26 | } 27 | 28 | public function edit(Galery $galery) 29 | { 30 | $this->dispatchBrowserEvent('galery:edit'); 31 | $this->selectedGalery = $galery; 32 | $this->oldImage = $this->selectedGalery->image; 33 | $this->title = $this->selectedGalery->title; 34 | } 35 | 36 | public function delete(Galery $galery) 37 | { 38 | $this->dispatchBrowserEvent('galery:delete'); 39 | $this->selectedGalery = $galery; 40 | } 41 | 42 | public function galeryCreated() 43 | { 44 | // 45 | } 46 | 47 | public function update() 48 | { 49 | $rules = [ 50 | 'title' => ['required'], 51 | ]; 52 | 53 | if ($this->new_image) { 54 | $rules['new_image'] = ['file', 'image', 'max:2048']; 55 | } 56 | 57 | $validatedData = $this->validate($rules); 58 | 59 | if ($this->new_image) { 60 | $validatedData['image'] = $this->new_image->store('img/galeries'); 61 | unset($validatedData['new_image']); 62 | Storage::delete($this->selectedGalery->image); 63 | } 64 | 65 | $this->selectedGalery->update($validatedData); 66 | 67 | $this->dispatchBrowserEvent('galery:edited'); 68 | $this->resetAll(); 69 | } 70 | 71 | public function destroy() 72 | { 73 | Storage::delete($this->selectedGalery->image); 74 | $this->selectedGalery->delete(); 75 | $this->dispatchBrowserEvent('galery:deleted'); 76 | $this->resetAll(); 77 | } 78 | 79 | public function resetAll() 80 | { 81 | $this->reset(['selectedGalery', 'oldImage', 'new_image', 'title']); 82 | } 83 | 84 | public function cancel() 85 | { 86 | $this->resetAll(); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /config/sanctum.php: -------------------------------------------------------------------------------- 1 | explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf( 17 | '%s%s', 18 | 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1', 19 | env('APP_URL') ? ','.parse_url(env('APP_URL'), PHP_URL_HOST) : '' 20 | ))), 21 | 22 | /* 23 | |-------------------------------------------------------------------------- 24 | | Sanctum Guards 25 | |-------------------------------------------------------------------------- 26 | | 27 | | This array contains the authentication guards that will be checked when 28 | | Sanctum is trying to authenticate a request. If none of these guards 29 | | are able to authenticate the request, Sanctum will use the bearer 30 | | token that's present on an incoming request for authentication. 31 | | 32 | */ 33 | 34 | 'guard' => ['web'], 35 | 36 | /* 37 | |-------------------------------------------------------------------------- 38 | | Expiration Minutes 39 | |-------------------------------------------------------------------------- 40 | | 41 | | This value controls the number of minutes until an issued token will be 42 | | considered expired. If this value is null, personal access tokens do 43 | | not expire. This won't tweak the lifetime of first-party sessions. 44 | | 45 | */ 46 | 47 | 'expiration' => null, 48 | 49 | /* 50 | |-------------------------------------------------------------------------- 51 | | Sanctum Middleware 52 | |-------------------------------------------------------------------------- 53 | | 54 | | When authenticating your first-party SPA with Sanctum you may need to 55 | | customize some of the middleware Sanctum uses while processing the 56 | | request. You may change the middleware listed below as required. 57 | | 58 | */ 59 | 60 | 'middleware' => [ 61 | 'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class, 62 | 'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class, 63 | ], 64 | 65 | ]; 66 | -------------------------------------------------------------------------------- /routes/auth.php: -------------------------------------------------------------------------------- 1 | group(function () { 14 | Route::get('register', [RegisteredUserController::class, 'create']) 15 | ->name('register'); 16 | 17 | Route::post('register', [RegisteredUserController::class, 'store']); 18 | 19 | Route::get('login', [AuthenticatedSessionController::class, 'create']) 20 | ->name('login'); 21 | 22 | Route::post('login', [AuthenticatedSessionController::class, 'store']); 23 | 24 | Route::get('forgot-password', [PasswordResetLinkController::class, 'create']) 25 | ->name('password.request'); 26 | 27 | Route::post('forgot-password', [PasswordResetLinkController::class, 'store']) 28 | ->name('password.email'); 29 | 30 | Route::get('reset-password/{token}', [NewPasswordController::class, 'create']) 31 | ->name('password.reset'); 32 | 33 | Route::post('reset-password', [NewPasswordController::class, 'store']) 34 | ->name('password.update'); 35 | }); 36 | 37 | Route::middleware('auth')->group(function () { 38 | Route::get('verify-email', [EmailVerificationPromptController::class, '__invoke']) 39 | ->name('verification.notice'); 40 | 41 | Route::get('verify-email/{id}/{hash}', [VerifyEmailController::class, '__invoke']) 42 | ->middleware(['signed', 'throttle:6,1']) 43 | ->name('verification.verify'); 44 | 45 | Route::post('email/verification-notification', [EmailVerificationNotificationController::class, 'store']) 46 | ->middleware('throttle:6,1') 47 | ->name('verification.send'); 48 | 49 | Route::get('confirm-password', [ConfirmablePasswordController::class, 'show']) 50 | ->name('password.confirm'); 51 | 52 | Route::post('confirm-password', [ConfirmablePasswordController::class, 'store']); 53 | 54 | Route::post('logout', [AuthenticatedSessionController::class, 'destroy']) 55 | ->name('logout'); 56 | }); 57 | -------------------------------------------------------------------------------- /config/filesystems.php: -------------------------------------------------------------------------------- 1 | env('FILESYSTEM_DISK', 'local'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Filesystem Disks 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Here you may configure as many filesystem "disks" as you wish, and you 24 | | may even configure multiple disks of the same driver. Defaults have 25 | | been setup for each driver as an example of the required options. 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 | ], 37 | 38 | 'public' => [ 39 | 'driver' => 'local', 40 | 'root' => storage_path('app/public'), 41 | 'url' => env('APP_URL').'/storage', 42 | 'visibility' => 'public', 43 | ], 44 | 45 | 's3' => [ 46 | 'driver' => 's3', 47 | 'key' => env('AWS_ACCESS_KEY_ID'), 48 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 49 | 'region' => env('AWS_DEFAULT_REGION'), 50 | 'bucket' => env('AWS_BUCKET'), 51 | 'url' => env('AWS_URL'), 52 | 'endpoint' => env('AWS_ENDPOINT'), 53 | 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false), 54 | ], 55 | 56 | ], 57 | 58 | /* 59 | |-------------------------------------------------------------------------- 60 | | Symbolic Links 61 | |-------------------------------------------------------------------------- 62 | | 63 | | Here you may configure the symbolic links that will be created when the 64 | | `storage:link` Artisan command is executed. The array keys should be 65 | | the locations of the links and the values should be their targets. 66 | | 67 | */ 68 | 69 | 'links' => [ 70 | public_path('storage') => storage_path('app/public'), 71 | ], 72 | 73 | ]; 74 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/NewPasswordController.php: -------------------------------------------------------------------------------- 1 | $request]); 24 | } 25 | 26 | /** 27 | * Handle an incoming new password request. 28 | * 29 | * @param \Illuminate\Http\Request $request 30 | * @return \Illuminate\Http\RedirectResponse 31 | * 32 | * @throws \Illuminate\Validation\ValidationException 33 | */ 34 | public function store(Request $request) 35 | { 36 | $request->validate([ 37 | 'token' => ['required'], 38 | 'email' => ['required', 'email'], 39 | 'password' => ['required', 'confirmed', Rules\Password::defaults()], 40 | ]); 41 | 42 | // Here we will attempt to reset the user's password. If it is successful we 43 | // will update the password on an actual user model and persist it to the 44 | // database. Otherwise we will parse the error and return the response. 45 | $status = Password::reset( 46 | $request->only('email', 'password', 'password_confirmation', 'token'), 47 | function ($user) use ($request) { 48 | $user->forceFill([ 49 | 'password' => Hash::make($request->password), 50 | 'remember_token' => Str::random(60), 51 | ])->save(); 52 | 53 | event(new PasswordReset($user)); 54 | } 55 | ); 56 | 57 | // If the password was successfully reset, we will redirect the user back to 58 | // the application's home authenticated view. If there is an error we can 59 | // redirect them back to where they came from with their error message. 60 | return $status == Password::PASSWORD_RESET 61 | ? redirect()->route('login')->with('status', __($status)) 62 | : back()->withInput($request->only('email')) 63 | ->withErrors(['email' => __($status)]); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/Http/Requests/Auth/LoginRequest.php: -------------------------------------------------------------------------------- 1 | ['required', 'string', 'email'], 33 | 'password' => ['required', 'string'], 34 | ]; 35 | } 36 | 37 | /** 38 | * Attempt to authenticate the request's credentials. 39 | * 40 | * @return void 41 | * 42 | * @throws \Illuminate\Validation\ValidationException 43 | */ 44 | public function authenticate() 45 | { 46 | $this->ensureIsNotRateLimited(); 47 | 48 | if (! Auth::attempt($this->only('email', 'password'), $this->boolean('remember'))) { 49 | RateLimiter::hit($this->throttleKey()); 50 | 51 | throw ValidationException::withMessages([ 52 | 'email' => trans('auth.failed'), 53 | ]); 54 | } 55 | 56 | RateLimiter::clear($this->throttleKey()); 57 | } 58 | 59 | /** 60 | * Ensure the login request is not rate limited. 61 | * 62 | * @return void 63 | * 64 | * @throws \Illuminate\Validation\ValidationException 65 | */ 66 | public function ensureIsNotRateLimited() 67 | { 68 | if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) { 69 | return; 70 | } 71 | 72 | event(new Lockout($this)); 73 | 74 | $seconds = RateLimiter::availableIn($this->throttleKey()); 75 | 76 | throw ValidationException::withMessages([ 77 | 'email' => trans('auth.throttle', [ 78 | 'seconds' => $seconds, 79 | 'minutes' => ceil($seconds / 60), 80 | ]), 81 | ]); 82 | } 83 | 84 | /** 85 | * Get the rate limiting throttle key for the request. 86 | * 87 | * @return string 88 | */ 89 | public function throttleKey() 90 | { 91 | return Str::lower($this->input('email')).'|'.$this->ip(); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /resources/views/auth/register.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | @csrf 14 | 15 | 16 |
17 | 18 | 19 | 20 |
21 | 22 | 23 |
24 | 25 | 26 | 27 |
28 | 29 | 30 |
31 | 32 | 33 | 34 |
35 | 36 | 37 |
38 | 39 | 40 | 44 |
45 | 46 | 47 |
48 | 49 | 50 | 53 |
54 | 55 |
56 | 57 | {{ __('Already registered?') }} 58 | 59 | 60 | 61 | {{ __('Register') }} 62 | 63 |
64 |
65 |
66 |
67 | -------------------------------------------------------------------------------- /app/Http/Livewire/Dashboard/Admin/Room/Edit.php: -------------------------------------------------------------------------------- 1 | layoutData(['title' => 'Edit Room | Hollux']); 31 | } 32 | 33 | public function mount(Room $room) 34 | { 35 | $this->fill([ 36 | 'room' => $room, 37 | 'roomFacilities' => $room->facilities->pluck('facility_code')->toArray(), 38 | 'name' => $room->name, 39 | 'price' => $room->price, 40 | 'total_rooms' => $room->total_rooms, 41 | 'description' => $room->description, 42 | 'oldImage' => $room->image, 43 | 'explanation' => $room->explanation, 44 | 'facilities' => Facility::where('type', 'room')->get(), 45 | ]); 46 | 47 | $facilities = []; 48 | 49 | foreach ($this->facilities->pluck('code') as $facility) { 50 | $facilities[$facility] = in_array($facility, $this->roomFacilities) ? $facility : false; 51 | } 52 | 53 | $this->fill(['selectedFacilities' => $facilities]); 54 | } 55 | 56 | public function update() 57 | { 58 | $rules = [ 59 | 'name' => ['required'], 60 | 'description' => ['required'], 61 | 'total_rooms' => ['required', 'numeric'], 62 | 'price' => ['required', 'numeric'], 63 | 'explanation' => ['required'], 64 | ]; 65 | 66 | if ($this->image) { 67 | $rules['image'] = ['required', 'image', 'max:2084']; 68 | } 69 | 70 | $validatedData = $this->validate($rules); 71 | 72 | if ($this->image) { 73 | $validatedData['image'] = $this->image->store('img/rooms'); 74 | Storage::delete($this->room->image); 75 | } 76 | 77 | $this->room->update($validatedData); 78 | 79 | RoomHasFacility::where('room_id', $this->room->id)->delete(); 80 | 81 | if (count(array_filter($this->selectedFacilities))) { 82 | foreach (array_filter($this->selectedFacilities) as $facility) { 83 | RoomHasFacility::create([ 84 | 'room_id' => $this->room->id, 85 | 'facility_code' => $facility 86 | ]); 87 | } 88 | } 89 | 90 | $this->dispatchBrowserEvent('room:edited'); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /app/Models/User.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | protected $fillable = [ 23 | 'name', 24 | 'email', 25 | 'password', 26 | 'phone_number', 27 | 'avatar', 28 | 'code' 29 | ]; 30 | 31 | /** 32 | * The attributes that should be hidden for serialization. 33 | * 34 | * @var array 35 | */ 36 | protected $hidden = [ 37 | 'password', 38 | 'remember_token', 39 | ]; 40 | 41 | /** 42 | * The attributes that should be cast. 43 | * 44 | * @var array 45 | */ 46 | protected $casts = [ 47 | 'email_verified_at' => 'datetime', 48 | ]; 49 | 50 | public function scopeFilter($query, array $filters) 51 | { 52 | $query->when($filters['search'], function ($query, $search) { 53 | return $query->where('name', 'like', "%$search%")->orWhere('email', 'like', "%$search%")->orWhere('phone_number', 'like', "%$search%"); 54 | }); 55 | 56 | $query->when($filters['filter_role'], function ($query, $filter_role) { 57 | return $query->whereHas('roles', fn ($query) => $query->where('name', $filter_role)); 58 | }); 59 | } 60 | 61 | /** 62 | * Get the route key for the model. 63 | * 64 | * @return string 65 | */ 66 | public function getRouteKeyName() 67 | { 68 | return 'code'; 69 | } 70 | 71 | /** 72 | * Get all of the room_reviews for the User 73 | * 74 | * @return \Illuminate\Database\Eloquent\Relations\HasMany 75 | */ 76 | public function room_reviews(): HasMany 77 | { 78 | return $this->hasMany(RoomReview::class); 79 | } 80 | 81 | /** 82 | * Get all of the facility_reviews for the User 83 | * 84 | * @return \Illuminate\Database\Eloquent\Relations\HasMany 85 | */ 86 | public function facility_reviews(): HasMany 87 | { 88 | return $this->hasMany(FacilityReview::class); 89 | } 90 | 91 | /** 92 | * Get all of the reservations for the User 93 | * 94 | * @return \Illuminate\Database\Eloquent\Relations\HasMany 95 | */ 96 | public function reservations(): HasMany 97 | { 98 | return $this->hasMany(Reservation::class); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /resources/views/components/application-logo.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/Http/Kernel.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | protected $middleware = [ 17 | // \App\Http\Middleware\TrustHosts::class, 18 | \App\Http\Middleware\TrustProxies::class, 19 | \Fruitcake\Cors\HandleCors::class, 20 | \App\Http\Middleware\PreventRequestsDuringMaintenance::class, 21 | \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, 22 | \App\Http\Middleware\TrimStrings::class, 23 | \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, 24 | ]; 25 | 26 | /** 27 | * The application's route middleware groups. 28 | * 29 | * @var array> 30 | */ 31 | protected $middlewareGroups = [ 32 | 'web' => [ 33 | \App\Http\Middleware\EncryptCookies::class, 34 | \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, 35 | \Illuminate\Session\Middleware\StartSession::class, 36 | // \Illuminate\Session\Middleware\AuthenticateSession::class, 37 | \Illuminate\View\Middleware\ShareErrorsFromSession::class, 38 | \App\Http\Middleware\VerifyCsrfToken::class, 39 | \Illuminate\Routing\Middleware\SubstituteBindings::class, 40 | ], 41 | 42 | 'api' => [ 43 | // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, 44 | 'throttle:api', 45 | \Illuminate\Routing\Middleware\SubstituteBindings::class, 46 | ], 47 | ]; 48 | 49 | /** 50 | * The application's route middleware. 51 | * 52 | * These middleware may be assigned to groups or used individually. 53 | * 54 | * @var array 55 | */ 56 | protected $routeMiddleware = [ 57 | 'auth' => \App\Http\Middleware\Authenticate::class, 58 | 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 59 | 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, 60 | 'can' => \Illuminate\Auth\Middleware\Authorize::class, 61 | 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 62 | 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class, 63 | 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, 64 | 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 65 | 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, 66 | 'role' => \Spatie\Permission\Middlewares\RoleMiddleware::class, 67 | 'permission' => \Spatie\Permission\Middlewares\PermissionMiddleware::class, 68 | 'role_or_permission' => \Spatie\Permission\Middlewares\RoleOrPermissionMiddleware::class, 69 | ]; 70 | } 71 | -------------------------------------------------------------------------------- /config/queue.php: -------------------------------------------------------------------------------- 1 | env('QUEUE_CONNECTION', 'sync'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Queue Connections 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Here you may configure the connection information for each server that 24 | | is used by your application. A default configuration has been added 25 | | for each back-end shipped with Laravel. You are free to add more. 26 | | 27 | | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null" 28 | | 29 | */ 30 | 31 | 'connections' => [ 32 | 33 | 'sync' => [ 34 | 'driver' => 'sync', 35 | ], 36 | 37 | 'database' => [ 38 | 'driver' => 'database', 39 | 'table' => 'jobs', 40 | 'queue' => 'default', 41 | 'retry_after' => 90, 42 | 'after_commit' => false, 43 | ], 44 | 45 | 'beanstalkd' => [ 46 | 'driver' => 'beanstalkd', 47 | 'host' => 'localhost', 48 | 'queue' => 'default', 49 | 'retry_after' => 90, 50 | 'block_for' => 0, 51 | 'after_commit' => false, 52 | ], 53 | 54 | 'sqs' => [ 55 | 'driver' => 'sqs', 56 | 'key' => env('AWS_ACCESS_KEY_ID'), 57 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 58 | 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'), 59 | 'queue' => env('SQS_QUEUE', 'default'), 60 | 'suffix' => env('SQS_SUFFIX'), 61 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 62 | 'after_commit' => false, 63 | ], 64 | 65 | 'redis' => [ 66 | 'driver' => 'redis', 67 | 'connection' => 'default', 68 | 'queue' => env('REDIS_QUEUE', 'default'), 69 | 'retry_after' => 90, 70 | 'block_for' => null, 71 | 'after_commit' => false, 72 | ], 73 | 74 | ], 75 | 76 | /* 77 | |-------------------------------------------------------------------------- 78 | | Failed Queue Jobs 79 | |-------------------------------------------------------------------------- 80 | | 81 | | These options configure the behavior of failed queue job logging so you 82 | | can control which database and table are used to store the jobs that 83 | | have failed. You may change them to any database / table you wish. 84 | | 85 | */ 86 | 87 | 'failed' => [ 88 | 'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'), 89 | 'database' => env('DB_CONNECTION', 'mysql'), 90 | 'table' => 'failed_jobs', 91 | ], 92 | 93 | ]; 94 | -------------------------------------------------------------------------------- /app/Http/Livewire/Dashboard/User/Review/Room/Index.php: -------------------------------------------------------------------------------- 1 | 'reviewEdited', 'review:deleted' => 'reviewDeleted']; 16 | 17 | public function reviewEdited() 18 | { 19 | $this->dispatchBrowserEvent('review:edited'); 20 | } 21 | 22 | public function reviewDeleted() 23 | { 24 | $this->dispatchBrowserEvent('review:deleted'); 25 | } 26 | 27 | public function render() 28 | { 29 | return view('livewire.dashboard.user.review.room.index')->layoutData(['title' => 'Room Review Dashboard | Hollux']); 30 | } 31 | 32 | public function mount() 33 | { 34 | $this->fill(['reviews' => auth()->user()->room_reviews]); 35 | } 36 | 37 | public function edit(RoomReview $roomReview) 38 | { 39 | $this->dispatchBrowserEvent('review:edit'); 40 | $this->fill([ 41 | 'review' => $roomReview, 42 | 'star' => $roomReview->star, 43 | 'message' => $roomReview->message 44 | ]); 45 | } 46 | 47 | public function update() 48 | { 49 | $validatedData = $this->validate([ 50 | 'message' => ['required'], 51 | 'star' => ['required'] 52 | ]); 53 | 54 | $this->review->update($validatedData); 55 | 56 | $allReviews = RoomReview::where('room_code', $this->review->room->code)->get(); 57 | 58 | if (count($allReviews) > 0) { 59 | $rate = 0; 60 | 61 | foreach ($allReviews as $review) { 62 | $rate += $review->star; 63 | } 64 | 65 | $rate /= $allReviews->count(); 66 | } else { 67 | $rate = 0; 68 | } 69 | 70 | $this->review->room->update(['rate' => $rate]); 71 | 72 | $this->emitSelf('review:edited'); 73 | } 74 | 75 | public function setRating($val) 76 | { 77 | if ($this->star == $val) { 78 | $this->star = null; 79 | } else { 80 | $this->star = $val; 81 | } 82 | } 83 | 84 | public function delete(RoomReview $roomReview) 85 | { 86 | $this->dispatchBrowserEvent('review:delete'); 87 | $this->fill([ 88 | 'review' => $roomReview, 89 | 'star' => $roomReview->star, 90 | 'message' => $roomReview->message 91 | ]); 92 | } 93 | 94 | public function destroy() 95 | { 96 | $allReviews = RoomReview::where('room_code', $this->review->code)->where('code', '<>', $this->review->code)->get(); 97 | 98 | $rate = 0; 99 | 100 | if (count($allReviews) > 0) { 101 | foreach ($allReviews as $review) { 102 | $rate += $review->star; 103 | } 104 | 105 | $rate /= $allReviews->count(); 106 | } 107 | 108 | $this->review->room->update(['rate' => $rate]); 109 | $this->review->delete(); 110 | $this->emitSelf('review:deleted'); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /resources/views/livewire/dashboard/admin/galery/create.blade.php: -------------------------------------------------------------------------------- 1 |
2 | 3 | 46 |
-------------------------------------------------------------------------------- /app/Http/Livewire/Dashboard/User/Review/Facility/Index.php: -------------------------------------------------------------------------------- 1 | 'reviewEdited', 'review:deleted' => 'reviewDeleted']; 16 | 17 | public function reviewEdited() 18 | { 19 | $this->dispatchBrowserEvent('review:edited'); 20 | } 21 | 22 | public function reviewDeleted() 23 | { 24 | $this->dispatchBrowserEvent('review:deleted'); 25 | } 26 | 27 | public function render() 28 | { 29 | return view('livewire.dashboard.user.review.facility.index')->layoutData(['title' => 'Facility Review Dashboard | Hollux']); 30 | } 31 | 32 | public function mount() 33 | { 34 | $this->fill(['reviews' => auth()->user()->facility_reviews]); 35 | } 36 | 37 | public function edit(FacilityReview $facilityReview) 38 | { 39 | $this->dispatchBrowserEvent('review:edit'); 40 | $this->fill([ 41 | 'review' => $facilityReview, 42 | 'star' => $facilityReview->star, 43 | 'message' => $facilityReview->message 44 | ]); 45 | } 46 | 47 | public function update() 48 | { 49 | $validatedData = $this->validate([ 50 | 'message' => ['required'], 51 | 'star' => ['required'] 52 | ]); 53 | 54 | $this->review->update($validatedData); 55 | 56 | $allReviews = FacilityReview::where('facility_code', $this->review->facility->code)->get(); 57 | 58 | if (count($allReviews) > 0) { 59 | $rate = 0; 60 | 61 | foreach ($allReviews as $review) { 62 | $rate += $review->star; 63 | } 64 | 65 | $rate /= $allReviews->count(); 66 | } else { 67 | $rate = 0; 68 | } 69 | 70 | $this->review->facility->update(['rate' => $rate]); 71 | 72 | $this->emitSelf('review:edited'); 73 | } 74 | 75 | public function setRating($val) 76 | { 77 | if ($this->star == $val) { 78 | $this->star = null; 79 | } else { 80 | $this->star = $val; 81 | } 82 | } 83 | 84 | public function delete(FacilityReview $facilityReview) 85 | { 86 | $this->dispatchBrowserEvent('review:delete'); 87 | $this->fill([ 88 | 'review' => $facilityReview, 89 | 'star' => $facilityReview->star, 90 | 'message' => $facilityReview->message 91 | ]); 92 | } 93 | 94 | public function destroy() 95 | { 96 | $allReviews = FacilityReview::where('facility_code', $this->review->code)->where('code', '<>', $this->review->code)->get(); 97 | 98 | $rate = 0; 99 | 100 | if (count($allReviews) > 0) { 101 | foreach ($allReviews as $review) { 102 | $rate += $review->star; 103 | } 104 | 105 | $rate /= $allReviews->count(); 106 | } 107 | 108 | $this->review->facility->update(['rate' => $rate]); 109 | $this->review->delete(); 110 | $this->emitSelf('review:deleted'); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /resources/views/livewire/room/index.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

Room Choices For You

5 |

We have a choice of {{ $rooms->count() }} different room types that you can choose according to your needs

6 |
7 |
8 | @forelse ($rooms as $room) 9 |
10 |
11 | {{ $room->name }} 12 |
13 |
14 |
15 | 16 | {{ $room->views }} 17 |
18 |
19 | 20 | {{ $room->rate }} 21 |
22 |
23 | 24 | {{ $room->reviews->count() }} 25 |
26 |
27 | 28 | ${{ $room->price }}/night 29 |
30 |
31 |
32 |
33 |

{{ $room->name }}

34 | 35 | Learn more 36 | 37 | 38 |
39 |

40 | {{ $room->description }} 41 |

42 |
43 | {{ (int) $room->total_rooms - (int) $room->reservations->count()}} rooms available 44 |
45 | @empty 46 |

There is nothing here

47 | @endforelse 48 |
49 |
50 |
--------------------------------------------------------------------------------