├── .devcontainer ├── Dockerfile ├── devcontainer.json └── docker-compose.yml ├── .editorconfig ├── .env.example ├── .gitattributes ├── .gitignore ├── Dockerfile ├── README.md ├── SSP2_FINAL2_CB010454.pdf ├── Salon Bliss.postman_collection.json ├── app ├── Actions │ ├── Fortify │ │ ├── CreateNewUser.php │ │ ├── PasswordValidationRules.php │ │ ├── ResetUserPassword.php │ │ ├── UpdateUserPassword.php │ │ └── UpdateUserProfileInformation.php │ └── Jetstream │ │ └── DeleteUser.php ├── Console │ └── Kernel.php ├── Enums │ └── UserRolesEnum.php ├── Events │ └── ServiceViewEvent.php ├── Exceptions │ └── Handler.php ├── Http │ ├── Controllers │ │ ├── AdminDashboardHomeController.php │ │ ├── CartController.php │ │ ├── Controller.php │ │ ├── CustomerAPI.php │ │ ├── DashboardHomeController.php │ │ ├── DisplayDeal.php │ │ ├── DisplayService.php │ │ ├── HomePageController.php │ │ ├── ManageService.php │ │ ├── ServicesAPI.php │ │ ├── UserController.php │ │ └── UserSuspensionController.php │ ├── Kernel.php │ ├── Livewire │ │ ├── AddingServiceToCart.php │ │ ├── CreateService.php │ │ ├── CustomerServicesView.php │ │ ├── ManageAppointments.php │ │ ├── ManageCategories.php │ │ ├── ManageDeals.php │ │ ├── ManageLocations.php │ │ ├── ManageServices copy.php │ │ ├── ManageServices.php │ │ └── ServiceTable.php │ └── Middleware │ │ ├── Authenticate.php │ │ ├── EncryptCookies.php │ │ ├── PreventRequestsDuringMaintenance.php │ │ ├── RedirectIfAuthenticated.php │ │ ├── TrimStrings.php │ │ ├── TrustHosts.php │ │ ├── TrustProxies.php │ │ ├── UserSuspendedRedirect.php │ │ ├── ValidateRole.php │ │ ├── ValidateSignature.php │ │ └── VerifyCsrfToken.php ├── Jobs │ ├── AnalyticsJob.php │ ├── SendAppointmentConfirmationMailJob.php │ └── SendNewServicePromoMailJob.php ├── Models │ ├── Appointment.php │ ├── Cart.php │ ├── Category.php │ ├── Deal.php │ ├── Location.php │ ├── Role.php │ ├── Service.php │ ├── ServiceHit.php │ ├── TimeSlot.php │ └── User.php ├── Notifications │ ├── AppointmentConfirmationNotification.php │ └── NewServiceReleasedNotification.php ├── Providers │ ├── AnalyticsServiceProvider.php │ ├── AppServiceProvider.php │ ├── AuthServiceProvider.php │ ├── BroadcastServiceProvider.php │ ├── EventServiceProvider.php │ ├── FortifyServiceProvider.php │ ├── JetstreamServiceProvider.php │ ├── RouteServiceProvider.php │ └── TelescopeServiceProvider.php ├── Singletons │ └── AnalyticsSingleton.php └── View │ └── Components │ ├── AppLayout.php │ ├── DaySchedule.php │ └── GuestLayout.php ├── artisan ├── bootstrap ├── app.php └── cache │ └── .gitignore ├── composer.json ├── composer.lock ├── config ├── app.php ├── auth.php ├── broadcasting.php ├── cache.php ├── cors.php ├── database.php ├── filesystems.php ├── fortify.php ├── hashing.php ├── jetstream.php ├── logging.php ├── mail.php ├── queue.php ├── sanctum.php ├── services.php ├── session.php ├── telescope.php └── view.php ├── database ├── .gitignore ├── database.sqlite ├── factories │ ├── DealFactory.php │ └── UserFactory.php ├── migrations │ ├── 2014_10_12_000000_create_users_table.php │ ├── 2014_10_12_100000_create_password_reset_tokens_table.php │ ├── 2014_10_12_200000_add_two_factor_columns_to_users_table.php │ ├── 2019_08_19_000000_create_failed_jobs_table.php │ ├── 2019_12_14_000001_create_personal_access_tokens_table.php │ ├── 2023_05_08_135614_create_sessions_table.php │ ├── 2023_05_13_144600_create_roles_table.php │ ├── 2023_05_13_155544_add_role_id_to_users_table.php │ ├── 2023_06_01_114923_create_locations_table.php │ ├── 2023_06_03_121845_create_services_table.php │ ├── 2023_06_07_091657_create_deals_table.php │ ├── 2023_09_18_134208_create_categories_table.php │ ├── 2023_09_26_040647_create_jobs_table.php │ ├── 2023_09_26_085642_create_service_hits_table.php │ ├── 2023_09_26_185410_create_time_slots_table.php │ ├── 2023_09_27_021712_create_carts_table.php │ └── 2023_09_27_023637_create_appointments_table.php └── seeders │ ├── DatabaseSeeder.php │ ├── LocationSeeder.php │ ├── ServicesSeeder.php │ └── TimeSlotSeeder.php ├── docker-compose.yml ├── init_codespace.sh ├── package-lock.json ├── package.json ├── phpunit.xml ├── postcss.config.js ├── public ├── .htaccess ├── favicon.ico ├── images │ ├── Gallery │ │ ├── gallery1.jpg │ │ ├── gallery2.jpg │ │ ├── gallery3.jpg │ │ ├── gallery4.jpg │ │ ├── gallery5.jpg │ │ └── gallery6.jpg │ ├── Salon2.jpg │ ├── banner-salon.png │ ├── hair-coloring.jpg │ ├── hair-cut.jpg │ ├── hair.jpg │ ├── logo-pink.png │ ├── logo-white.png │ ├── makeup.jpg │ ├── nail-coloring.jpg │ ├── nails.jpg │ ├── salon1.png │ └── skin.jpg ├── index.php ├── robots.txt └── vendor │ └── telescope │ ├── app-dark.css │ ├── app.css │ ├── app.js │ ├── favicon.ico │ └── mix-manifest.json ├── readme-assets └── screenshots │ ├── admin-dash-analytics.png │ ├── admin-location-day-schedule.png │ ├── analytics_recording.png │ ├── appointment_confirm_email_queue.png │ ├── cart.png │ ├── customer-analytics.png │ ├── customer-details-analytics.png │ ├── dashboard.png │ ├── homepage.png │ ├── locations-manage.png │ ├── logo-readme.png │ ├── manage-appointment-admin-view.png │ ├── manage-deals.png │ ├── manage-services.png │ ├── manage-users.png │ ├── new-service-email.png │ ├── new_service_promo_queue.png │ ├── queued_mail_appointment_confirm_email.png │ ├── service-analytics.png │ ├── services-page.png │ ├── view-a-service.png │ └── view-appointment-customer.png ├── resources ├── css │ └── app.css ├── js │ ├── app.js │ └── bootstrap.js ├── markdown │ ├── policy.md │ └── terms.md └── views │ ├── api │ ├── api-token-manager.blade.php │ └── index.blade.php │ ├── auth │ ├── confirm-password.blade.php │ ├── forgot-password.blade.php │ ├── login.blade.php │ ├── register.blade.php │ ├── reset-password.blade.php │ ├── two-factor-challenge.blade.php │ └── verify-email.blade.php │ ├── components │ ├── action-message.blade.php │ ├── action-section.blade.php │ ├── application-logo.blade.php │ ├── application-mark.blade.php │ ├── authentication-card-logo.blade.php │ ├── authentication-card.blade.php │ ├── banner.blade.php │ ├── button.blade.php │ ├── checkbox.blade.php │ ├── confirmation-modal.blade.php │ ├── confirms-password.blade.php │ ├── danger-button.blade.php │ ├── dashboard.blade.php │ ├── dashboard │ │ └── navlinks.blade.php │ ├── day-schedule.blade.php │ ├── dialog-modal.blade.php │ ├── dropdown-link.blade.php │ ├── dropdown.blade.php │ ├── form-section.blade.php │ ├── input-error.blade.php │ ├── input.blade.php │ ├── label.blade.php │ ├── modal.blade.php │ ├── nav-link.blade.php │ ├── navigation-menu.blade.php │ ├── responsive-nav-link.blade.php │ ├── secondary-button.blade.php │ ├── section-border.blade.php │ ├── section-title.blade.php │ ├── service-card.blade.php │ ├── switchable-team.blade.php │ ├── validation-errors.blade.php │ ├── web │ │ └── navlinks.blade.php │ └── welcome.blade.php │ ├── dashboard │ ├── admin-employee.blade.php │ ├── customer.blade.php │ ├── manage-appointments │ │ └── index.blade.php │ ├── manage-categories │ │ └── index.blade.php │ ├── manage-deals │ │ └── index.blade.php │ ├── manage-locations │ │ └── index.blade.php │ ├── manage-services │ │ ├── create.blade.php │ │ └── index.blade.php │ └── manage-users │ │ ├── create-user.blade.php │ │ ├── index.blade.php │ │ └── show-user.blade.php │ ├── emails │ └── team-invitation.blade.php │ ├── layouts │ ├── app.blade.php │ └── guest.blade.php │ ├── livewire │ ├── adding-service-to-cart.blade.php │ ├── create-service.blade.php │ ├── customer-services-view.blade.php │ ├── manage-appointments.blade.php │ ├── manage-categories.blade.php │ ├── manage-deals.blade.php │ ├── manage-locations.blade.php │ ├── manage-services.blade copy.php │ └── manage-services.blade.php │ ├── policy.blade.php │ ├── profile │ ├── delete-user-form.blade.php │ ├── logout-other-browser-sessions-form.blade.php │ ├── show.blade.php │ ├── two-factor-authentication-form.blade.php │ ├── update-password-form.blade.php │ └── update-profile-information-form.blade.php │ ├── terms.blade.php │ ├── vendor │ └── mail │ │ ├── html │ │ ├── button.blade.php │ │ ├── footer.blade.php │ │ ├── header.blade.php │ │ ├── layout.blade.php │ │ ├── message.blade.php │ │ ├── panel.blade.php │ │ ├── subcopy.blade.php │ │ ├── table.blade.php │ │ └── themes │ │ │ └── default.css │ │ └── text │ │ ├── button.blade.php │ │ ├── footer.blade.php │ │ ├── header.blade.php │ │ ├── layout.blade.php │ │ ├── message.blade.php │ │ ├── panel.blade.php │ │ ├── subcopy.blade.php │ │ └── table.blade.php │ └── web │ ├── cart.blade.php │ ├── deals.blade.php │ ├── home.blade.php │ ├── services.blade.php │ └── view-service.blade.php ├── routes ├── api.php ├── channels.php ├── console.php └── web.php ├── storage ├── app │ ├── .gitignore │ └── public │ │ └── .gitignore ├── debugbar │ └── .gitignore ├── framework │ ├── .gitignore │ ├── cache │ │ ├── .gitignore │ │ └── data │ │ │ └── .gitignore │ ├── sessions │ │ └── .gitignore │ ├── testing │ │ └── .gitignore │ └── views │ │ └── .gitignore └── logs │ └── .gitignore ├── tailwind.config.js ├── tests ├── CreatesApplication.php ├── Feature │ ├── ApiTokenPermissionsTest.php │ ├── AuthenticationTest.php │ ├── BrowserSessionsTest.php │ ├── CreateApiTokenTest.php │ ├── DeleteAccountTest.php │ ├── DeleteApiTokenTest.php │ ├── EmailVerificationTest.php │ ├── ExampleTest.php │ ├── PasswordConfirmationTest.php │ ├── PasswordResetTest.php │ ├── ProfileInformationTest.php │ ├── RegistrationTest.php │ ├── TwoFactorAuthenticationSettingsTest.php │ └── UpdatePasswordTest.php ├── TestCase.php └── Unit │ └── ExampleTest.php └── vite.config.js /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/vscode-remote/devcontainer.json or the definition README at 2 | // https://github.com/microsoft/vscode-dev-containers/tree/master/containers/php-7 3 | { 4 | "name": "PHP 8.1", 5 | "dockerComposeFile": "docker-compose.yml", 6 | "service": "php", 7 | // Use 'settings' to set *default* container specific settings.json values on container create. 8 | // You can edit these settings after create using File > Preferences > Settings > Remote. 9 | "settings": { 10 | "terminal.integrated.shell.linux": "/bin/bash", 11 | "phpSniffer.executablesFolder": "./vendor/bin/", 12 | "phpSniffer.run": "onSave" 13 | }, 14 | 15 | // Uncomment the next line if you want to publish any ports. 16 | "forwardPorts": [8000], 17 | 18 | // Uncomment the next line to run commands after the container is created. 19 | "postCreateCommand": "", 20 | 21 | // Uncomment the next line to use a non-root user. On Linux, this will prevent 22 | // new files getting created as root, but you may need to update the USER_UID 23 | // and USER_GID in .devcontainer/Dockerfile to match your user if not 1000. 24 | "runArgs": [ 25 | "-u", "vscode" , 26 | "-v", "${env:HOME}${env:USERPROFILE}/.ssh:/home/vscode/.ssh" 27 | ], 28 | 29 | 30 | // Add the IDs of extensions you want installed when the container is created in the array below. 31 | "extensions": [ 32 | "bmewburn.vscode-intelephense-client", 33 | "MehediDracula.php-namespace-resolver", 34 | "MS-vsliveshare.vsliveshare-pack", 35 | "EditorConfig.EditorConfig", 36 | "wongjn.php-sniffer" 37 | ] 38 | } -------------------------------------------------------------------------------- /.devcontainer/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | php: 5 | build: . 6 | volumes: 7 | - ..:/workspace:cached 8 | ports: 9 | # For use with PHP (e.g. `php -S localhost:8080`) 10 | - "8000:8000" 11 | command: sleep infinity 12 | mariadb: 13 | image: mariadb:10.11 14 | expose: 15 | # Expose mariadb port to php service (Access as hostname "mariadb" from within php container) 16 | - "3306" 17 | # Uncomment to allow access to mariadb from external tools 18 | # ports: 19 | # - "3306:3306" 20 | environment: 21 | MYSQL_ROOT_PASSWORD: secret 22 | MYSQL_DATABASE: laravel 23 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.{yml,yaml}] 15 | indent_size = 2 16 | 17 | [docker-compose.yml] 18 | indent_size = 4 19 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | APP_NAME='Salon Bliss' 2 | APP_ENV=local 3 | APP_KEY=base64:yoQqoHG+sUSaUg6A3FxVFKUSXwf0eTnKKPkrt4VNZpk= 4 | APP_DEBUG=true 5 | APP_URL=http://127.0.0.1:8000 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=laravel 15 | # DB_USERNAME=root 16 | # DB_PASSWORD= 17 | 18 | DB_CONNECTION=sqlite 19 | DB_DATABASE=F:\APIIT\Level_5\Sem_1\Assignmnets\CRM\code_repo\crm-with-jetstream\database\database.sqlite 20 | DB_FOREIGN_KEYS=true 21 | 22 | BROADCAST_DRIVER=log 23 | CACHE_DRIVER=file 24 | FILESYSTEM_DISK=local 25 | #QUEUE_CONNECTION=sync 26 | QUEUE_CONNECTION=database 27 | SESSION_DRIVER=database 28 | SESSION_LIFETIME=120 29 | 30 | MEMCACHED_HOST=127.0.0.1 31 | 32 | REDIS_HOST=127.0.0.1 33 | REDIS_PASSWORD=null 34 | REDIS_PORT=6379 35 | 36 | MAIL_MAILER=smtp 37 | MAIL_HOST=mailpit 38 | MAIL_PORT=1025 39 | MAIL_USERNAME=null 40 | MAIL_PASSWORD=null 41 | MAIL_ENCRYPTION=null 42 | MAIL_FROM_ADDRESS="hello@example.com" 43 | MAIL_FROM_NAME="${APP_NAME}" 44 | 45 | AWS_ACCESS_KEY_ID= 46 | AWS_SECRET_ACCESS_KEY= 47 | AWS_DEFAULT_REGION=us-east-1 48 | AWS_BUCKET= 49 | AWS_USE_PATH_STYLE_ENDPOINT=false 50 | 51 | PUSHER_APP_ID= 52 | PUSHER_APP_KEY= 53 | PUSHER_APP_SECRET= 54 | PUSHER_HOST= 55 | PUSHER_PORT=443 56 | PUSHER_SCHEME=https 57 | PUSHER_APP_CLUSTER=mt1 58 | 59 | VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}" 60 | VITE_PUSHER_HOST="${PUSHER_HOST}" 61 | VITE_PUSHER_PORT="${PUSHER_PORT}" 62 | VITE_PUSHER_SCHEME="${PUSHER_SCHEME}" 63 | VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" 64 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | 3 | *.blade.php diff=html 4 | *.css diff=css 5 | *.html diff=html 6 | *.md diff=markdown 7 | *.php diff=php 8 | 9 | /.github export-ignore 10 | CHANGELOG.md export-ignore 11 | .styleci.yml export-ignore 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.phpunit.cache 2 | /node_modules 3 | /public/build 4 | /public/hot 5 | /public/storage 6 | /storage/*.key 7 | /vendor 8 | .env 9 | .env.backup 10 | .env.production 11 | .phpunit.result.cache 12 | Homestead.json 13 | Homestead.yaml 14 | auth.json 15 | npm-debug.log 16 | yarn-error.log 17 | /.fleet 18 | /.idea 19 | /.vscode 20 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:8.1-fpm-alpine 2 | 3 | RUN docker-php-ext-install pdo pdo_mysql sockets 4 | RUN curl -sS https://getcomposer.org/installer | php -- \ 5 | --install-dir=/usr/local/bin --filename=composer 6 | 7 | COPY --from=composer:latest /usr/bin/composer /usr/bin/composer 8 | 9 | WORKDIR /app 10 | COPY . . 11 | 12 | # Copy the .env file 13 | COPY .env .env 14 | 15 | RUN composer install 16 | 17 | #ref : https://medium.com/@eloufirhatim/add-docker-to-an-existing-laravel-10-project-1e6c383fc7a8 18 | 19 | 20 | # prob better? check later https://adambailey.io/blog/dockerize-a-laravel-application/ 21 | # to build run 22 | # docker-compose build 23 | -------------------------------------------------------------------------------- /SSP2_FINAL2_CB010454.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sachintha-lk/CRM-laravel/dae81639d5e9697f62eaa4990e7a24c9c0e19291/SSP2_FINAL2_CB010454.pdf -------------------------------------------------------------------------------- /app/Actions/Fortify/CreateNewUser.php: -------------------------------------------------------------------------------- 1 | $input 20 | */ 21 | public function create(array $input): User 22 | { 23 | Validator::make($input, [ 24 | 'name' => ['required', 'string', 'max:255'], 25 | 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'], 26 | 'password' => $this->passwordRules(), 27 | 'phone_number' => ['required', 'string', 'regex:/^[0-9]{10}$/', 'unique:users'], 28 | 'terms' => Jetstream::hasTermsAndPrivacyPolicyFeature() ? ['accepted', 'required'] : '', 29 | ])->validate(); 30 | 31 | // if ($input['role_id'] == 2) { 32 | // $role_id = UserRolesEnum::Employee; 33 | // } else { 34 | $role_id = UserRolesEnum::Customer; 35 | // } 36 | 37 | return User::create([ 38 | 'name' => $input['name'], 39 | 'email' => $input['email'], 40 | 'password' => Hash::make($input['password']), 41 | 'phone_number' => $input['phone_number'], 42 | 'role_id' => $role_id, 43 | ]); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/Actions/Fortify/PasswordValidationRules.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | protected function passwordRules(): array 15 | { 16 | return ['required', 'string', new Password, 'confirmed']; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/Actions/Fortify/ResetUserPassword.php: -------------------------------------------------------------------------------- 1 | $input 18 | */ 19 | public function reset(User $user, array $input): void 20 | { 21 | Validator::make($input, [ 22 | 'password' => $this->passwordRules(), 23 | ])->validate(); 24 | 25 | $user->forceFill([ 26 | 'password' => Hash::make($input['password']), 27 | ])->save(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/Actions/Fortify/UpdateUserPassword.php: -------------------------------------------------------------------------------- 1 | $input 18 | */ 19 | public function update(User $user, array $input): void 20 | { 21 | Validator::make($input, [ 22 | 'current_password' => ['required', 'string', 'current_password:web'], 23 | 'password' => $this->passwordRules(), 24 | ], [ 25 | 'current_password.current_password' => __('The provided password does not match your current password.'), 26 | ])->validateWithBag('updatePassword'); 27 | 28 | $user->forceFill([ 29 | 'password' => Hash::make($input['password']), 30 | ])->save(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/Actions/Fortify/UpdateUserProfileInformation.php: -------------------------------------------------------------------------------- 1 | $input 17 | */ 18 | public function update(User $user, array $input): void 19 | { 20 | Validator::make($input, [ 21 | 'name' => ['required', 'string', 'max:255'], 22 | 'email' => ['required', 'email', 'max:255', Rule::unique('users')->ignore($user->id)], 23 | 'phone_number' => ['required', 'string', 'regex:/^[0-9]{10}$/',Rule::unique('users')->ignore($user->id)], 24 | 'photo' => ['nullable', 'mimes:jpg,jpeg,png', 'max:1024'], 25 | ])->validateWithBag('updateProfileInformation'); 26 | 27 | if (isset($input['photo'])) { 28 | $user->updateProfilePhoto($input['photo']); 29 | } 30 | 31 | if ($input['email'] !== $user->email && 32 | $user instanceof MustVerifyEmail) { 33 | $this->updateVerifiedUser($user, $input); 34 | } else { 35 | $user->forceFill([ 36 | 'name' => $input['name'], 37 | 'email' => $input['email'], 38 | 'phone_number' => $input['phone_number'], 39 | ])->save(); 40 | } 41 | } 42 | 43 | /** 44 | * Update the given verified user's profile information. 45 | * 46 | * @param array $input 47 | */ 48 | protected function updateVerifiedUser(User $user, array $input): void 49 | { 50 | $user->forceFill([ 51 | 'name' => $input['name'], 52 | 'email' => $input['email'], 53 | 'phone_number' => $input['phone_number'], 54 | 'email_verified_at' => null, 55 | ])->save(); 56 | 57 | $user->sendEmailVerificationNotification(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/Actions/Jetstream/DeleteUser.php: -------------------------------------------------------------------------------- 1 | deleteProfilePhoto(); 16 | $user->tokens->each->delete(); 17 | $user->delete(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/Console/Kernel.php: -------------------------------------------------------------------------------- 1 | command('inspire')->hourly(); 16 | } 17 | 18 | /** 19 | * Register the commands for the application. 20 | */ 21 | protected function commands(): void 22 | { 23 | $this->load(__DIR__.'/Commands'); 24 | 25 | require base_path('routes/console.php'); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/Enums/UserRolesEnum.php: -------------------------------------------------------------------------------- 1 | service = $service; 19 | // } 20 | // 21 | //} 22 | -------------------------------------------------------------------------------- /app/Exceptions/Handler.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | protected $dontFlash = [ 16 | 'current_password', 17 | 'password', 18 | 'password_confirmation', 19 | ]; 20 | 21 | /** 22 | * Register the exception handling callbacks for the application. 23 | */ 24 | public function register(): void 25 | { 26 | $this->reportable(function (Throwable $e) { 27 | // 28 | }); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/Http/Controllers/Controller.php: -------------------------------------------------------------------------------- 1 | user()->role_id == 1 || auth()->user()->role_id == 2) { 13 | $adminDashboardHomeController = new AdminDashboardHomeController(); 14 | return $adminDashboardHomeController->index(); 15 | } else if (auth()->user()->role_id == 3) { 16 | return view('dashboard.customer'); 17 | } 18 | else { 19 | return redirect()->route('home')->with('error', 'You are not authorized to perform this action.'); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/Http/Controllers/DisplayDeal.php: -------------------------------------------------------------------------------- 1 | ', now()) 11 | ->where('is_hidden', false) 12 | ->orderBy('end_date', 'asc') 13 | ->take(3) 14 | ->get(); 15 | 16 | // get the top 3 popular services using most bookings in the last 30 days 17 | $popularServices = \App\Models\Service::withCount('appointments') 18 | ->orderBy('appointments_count', 'desc') 19 | ->take(3) 20 | ->where('is_hidden', false) 21 | ->get(); 22 | 23 | 24 | return view('web.home', compact('deals', 'popularServices')); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/Http/Controllers/ManageService.php: -------------------------------------------------------------------------------- 1 | $services, 20 | ] 21 | ); 22 | } 23 | 24 | /** 25 | * Show the form for creating a new resource. 26 | */ 27 | public function create() 28 | { 29 | return view('dashboard.manage-services.create'); 30 | } 31 | 32 | /** 33 | * Store a newly created resource in storage. 34 | */ 35 | public function store() 36 | { 37 | // 38 | } 39 | 40 | /** 41 | * Display the specified resource. 42 | */ 43 | public function show() 44 | { 45 | // 46 | } 47 | 48 | /** 49 | * Show the form for editing the specified resource. 50 | */ 51 | public function edit() 52 | { 53 | // 54 | } 55 | 56 | /** 57 | * Update the specified resource in storage. 58 | */ 59 | public function update() 60 | { 61 | // 62 | } 63 | 64 | /** 65 | * Remove the specified resource from storage. 66 | */ 67 | public function destroy(Service $service) 68 | { 69 | // 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /app/Http/Controllers/UserSuspensionController.php: -------------------------------------------------------------------------------- 1 | route('manageusers')->with('errormsg', 'You cannot suspend admin.'); 16 | } 17 | $user = User::findOrFail($id); 18 | // throw new Exception('test'); 19 | 20 | $user->status = 0; 21 | $user->save(); 22 | return redirect()->route('manageusers')->with('success', 'User suspended successfully.'); 23 | 24 | } catch (Exception $e) { 25 | return redirect()->route('manageusers')->with('errormsg', 'User suspension failed.'); 26 | } 27 | } 28 | 29 | public function activate(string $id) { 30 | try { 31 | $user = User::findOrFail($id); 32 | $user->status = 1; 33 | $user->save(); 34 | return redirect()->route('manageusers')->with('success', 'User activated successfully.'); 35 | 36 | } catch (Exception $e) { 37 | return redirect()->route('manageusers')->with('errormsg', 'User activation failed.'); 38 | } 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /app/Http/Livewire/CreateService.php: -------------------------------------------------------------------------------- 1 | 'required|string|min:1|max:255', 22 | 'description' => 'required|string|min:1|max:255', 23 | 'image' => 'required|image|mimes:jpg,jpeg,png,svg,gif|max:2048', 24 | 'price' => 'required|numeric|min:0', 25 | 'is_hidden' => 'boolean', 26 | ]; 27 | 28 | public function submit() { 29 | 30 | $this->validate(); 31 | 32 | // store image in a safe way 33 | // $this->image->store('public/images'); 34 | $this->image->store('images', 'public'); 35 | 36 | Service::create([ 37 | 'name' => $this->name, 38 | 'description' => $this->description, 39 | 'image' => $this->image->hashName(), 40 | 'price' => $this->price, 41 | 'is_hidden' => $this->is_hidden, 42 | ]); 43 | 44 | session()->flash('message', 'Service successfully created.'); 45 | 46 | return redirect()->route('dashboard'); 47 | } 48 | 49 | public function render() 50 | { 51 | return view('livewire.create-service'); 52 | } 53 | 54 | 55 | } 56 | -------------------------------------------------------------------------------- /app/Http/Livewire/ManageCategories.php: -------------------------------------------------------------------------------- 1 | ['except' => ''], 17 | ]; 18 | 19 | public $category; 20 | 21 | public $confirmingCategoryAdd; 22 | 23 | public $confirmCategoryDeletion = false; 24 | public $confirmingCategoryDeletion = false; 25 | 26 | protected $rules = [ 27 | "category.name" => "required|string|max:255", 28 | ]; 29 | public function render() 30 | { 31 | $this->categories = Category::when($this->search, function ($query) { 32 | $query->where('name', 'like', '%'.$this->search.'%'); 33 | })->paginate(10); 34 | 35 | return view('livewire.manage-categories', [ 36 | 'categories' => $this->categories, 37 | ]); 38 | } 39 | 40 | public function confirmCategoryEdit(Category $category) { 41 | $this->category = $category; 42 | $this->confirmingCategoryAdd= true; 43 | } 44 | public function confirmCategoryDeletion() { 45 | $this->confirmingCategoryDeletion = true; 46 | } 47 | 48 | public function saveCategory() { 49 | $this->validate(); 50 | 51 | if (isset($this->category->id)) { 52 | $this->category->save(); 53 | } else { 54 | Category::create( 55 | [ 56 | 'name' => $this->category['name'], 57 | ] 58 | ); 59 | } 60 | 61 | $this->confirmingCategoryAdd = false; 62 | $this->category = null; 63 | } 64 | 65 | public function deleteCategory(Category $categoryId) { 66 | $this->category = $categoryId; 67 | $this->category->delete(); 68 | $this->confirmingCategoryDeletion = false; 69 | } 70 | 71 | public function confirmCategoryAdd() { 72 | $this->confirmingCategoryAdd = true; 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /app/Http/Livewire/ManageLocations.php: -------------------------------------------------------------------------------- 1 | ['except' => ''], 17 | ]; 18 | 19 | public $location; 20 | 21 | public $confirmingLocationAdd; 22 | 23 | public $confirmLocationDeletion = false; 24 | public $confirmingLocationDeletion = false; 25 | 26 | protected $rules = [ 27 | "location.name" => "required|string|max:255", 28 | "location.address" => "required|string|max:255", 29 | "location.telephone_number" => "required|string|min_digits:10|max_digits:10", 30 | "location.status" => "required|boolean", 31 | ]; 32 | public function render() 33 | { 34 | $this->locations = Location::when($this->search, function ($query) { 35 | $query->where('name', 'like', '%'.$this->search.'%'); 36 | })->paginate(10); 37 | 38 | return view('livewire.manage-locations', [ 39 | 'locations' => $this->locations, 40 | ]); 41 | } 42 | 43 | public function confirmLocationEdit(Location $location) { 44 | $this->location = $location; 45 | $this->confirmingLocationAdd= true; 46 | } 47 | public function confirmLocationDeletion() { 48 | $this->confirmingLocationDeletion = true; 49 | } 50 | 51 | public function saveLocation() { 52 | $this->validate(); 53 | 54 | if (isset($this->location->id)) { 55 | $this->location->save(); 56 | } else { 57 | Location::create( 58 | [ 59 | 'name' => $this->location['name'], 60 | 'address' => $this->location['address'], 61 | 'telephone_number' => $this->location['telephone_number'], 62 | 'status' => $this->location['status'], 63 | ] 64 | ); 65 | } 66 | 67 | $this->confirmingLocationAdd = false; 68 | $this->location = null; 69 | } 70 | 71 | public function deleteLocation(Location $locationId) { 72 | $this->location = $locationId; 73 | $this->location->delete(); 74 | $this->confirmingLocationDeletion = false; 75 | } 76 | 77 | public function confirmLocationAdd() { 78 | $this->confirmingLocationAdd = true; 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /app/Http/Livewire/ServiceTable.php: -------------------------------------------------------------------------------- 1 | setPrimaryKey('id'); 16 | } 17 | 18 | public function columns(): array 19 | { 20 | return [ 21 | Column::make("Id", "id") 22 | ->sortable(), 23 | Column::make("Name", "name") 24 | ->sortable(), 25 | Column::make("Description", "description") 26 | ->sortable(), 27 | Column::make("Image", "image") 28 | ->sortable(), 29 | Column::make("Price", "price") 30 | ->sortable(), 31 | Column::make("Is hidden", "is_hidden") 32 | ->sortable(), 33 | Column::make("Created at", "created_at") 34 | ->sortable(), 35 | Column::make("Updated at", "updated_at") 36 | ->sortable(), 37 | ]; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/Http/Middleware/Authenticate.php: -------------------------------------------------------------------------------- 1 | expectsJson() ? null : route('login'); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/Http/Middleware/EncryptCookies.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | protected $except = [ 15 | // 16 | ]; 17 | } 18 | -------------------------------------------------------------------------------- /app/Http/Middleware/PreventRequestsDuringMaintenance.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | protected $except = [ 15 | // 16 | ]; 17 | } 18 | -------------------------------------------------------------------------------- /app/Http/Middleware/RedirectIfAuthenticated.php: -------------------------------------------------------------------------------- 1 | check()) { 24 | return redirect(RouteServiceProvider::HOME); 25 | } 26 | } 27 | 28 | return $next($request); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/Http/Middleware/TrimStrings.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | protected $except = [ 15 | 'current_password', 16 | 'password', 17 | 'password_confirmation', 18 | ]; 19 | } 20 | -------------------------------------------------------------------------------- /app/Http/Middleware/TrustHosts.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | public function hosts(): array 15 | { 16 | return [ 17 | $this->allSubdomainsOfApplicationUrl(), 18 | ]; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /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/Http/Middleware/UserSuspendedRedirect.php: -------------------------------------------------------------------------------- 1 | status == false) { 22 | // dd('User is suspended'); 23 | Auth::guard('web')->logout(); 24 | 25 | 26 | return redirect('/login')->with('errormsg', 'Your account has been suspended. Please contact the administrator.'); 27 | 28 | } 29 | } 30 | return $next($request); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/Http/Middleware/ValidateRole.php: -------------------------------------------------------------------------------- 1 | user()->role()->first()->name; 21 | 22 | foreach ($roles as $role) { 23 | if ($user_role == $role) { 24 | return $next($request); 25 | } 26 | } 27 | 28 | return redirect('/dashboard')->with('errormsg', 'You do not have permission to access this page.'); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/Http/Middleware/ValidateSignature.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | protected $except = [ 15 | // 'fbclid', 16 | // 'utm_campaign', 17 | // 'utm_content', 18 | // 'utm_medium', 19 | // 'utm_source', 20 | // 'utm_term', 21 | ]; 22 | } 23 | -------------------------------------------------------------------------------- /app/Http/Middleware/VerifyCsrfToken.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | protected $except = [ 15 | // 16 | ]; 17 | } 18 | -------------------------------------------------------------------------------- /app/Jobs/AnalyticsJob.php: -------------------------------------------------------------------------------- 1 | makeHit( 27 | $this->model, 28 | $this->id, 29 | $this->analytic_type, 30 | auth()->user()?->id() 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/Jobs/SendAppointmentConfirmationMailJob.php: -------------------------------------------------------------------------------- 1 | appointment 34 | ); 35 | 36 | Notification::send($this->customer, $notification); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/Jobs/SendNewServicePromoMailJob.php: -------------------------------------------------------------------------------- 1 | service 32 | ); 33 | 34 | Notification::send($this->customer, $notification); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/Models/Appointment.php: -------------------------------------------------------------------------------- 1 | 'string', // as string cuz we get it from the time slot 29 | 'end_time' => 'string', 30 | ]; 31 | 32 | public function user() 33 | { 34 | return $this->belongsTo(User::class); 35 | } 36 | 37 | public function service() 38 | { 39 | return $this->belongsTo(Service::class); 40 | } 41 | 42 | public function timeSlot() 43 | { 44 | return $this->belongsTo(TimeSlot::class); 45 | } 46 | 47 | public function cart() 48 | { 49 | return $this->belongsTo(Cart::class); 50 | } 51 | 52 | public function location() 53 | { 54 | return $this->belongsTo(Location::class); 55 | } 56 | 57 | 58 | 59 | static function boot() 60 | { 61 | parent::boot(); 62 | 63 | static::creating(function ($appointment) { 64 | // a readable unique code for the appointment, including the id in the code 65 | $appointment->appointment_code = 'APP-'. ($appointment->count() + 1) ; 66 | 67 | }); 68 | } 69 | 70 | 71 | } 72 | -------------------------------------------------------------------------------- /app/Models/Cart.php: -------------------------------------------------------------------------------- 1 | belongsTo(User::class); 24 | } 25 | 26 | public function services() 27 | { 28 | return $this 29 | ->belongsToMany(Service::class) 30 | ->with('locations') 31 | ->withPivot('id','time_slot_id','date', 'start_time', 'end_time', 'location_id' , 'price'); 32 | 33 | } 34 | 35 | 36 | 37 | protected static function booted() 38 | { 39 | static::creating(function ($cart) { 40 | $cart->uuid = \Illuminate\Support\Str::uuid(); 41 | }); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /app/Models/Category.php: -------------------------------------------------------------------------------- 1 | hasMany(Service::class); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/Models/Deal.php: -------------------------------------------------------------------------------- 1 | hasMany(User::class); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/Models/ServiceHit.php: -------------------------------------------------------------------------------- 1 | belongsTo(Service::class); 23 | } 24 | 25 | public function user() 26 | { 27 | return $this->belongsTo(User::class); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/Models/TimeSlot.php: -------------------------------------------------------------------------------- 1 | hasMany(Appointment::class); 18 | } 19 | 20 | 21 | 22 | } 23 | -------------------------------------------------------------------------------- /app/Models/User.php: -------------------------------------------------------------------------------- 1 | 25 | */ 26 | protected $fillable = [ 27 | 'name', 28 | 'email', 29 | 'password', 30 | 'phone_number', 31 | 'role_id', 32 | 'status', 33 | 34 | ]; 35 | 36 | /** 37 | * The attributes that should be hidden for serialization. 38 | * 39 | * @var array 40 | */ 41 | protected $hidden = [ 42 | 'password', 43 | 'remember_token', 44 | 'two_factor_recovery_codes', 45 | 'two_factor_secret', 46 | ]; 47 | 48 | /** 49 | * The attributes that should be cast. 50 | * 51 | * @var array 52 | */ 53 | protected $casts = [ 54 | 'email_verified_at' => 'datetime', 55 | ]; 56 | 57 | /** 58 | * The accessors to append to the model's array form. 59 | * 60 | * @var array 61 | */ 62 | protected $appends = [ 63 | 'profile_photo_url', 64 | ]; 65 | 66 | function role() { 67 | return $this->belongsTo(Role::class); 68 | } 69 | 70 | function cart() { 71 | return $this->hasOne(Cart::class); 72 | } 73 | } 74 | 75 | -------------------------------------------------------------------------------- /app/Notifications/AppointmentConfirmationNotification.php: -------------------------------------------------------------------------------- 1 | subject( 'Appointment Confirmation - Salon Bliss 🎉' . $this->appointment->service->name) 32 | ->from('noreply@salonbliss.com') 33 | ->greeting('Hello ' . $notifiable->name . '!') 34 | ->line('Your appointment for ' . $this->appointment->service->name . ' has been confirmed!') 35 | // invoice 36 | ->line('Your payment of LKR ' . $this->appointment->total . ' has been processed.') 37 | ->line('🧾 Appointment Code: ' . $this->appointment->appointment_code) 38 | ->line('📅 Date: ' . $this->appointment->date) 39 | ->line('⏰ Time: ' . $this->appointment->start_time . ' - ' . $this->appointment->end_time) 40 | ->line('📍 Location: ' . $this->appointment->location->name) 41 | ->line('📍 Address: ' . $this->appointment->location->address) 42 | ->line('📞 Contact: ' . $this->appointment->location->telephone_number) 43 | ->action('View Your Appointment', route('dashboard').'?search='. $this->appointment->appointment_code ) 44 | ->line('Thank you for using Salon Bliss! We hope to see you again soon.'); 45 | 46 | } 47 | 48 | public function toArray($notifiable): array 49 | { 50 | return []; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/Notifications/NewServiceReleasedNotification.php: -------------------------------------------------------------------------------- 1 | subject( $this->service->name . ' now available at Salon Bliss!') 31 | ->from('info@salonbliss.com') 32 | ->greeting('Hello ' . $notifiable->name . '!') 33 | ->line('Big News! 🎉') 34 | ->line('Introducing ' . $this->service->name . ' - our latest service!') 35 | ->line('✨ Priced at LKR ' . number_format($this->service->price, 2, '.', ',') . ' ✨') 36 | ->line('💆‍♀️ The benefits: ' . $this->service->benefits) 37 | ->action('Book Now', url('/services/' . $this->service->slug)) 38 | ->line('Thank you for choosing Salon Bliss!'); 39 | } 40 | 41 | public function toArray($notifiable): array 42 | { 43 | return []; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/Providers/AnalyticsServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->singleton('AnalyticsSingleton', function () { 12 | return new \App\Singletons\AnalyticsSingleton(); 13 | }); 14 | } 15 | 16 | public function boot(): void 17 | { 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/Providers/AppServiceProvider.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | protected $policies = [ 16 | // 17 | ]; 18 | 19 | /** 20 | * Register any authentication / authorization services. 21 | */ 22 | public function boot(): void 23 | { 24 | // 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/Providers/BroadcastServiceProvider.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 | public function boot(): void 27 | { 28 | // 29 | } 30 | 31 | /** 32 | * Determine if events and listeners should be automatically discovered. 33 | */ 34 | public function shouldDiscoverEvents(): bool 35 | { 36 | return false; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/Providers/FortifyServiceProvider.php: -------------------------------------------------------------------------------- 1 | email; 37 | 38 | return Limit::perMinute(5)->by($email.$request->ip()); 39 | }); 40 | 41 | RateLimiter::for('two-factor', function (Request $request) { 42 | return Limit::perMinute(5)->by($request->session()->get('login.id')); 43 | }); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/Providers/JetstreamServiceProvider.php: -------------------------------------------------------------------------------- 1 | configurePermissions(); 25 | 26 | Jetstream::deleteUsersUsing(DeleteUser::class); 27 | } 28 | 29 | /** 30 | * Configure the permissions that are available within the application. 31 | */ 32 | protected function configurePermissions(): void 33 | { 34 | Jetstream::defaultApiTokenPermissions(['read']); 35 | 36 | Jetstream::permissions([ 37 | 'create', 38 | 'read', 39 | 'update', 40 | 'delete', 41 | ]); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/Providers/RouteServiceProvider.php: -------------------------------------------------------------------------------- 1 | by($request->user()?->id ?: $request->ip()); 31 | }); 32 | 33 | $this->routes(function () { 34 | Route::middleware('api') 35 | ->prefix('api') 36 | ->group(base_path('routes/api.php')); 37 | 38 | Route::middleware('web') 39 | ->group(base_path('routes/web.php')); 40 | }); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/Providers/TelescopeServiceProvider.php: -------------------------------------------------------------------------------- 1 | hideSensitiveRequestDetails(); 20 | 21 | Telescope::filter(function (IncomingEntry $entry) { 22 | if ($this->app->environment('local')) { 23 | return true; 24 | } 25 | 26 | return $entry->isReportableException() || 27 | $entry->isFailedRequest() || 28 | $entry->isFailedJob() || 29 | $entry->isScheduledTask() || 30 | $entry->hasMonitoredTag(); 31 | }); 32 | } 33 | 34 | /** 35 | * Prevent sensitive request details from being logged by Telescope. 36 | */ 37 | protected function hideSensitiveRequestDetails(): void 38 | { 39 | if ($this->app->environment('local')) { 40 | return; 41 | } 42 | 43 | Telescope::hideRequestParameters(['_token']); 44 | 45 | Telescope::hideRequestHeaders([ 46 | 'cookie', 47 | 'x-csrf-token', 48 | 'x-xsrf-token', 49 | ]); 50 | } 51 | 52 | /** 53 | * Register the Telescope gate. 54 | * 55 | * This gate determines who can access Telescope in non-local environments. 56 | */ 57 | protected function gate(): void 58 | { 59 | Gate::define('viewTelescope', function ($user) { 60 | return in_array($user->email, [ 61 | // 62 | ]); 63 | }); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/Singletons/AnalyticsSingleton.php: -------------------------------------------------------------------------------- 1 | makeServiceHit($modelId, $userId); 22 | } 23 | 24 | } 25 | 26 | private function makeServiceHit(int $serviceId, int $userId = null): void 27 | { 28 | // dd('make service hit', $serviceId, $userId); 29 | $serviceHit = new \App\Models\ServiceHit(); 30 | $serviceHit->service_id = $serviceId; 31 | $serviceHit->hit_time = Carbon::now(); 32 | $serviceHit->analytic_data_type = 'view'; 33 | $serviceHit->user_id = $userId; 34 | $serviceHit->save(); 35 | 36 | } 37 | 38 | 39 | 40 | } 41 | -------------------------------------------------------------------------------- /app/View/Components/AppLayout.php: -------------------------------------------------------------------------------- 1 | daySchedule = $this->getDaySchedule(); 31 | $this->timeSlots = $this->getTimeSlots(); 32 | $this->location = Location::where('id', $this->locationId)->first(); 33 | } 34 | 35 | public function render(): View 36 | { 37 | 38 | return view('components.day-schedule'); 39 | } 40 | 41 | private function getDaySchedule() 42 | { 43 | 44 | return ( 45 | Appointment::orderBy('start_time', 'asc') 46 | ->where('date', $this->date->toDateString()) 47 | ->where('status', '!=', 0) 48 | ->orderBy('time_slot_id', 'asc') 49 | ->where('status', '!=', 0) 50 | ->where('location_id', $this->locationId) 51 | ->with('service', 'timeSlot', 'user') 52 | ->get()); 53 | } 54 | 55 | private function getTimeSlots() 56 | { 57 | return TimeSlot::all(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/View/Components/GuestLayout.php: -------------------------------------------------------------------------------- 1 | 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /bootstrap/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /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.1", 9 | "guzzlehttp/guzzle": "^7.2", 10 | "laravel/framework": "^10.8", 11 | "laravel/jetstream": "^3.1", 12 | "laravel/sanctum": "^3.2", 13 | "laravel/telescope": "^4.16", 14 | "laravel/tinker": "^2.8", 15 | "livewire/livewire": "^2.11", 16 | "rappasoft/laravel-livewire-tables": "^2.14" 17 | }, 18 | "require-dev": { 19 | "barryvdh/laravel-debugbar": "^3.9", 20 | "fakerphp/faker": "^1.9.1", 21 | "laravel/pint": "^1.0", 22 | "laravel/sail": "^1.18", 23 | "mockery/mockery": "^1.4.4", 24 | "nunomaduro/collision": "^7.0", 25 | "phpunit/phpunit": "^10.1", 26 | "spatie/laravel-ignition": "^2.0" 27 | }, 28 | "autoload": { 29 | "psr-4": { 30 | "App\\": "app/", 31 | "Database\\Factories\\": "database/factories/", 32 | "Database\\Seeders\\": "database/seeders/" 33 | } 34 | }, 35 | "autoload-dev": { 36 | "psr-4": { 37 | "Tests\\": "tests/" 38 | } 39 | }, 40 | "scripts": { 41 | "post-autoload-dump": [ 42 | "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", 43 | "@php artisan package:discover --ansi" 44 | ], 45 | "post-update-cmd": [ 46 | "@php artisan vendor:publish --tag=laravel-assets --ansi --force" 47 | ], 48 | "post-root-package-install": [ 49 | "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" 50 | ], 51 | "post-create-project-cmd": [ 52 | "@php artisan key:generate --ansi" 53 | ] 54 | }, 55 | "extra": { 56 | "laravel": { 57 | "dont-discover": [] 58 | } 59 | }, 60 | "config": { 61 | "optimize-autoloader": true, 62 | "preferred-install": "dist", 63 | "sort-packages": true, 64 | "allow-plugins": { 65 | "pestphp/pest-plugin": true, 66 | "php-http/discovery": true 67 | } 68 | }, 69 | "minimum-stability": "stable", 70 | "prefer-stable": true 71 | } 72 | -------------------------------------------------------------------------------- /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 | 'host' => env('PUSHER_HOST') ?: 'api-'.env('PUSHER_APP_CLUSTER', 'mt1').'.pusher.com', 40 | 'port' => env('PUSHER_PORT', 443), 41 | 'scheme' => env('PUSHER_SCHEME', 'https'), 42 | 'encrypted' => true, 43 | 'useTLS' => env('PUSHER_SCHEME', 'https') === 'https', 44 | ], 45 | 'client_options' => [ 46 | // Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html 47 | ], 48 | ], 49 | 50 | 'ably' => [ 51 | 'driver' => 'ably', 52 | 'key' => env('ABLY_KEY'), 53 | ], 54 | 55 | 'redis' => [ 56 | 'driver' => 'redis', 57 | 'connection' => 'default', 58 | ], 59 | 60 | 'log' => [ 61 | 'driver' => 'log', 62 | ], 63 | 64 | 'null' => [ 65 | 'driver' => 'null', 66 | ], 67 | 68 | ], 69 | 70 | ]; 71 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 set up for each driver as an example of the required values. 26 | | 27 | | Supported Drivers: "local", "ftp", "sftp", "s3" 28 | | 29 | */ 30 | 31 | 'disks' => [ 32 | 33 | 'local' => [ 34 | 'driver' => 'local', 35 | 'root' => storage_path('app'), 36 | 'throw' => false, 37 | ], 38 | 39 | 'public' => [ 40 | 'driver' => 'local', 41 | 'root' => storage_path('app/public'), 42 | 'url' => env('APP_URL').'/storage', 43 | 'visibility' => 'public', 44 | 'throw' => false, 45 | ], 46 | 47 | 's3' => [ 48 | 'driver' => 's3', 49 | 'key' => env('AWS_ACCESS_KEY_ID'), 50 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 51 | 'region' => env('AWS_DEFAULT_REGION'), 52 | 'bucket' => env('AWS_BUCKET'), 53 | 'url' => env('AWS_URL'), 54 | 'endpoint' => env('AWS_ENDPOINT'), 55 | 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false), 56 | 'throw' => false, 57 | ], 58 | 59 | ], 60 | 61 | /* 62 | |-------------------------------------------------------------------------- 63 | | Symbolic Links 64 | |-------------------------------------------------------------------------- 65 | | 66 | | Here you may configure the symbolic links that will be created when the 67 | | `storage:link` Artisan command is executed. The array keys should be 68 | | the locations of the links and the values should be their targets. 69 | | 70 | */ 71 | 72 | 'links' => [ 73 | public_path('storage') => storage_path('app/public'), 74 | ], 75 | 76 | ]; 77 | -------------------------------------------------------------------------------- /config/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 | -------------------------------------------------------------------------------- /config/sanctum.php: -------------------------------------------------------------------------------- 1 | explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf( 19 | '%s%s', 20 | 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1', 21 | Sanctum::currentApplicationUrlWithPort() 22 | ))), 23 | 24 | /* 25 | |-------------------------------------------------------------------------- 26 | | Sanctum Guards 27 | |-------------------------------------------------------------------------- 28 | | 29 | | This array contains the authentication guards that will be checked when 30 | | Sanctum is trying to authenticate a request. If none of these guards 31 | | are able to authenticate the request, Sanctum will use the bearer 32 | | token that's present on an incoming request for authentication. 33 | | 34 | */ 35 | 36 | 'guard' => ['web'], 37 | 38 | /* 39 | |-------------------------------------------------------------------------- 40 | | Expiration Minutes 41 | |-------------------------------------------------------------------------- 42 | | 43 | | This value controls the number of minutes until an issued token will be 44 | | considered expired. This will override any values set in the token's 45 | | "expires_at" attribute, but first-party sessions are not affected. 46 | | 47 | */ 48 | 49 | 'expiration' => null, 50 | 51 | /* 52 | |-------------------------------------------------------------------------- 53 | | Sanctum Middleware 54 | |-------------------------------------------------------------------------- 55 | | 56 | | When authenticating your first-party SPA with Sanctum you may need to 57 | | customize some of the middleware Sanctum uses while processing the 58 | | request. You may change the middleware listed below as required. 59 | | 60 | */ 61 | 62 | 'middleware' => [ 63 | 'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class, 64 | 'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class, 65 | ], 66 | 67 | ]; 68 | -------------------------------------------------------------------------------- /config/services.php: -------------------------------------------------------------------------------- 1 | [ 18 | 'domain' => env('MAILGUN_DOMAIN'), 19 | 'secret' => env('MAILGUN_SECRET'), 20 | 'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'), 21 | 'scheme' => 'https', 22 | ], 23 | 24 | 'postmark' => [ 25 | 'token' => env('POSTMARK_TOKEN'), 26 | ], 27 | 28 | 'ses' => [ 29 | 'key' => env('AWS_ACCESS_KEY_ID'), 30 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 31 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 32 | ], 33 | 34 | ]; 35 | -------------------------------------------------------------------------------- /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/.gitignore: -------------------------------------------------------------------------------- 1 | #*.sqlite* 2 | 3 | 4 | -------------------------------------------------------------------------------- /database/database.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sachintha-lk/CRM-laravel/dae81639d5e9697f62eaa4990e7a24c9c0e19291/database/database.sqlite -------------------------------------------------------------------------------- /database/factories/DealFactory.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class DealFactory extends Factory 11 | { 12 | /** 13 | * Define the model's default state. 14 | * 15 | * @return array 16 | */ 17 | public function definition(): array 18 | { 19 | return [ 20 | 21 | 'name' => $this->faker->name(), 22 | 'description' => $this->faker->text(), 23 | 'discount' => $this->faker->randomFloat(2, 0, 100), 24 | 'start_date' => $this->faker->date(), 25 | 'end_date' => $this->faker->date(), 26 | 27 | ]; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /database/factories/UserFactory.php: -------------------------------------------------------------------------------- 1 | 24 | */ 25 | public function definition(): array 26 | { 27 | return [ 28 | 'name' => $this->faker->name(), 29 | 'email' => $this->faker->unique()->safeEmail(), 30 | 'email_verified_at' => now(), 31 | 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 32 | 'two_factor_secret' => null, 33 | 'two_factor_recovery_codes' => null, 34 | 'remember_token' => Str::random(10), 35 | 'profile_photo_path' => null, 36 | 'current_team_id' => null, 37 | ]; 38 | } 39 | 40 | /** 41 | * Indicate that the model's email address should be unverified. 42 | */ 43 | public function unverified(): static 44 | { 45 | return $this->state(function (array $attributes) { 46 | return [ 47 | 'email_verified_at' => null, 48 | ]; 49 | }); 50 | } 51 | 52 | /** 53 | * Indicate that the user should have a personal team. 54 | */ 55 | public function withPersonalTeam(callable $callback = null): static 56 | { 57 | if (! Features::hasTeamFeatures()) { 58 | return $this->state([]); 59 | } 60 | 61 | return $this->has( 62 | Team::factory() 63 | ->state(fn (array $attributes, User $user) => [ 64 | 'name' => $user->name.'\'s Team', 65 | 'user_id' => $user->id, 66 | 'personal_team' => true, 67 | ]) 68 | ->when(is_callable($callback), $callback), 69 | 'ownedTeams' 70 | ); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /database/migrations/2014_10_12_000000_create_users_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->string('name'); 17 | $table->string('email')->unique(); 18 | $table->timestamp('email_verified_at')->nullable(); 19 | $table->string('password'); 20 | $table->string('phone_number', 10)->unique()->nullable(); 21 | $table->rememberToken(); 22 | $table->foreignId('current_team_id')->nullable(); 23 | $table->string('profile_photo_path', 2048)->nullable(); 24 | $table->boolean('status')->default(true); 25 | $table->timestamps(); 26 | }); 27 | } 28 | 29 | /** 30 | * Reverse the migrations. 31 | */ 32 | public function down(): void 33 | { 34 | Schema::dropIfExists('users'); 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /database/migrations/2014_10_12_100000_create_password_reset_tokens_table.php: -------------------------------------------------------------------------------- 1 | string('email')->primary(); 16 | $table->string('token'); 17 | $table->timestamp('created_at')->nullable(); 18 | }); 19 | } 20 | 21 | /** 22 | * Reverse the migrations. 23 | */ 24 | public function down(): void 25 | { 26 | Schema::dropIfExists('password_reset_tokens'); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2014_10_12_200000_add_two_factor_columns_to_users_table.php: -------------------------------------------------------------------------------- 1 | text('two_factor_secret') 17 | ->after('password') 18 | ->nullable(); 19 | 20 | $table->text('two_factor_recovery_codes') 21 | ->after('two_factor_secret') 22 | ->nullable(); 23 | 24 | if (Fortify::confirmsTwoFactorAuthentication()) { 25 | $table->timestamp('two_factor_confirmed_at') 26 | ->after('two_factor_recovery_codes') 27 | ->nullable(); 28 | } 29 | }); 30 | } 31 | 32 | /** 33 | * Reverse the migrations. 34 | */ 35 | public function down(): void 36 | { 37 | Schema::table('users', function (Blueprint $table) { 38 | $table->dropColumn(array_merge([ 39 | 'two_factor_secret', 40 | 'two_factor_recovery_codes', 41 | ], Fortify::confirmsTwoFactorAuthentication() ? [ 42 | 'two_factor_confirmed_at', 43 | ] : [])); 44 | }); 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /database/migrations/2019_08_19_000000_create_failed_jobs_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->string('uuid')->unique(); 17 | $table->text('connection'); 18 | $table->text('queue'); 19 | $table->longText('payload'); 20 | $table->longText('exception'); 21 | $table->timestamp('failed_at')->useCurrent(); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | */ 28 | public function down(): void 29 | { 30 | Schema::dropIfExists('failed_jobs'); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->morphs('tokenable'); 17 | $table->string('name'); 18 | $table->string('token', 64)->unique(); 19 | $table->text('abilities')->nullable(); 20 | $table->timestamp('last_used_at')->nullable(); 21 | $table->timestamp('expires_at')->nullable(); 22 | $table->timestamps(); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | */ 29 | public function down(): void 30 | { 31 | Schema::dropIfExists('personal_access_tokens'); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /database/migrations/2023_05_08_135614_create_sessions_table.php: -------------------------------------------------------------------------------- 1 | string('id')->primary(); 16 | $table->foreignId('user_id')->nullable()->index(); 17 | $table->string('ip_address', 45)->nullable(); 18 | $table->text('user_agent')->nullable(); 19 | $table->longText('payload'); 20 | $table->integer('last_activity')->index(); 21 | }); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | */ 27 | public function down(): void 28 | { 29 | Schema::dropIfExists('sessions'); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /database/migrations/2023_05_13_144600_create_roles_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->string('name'); 17 | $table->boolean('status')->default(true); 18 | $table->timestamps(); 19 | }); 20 | } 21 | 22 | /** 23 | * Reverse the migrations. 24 | */ 25 | public function down(): void 26 | { 27 | Schema::dropIfExists('roles'); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /database/migrations/2023_05_13_155544_add_role_id_to_users_table.php: -------------------------------------------------------------------------------- 1 | foreignId('role_id')->default(UserRolesEnum::Customer->value)->constrained('roles'); 18 | }); 19 | } 20 | 21 | /** 22 | * Reverse the migrations. 23 | */ 24 | public function down(): void 25 | { 26 | Schema::table('users', function (Blueprint $table) { 27 | $table -> dropColumn('role_id'); 28 | }); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /database/migrations/2023_06_01_114923_create_locations_table.php: -------------------------------------------------------------------------------- 1 | id(); 12 | $table->string('name'); 13 | $table->string('address'); 14 | $table->string('telephone_number'); 15 | $table->boolean('status'); 16 | $table->timestamps(); 17 | }); 18 | } 19 | 20 | public function down(): void 21 | { 22 | Schema::dropIfExists('locations'); 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /database/migrations/2023_06_03_121845_create_services_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->string('name'); 17 | $table->string('slug')->unique(); 18 | $table->string('description'); 19 | $table->string('image')->nullable(); 20 | $table->decimal('price', 8, 2); 21 | $table->string('notes')->nullable(); 22 | $table->string('allergens')->nullable(); 23 | $table->string('benefits')->nullable(); 24 | $table->string('aftercare_tips')->nullable(); 25 | $table->string('cautions')->nullable(); 26 | // $table->integer('duration_minutes')->default(15)->nullable(); 27 | $table->foreignId('category_id')->nullable()->index(); 28 | $table->boolean('is_hidden')->default(false); 29 | $table->timestamps(); 30 | }); 31 | } 32 | 33 | /** 34 | * Reverse the migrations. 35 | */ 36 | public function down(): void 37 | { 38 | Schema::dropIfExists('services'); 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /database/migrations/2023_06_07_091657_create_deals_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->string('name'); 17 | $table->string('description'); 18 | $table->double('discount'); 19 | $table->date('start_date'); 20 | $table->date('end_date'); 21 | $table->boolean('is_hidden')->default(false); 22 | $table->timestamps(); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | */ 29 | public function down(): void 30 | { 31 | Schema::dropIfExists('deals'); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /database/migrations/2023_09_18_134208_create_categories_table.php: -------------------------------------------------------------------------------- 1 | id(); 12 | $table->string('name'); 13 | $table->timestamps(); 14 | }); 15 | } 16 | 17 | public function down(): void 18 | { 19 | Schema::dropIfExists('categories'); 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /database/migrations/2023_09_26_040647_create_jobs_table.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 16 | $table->string('queue')->index(); 17 | $table->longText('payload'); 18 | $table->unsignedTinyInteger('attempts'); 19 | $table->unsignedInteger('reserved_at')->nullable(); 20 | $table->unsignedInteger('available_at'); 21 | $table->unsignedInteger('created_at'); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | */ 28 | public function down(): void 29 | { 30 | Schema::dropIfExists('jobs'); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /database/migrations/2023_09_26_085642_create_service_hits_table.php: -------------------------------------------------------------------------------- 1 | id(); 12 | $table->unsignedBigInteger('service_id'); 13 | $table->timestamp('hit_time')->default(now()); 14 | $table->string('analytic_data_type'); 15 | $table->string('user_id')->nullable(); 16 | $table->timestamps(); 17 | }); 18 | } 19 | 20 | public function down(): void 21 | { 22 | Schema::dropIfExists('service_hits'); 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /database/migrations/2023_09_26_185410_create_time_slots_table.php: -------------------------------------------------------------------------------- 1 | id(); 12 | $table->time('start_time'); 13 | $table->time('end_time'); 14 | $table->timestamps(); 15 | }); 16 | } 17 | 18 | public function down(): void 19 | { 20 | Schema::dropIfExists('time_slots'); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /database/migrations/2023_09_27_021712_create_carts_table.php: -------------------------------------------------------------------------------- 1 | id(); 12 | $table->uuid('uuid')->unique(); 13 | $table->foreignId('user_id')->constrained(); 14 | $table->boolean('is_paid')->default(false); 15 | $table->boolean('is_cancelled')->default(false); // is abandoned 16 | $table->boolean('is_abandoned')->default(false); 17 | $table->double('total', 10, 2)->default(0); 18 | $table->timestamps(); 19 | }); 20 | 21 | // pivot table 22 | Schema::create('cart_service', function (Blueprint $table) { 23 | $table->id(); 24 | $table->foreignId('cart_id')->constrained(); 25 | $table->foreignId('service_id')->constrained(); 26 | $table->foreignId('time_slot_id')->constrained(); 27 | $table->date('date'); 28 | $table->time('start_time'); 29 | $table->time('end_time'); 30 | $table->foreignId('location_id')->constrained(); 31 | $table->double('price', 10, 2)->default(0); 32 | $table->timestamps(); 33 | }); 34 | 35 | } 36 | 37 | public function down(): void 38 | { 39 | Schema::dropIfExists('carts'); 40 | Schema::dropIfExists('cart_services'); 41 | 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /database/migrations/2023_09_27_023637_create_appointments_table.php: -------------------------------------------------------------------------------- 1 | id(); 13 | $table->string('appointment_code')->unique(); 14 | $table->foreignId('cart_id')->constrained(); 15 | $table->foreignId('user_id')->constrained(); 16 | $table->foreignId('service_id')->constrained(); 17 | $table->date('date'); 18 | $table->foreignId('time_slot_id')->constrained(); 19 | $table->time('start_time'); 20 | $table->time('end_time'); 21 | $table->foreignId('location_id')->constrained(); 22 | $table->double('total', 10, 2)->default(0); 23 | $table->boolean('status')->default(true); 24 | $table->timestamps(); 25 | }); 26 | } 27 | 28 | public function down(): void 29 | { 30 | Schema::dropIfExists('appointments'); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /database/seeders/LocationSeeder.php: -------------------------------------------------------------------------------- 1 | 'Location 1', 14 | 'address' => 'Address 1', 15 | 'telephone_number' => '1234567890', 16 | 'status' => true, 17 | ]); 18 | 19 | Location::create([ 20 | 'name' => 'Location 2', 21 | 'address' => 'Address 2', 22 | 'telephone_number' => '1234567890', 23 | 'status' => true, 24 | ]); 25 | 26 | Location::create([ 27 | 'name' => 'Location 3', 28 | 'address' => 'Address 3', 29 | 'telephone_number' => '1234567890', 30 | 'status' => true, 31 | ]); 32 | 33 | Location::create([ 34 | 'name' => 'Location 4', 35 | 'address' => 'Address 4', 36 | 'telephone_number' => '1234567890', 37 | 'status' => true, 38 | ]); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /database/seeders/TimeSlotSeeder.php: -------------------------------------------------------------------------------- 1 | $startDateTime->format('H:i:s'), 28 | 'end_time' => $startDateTime->add($interval)->format('H:i:s'), 29 | ]); 30 | } 31 | 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | services: 3 | main: 4 | container_name: Serve 5 | build: 6 | context: . 7 | dockerfile: Dockerfile 8 | command: 'php artisan serve --host=0.0.0.0' 9 | 10 | # command: > 11 | # sh -c "php artisan migrate && php artisan db:seed && php artisan key:generate && php artisan serve --host=0.0.0.0" 12 | volumes: 13 | - .:/app 14 | ports: 15 | - 8000:8000 16 | depends_on: 17 | - db 18 | # - queue 19 | 20 | queue: 21 | container_name: Queue 22 | build: 23 | context: . 24 | dockerfile: Dockerfile 25 | command: 'php artisan queue:listen' 26 | depends_on: 27 | - db 28 | 29 | db: 30 | platform: linux/x86_64 31 | image: mysql:8.0 32 | container_name: Database 33 | environment: 34 | MYSQL_DATABASE: "${DB_DATABASE}" 35 | # MYSQL_USER: "${DB_USERNAME}" 36 | MYSQL_ROOT: "root" 37 | MYSQL_PASSWORD: "${DB_PASSWORD}" 38 | MYSQL_ROOT_PASSWORD: "${DB_PASSWORD}" 39 | 40 | volumes: 41 | - db:/var/lib/mysql 42 | ports: 43 | - 3306:3306 44 | 45 | phpmyadmin: 46 | image: phpmyadmin/phpmyadmin 47 | container_name: PhpMyAdmin 48 | links: 49 | - db 50 | environment: 51 | PMA_HOST: db 52 | PMA_PORT: 3306 53 | PMA_ARBITRARY: 1 54 | restart: always 55 | ports: 56 | - 8081:80 57 | 58 | mailpit: 59 | image: axllent/mailpit 60 | container_name: MailPit 61 | ports: 62 | - "1025:1025" 63 | - "8025:8025" 64 | networks: 65 | - laravel-exa 66 | 67 | networks: 68 | laravel-exa: 69 | driver: bridge 70 | 71 | volumes: 72 | db: 73 | driver: local 74 | -------------------------------------------------------------------------------- /init_codespace.sh: -------------------------------------------------------------------------------- 1 | cp .env.example .env 2 | 3 | composer install 4 | 5 | php artisan key:generate 6 | php artisan migrate:fresh --seed 7 | 8 | npm install 9 | npm run dev -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev": "vite", 5 | "build": "vite build" 6 | }, 7 | "devDependencies": { 8 | "@alpinejs/focus": "^3.10.5", 9 | "@tailwindcss/aspect-ratio": "^0.4.2", 10 | "@tailwindcss/forms": "^0.5.2", 11 | "@tailwindcss/typography": "^0.5.0", 12 | "alpinejs": "^3.0.6", 13 | "autoprefixer": "^10.4.7", 14 | "axios": "^1.6.0", 15 | "laravel-vite-plugin": "^0.7.2", 16 | "postcss": "^8.4.31", 17 | "tailwindcss": "^3.1.0", 18 | "vite": "^4.0.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | 3 | Options -MultiViews -Indexes 4 | 5 | 6 | RewriteEngine On 7 | 8 | # Handle Authorization Header 9 | RewriteCond %{HTTP:Authorization} . 10 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 11 | 12 | # Redirect Trailing Slashes If Not A Folder... 13 | RewriteCond %{REQUEST_FILENAME} !-d 14 | RewriteCond %{REQUEST_URI} (.+)/$ 15 | RewriteRule ^ %1 [L,R=301] 16 | 17 | # Send Requests To Front Controller... 18 | RewriteCond %{REQUEST_FILENAME} !-d 19 | RewriteCond %{REQUEST_FILENAME} !-f 20 | RewriteRule ^ index.php [L] 21 | 22 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sachintha-lk/CRM-laravel/dae81639d5e9697f62eaa4990e7a24c9c0e19291/public/favicon.ico -------------------------------------------------------------------------------- /public/images/Gallery/gallery1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sachintha-lk/CRM-laravel/dae81639d5e9697f62eaa4990e7a24c9c0e19291/public/images/Gallery/gallery1.jpg -------------------------------------------------------------------------------- /public/images/Gallery/gallery2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sachintha-lk/CRM-laravel/dae81639d5e9697f62eaa4990e7a24c9c0e19291/public/images/Gallery/gallery2.jpg -------------------------------------------------------------------------------- /public/images/Gallery/gallery3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sachintha-lk/CRM-laravel/dae81639d5e9697f62eaa4990e7a24c9c0e19291/public/images/Gallery/gallery3.jpg -------------------------------------------------------------------------------- /public/images/Gallery/gallery4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sachintha-lk/CRM-laravel/dae81639d5e9697f62eaa4990e7a24c9c0e19291/public/images/Gallery/gallery4.jpg -------------------------------------------------------------------------------- /public/images/Gallery/gallery5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sachintha-lk/CRM-laravel/dae81639d5e9697f62eaa4990e7a24c9c0e19291/public/images/Gallery/gallery5.jpg -------------------------------------------------------------------------------- /public/images/Gallery/gallery6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sachintha-lk/CRM-laravel/dae81639d5e9697f62eaa4990e7a24c9c0e19291/public/images/Gallery/gallery6.jpg -------------------------------------------------------------------------------- /public/images/Salon2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sachintha-lk/CRM-laravel/dae81639d5e9697f62eaa4990e7a24c9c0e19291/public/images/Salon2.jpg -------------------------------------------------------------------------------- /public/images/banner-salon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sachintha-lk/CRM-laravel/dae81639d5e9697f62eaa4990e7a24c9c0e19291/public/images/banner-salon.png -------------------------------------------------------------------------------- /public/images/hair-coloring.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sachintha-lk/CRM-laravel/dae81639d5e9697f62eaa4990e7a24c9c0e19291/public/images/hair-coloring.jpg -------------------------------------------------------------------------------- /public/images/hair-cut.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sachintha-lk/CRM-laravel/dae81639d5e9697f62eaa4990e7a24c9c0e19291/public/images/hair-cut.jpg -------------------------------------------------------------------------------- /public/images/hair.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sachintha-lk/CRM-laravel/dae81639d5e9697f62eaa4990e7a24c9c0e19291/public/images/hair.jpg -------------------------------------------------------------------------------- /public/images/logo-pink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sachintha-lk/CRM-laravel/dae81639d5e9697f62eaa4990e7a24c9c0e19291/public/images/logo-pink.png -------------------------------------------------------------------------------- /public/images/logo-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sachintha-lk/CRM-laravel/dae81639d5e9697f62eaa4990e7a24c9c0e19291/public/images/logo-white.png -------------------------------------------------------------------------------- /public/images/makeup.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sachintha-lk/CRM-laravel/dae81639d5e9697f62eaa4990e7a24c9c0e19291/public/images/makeup.jpg -------------------------------------------------------------------------------- /public/images/nail-coloring.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sachintha-lk/CRM-laravel/dae81639d5e9697f62eaa4990e7a24c9c0e19291/public/images/nail-coloring.jpg -------------------------------------------------------------------------------- /public/images/nails.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sachintha-lk/CRM-laravel/dae81639d5e9697f62eaa4990e7a24c9c0e19291/public/images/nails.jpg -------------------------------------------------------------------------------- /public/images/salon1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sachintha-lk/CRM-laravel/dae81639d5e9697f62eaa4990e7a24c9c0e19291/public/images/salon1.png -------------------------------------------------------------------------------- /public/images/skin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sachintha-lk/CRM-laravel/dae81639d5e9697f62eaa4990e7a24c9c0e19291/public/images/skin.jpg -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /public/vendor/telescope/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sachintha-lk/CRM-laravel/dae81639d5e9697f62eaa4990e7a24c9c0e19291/public/vendor/telescope/favicon.ico -------------------------------------------------------------------------------- /public/vendor/telescope/mix-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "/app.js": "/app.js?id=140ed4bc5b10bc99492b97668c59272d", 3 | "/app-dark.css": "/app-dark.css?id=b11fa9a28e9d3aeb8c92986f319b3c44", 4 | "/app.css": "/app.css?id=b3ccfbe68f24cff776f83faa8dead721" 5 | } 6 | -------------------------------------------------------------------------------- /readme-assets/screenshots/admin-dash-analytics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sachintha-lk/CRM-laravel/dae81639d5e9697f62eaa4990e7a24c9c0e19291/readme-assets/screenshots/admin-dash-analytics.png -------------------------------------------------------------------------------- /readme-assets/screenshots/admin-location-day-schedule.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sachintha-lk/CRM-laravel/dae81639d5e9697f62eaa4990e7a24c9c0e19291/readme-assets/screenshots/admin-location-day-schedule.png -------------------------------------------------------------------------------- /readme-assets/screenshots/analytics_recording.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sachintha-lk/CRM-laravel/dae81639d5e9697f62eaa4990e7a24c9c0e19291/readme-assets/screenshots/analytics_recording.png -------------------------------------------------------------------------------- /readme-assets/screenshots/appointment_confirm_email_queue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sachintha-lk/CRM-laravel/dae81639d5e9697f62eaa4990e7a24c9c0e19291/readme-assets/screenshots/appointment_confirm_email_queue.png -------------------------------------------------------------------------------- /readme-assets/screenshots/cart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sachintha-lk/CRM-laravel/dae81639d5e9697f62eaa4990e7a24c9c0e19291/readme-assets/screenshots/cart.png -------------------------------------------------------------------------------- /readme-assets/screenshots/customer-analytics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sachintha-lk/CRM-laravel/dae81639d5e9697f62eaa4990e7a24c9c0e19291/readme-assets/screenshots/customer-analytics.png -------------------------------------------------------------------------------- /readme-assets/screenshots/customer-details-analytics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sachintha-lk/CRM-laravel/dae81639d5e9697f62eaa4990e7a24c9c0e19291/readme-assets/screenshots/customer-details-analytics.png -------------------------------------------------------------------------------- /readme-assets/screenshots/dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sachintha-lk/CRM-laravel/dae81639d5e9697f62eaa4990e7a24c9c0e19291/readme-assets/screenshots/dashboard.png -------------------------------------------------------------------------------- /readme-assets/screenshots/homepage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sachintha-lk/CRM-laravel/dae81639d5e9697f62eaa4990e7a24c9c0e19291/readme-assets/screenshots/homepage.png -------------------------------------------------------------------------------- /readme-assets/screenshots/locations-manage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sachintha-lk/CRM-laravel/dae81639d5e9697f62eaa4990e7a24c9c0e19291/readme-assets/screenshots/locations-manage.png -------------------------------------------------------------------------------- /readme-assets/screenshots/logo-readme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sachintha-lk/CRM-laravel/dae81639d5e9697f62eaa4990e7a24c9c0e19291/readme-assets/screenshots/logo-readme.png -------------------------------------------------------------------------------- /readme-assets/screenshots/manage-appointment-admin-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sachintha-lk/CRM-laravel/dae81639d5e9697f62eaa4990e7a24c9c0e19291/readme-assets/screenshots/manage-appointment-admin-view.png -------------------------------------------------------------------------------- /readme-assets/screenshots/manage-deals.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sachintha-lk/CRM-laravel/dae81639d5e9697f62eaa4990e7a24c9c0e19291/readme-assets/screenshots/manage-deals.png -------------------------------------------------------------------------------- /readme-assets/screenshots/manage-services.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sachintha-lk/CRM-laravel/dae81639d5e9697f62eaa4990e7a24c9c0e19291/readme-assets/screenshots/manage-services.png -------------------------------------------------------------------------------- /readme-assets/screenshots/manage-users.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sachintha-lk/CRM-laravel/dae81639d5e9697f62eaa4990e7a24c9c0e19291/readme-assets/screenshots/manage-users.png -------------------------------------------------------------------------------- /readme-assets/screenshots/new-service-email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sachintha-lk/CRM-laravel/dae81639d5e9697f62eaa4990e7a24c9c0e19291/readme-assets/screenshots/new-service-email.png -------------------------------------------------------------------------------- /readme-assets/screenshots/new_service_promo_queue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sachintha-lk/CRM-laravel/dae81639d5e9697f62eaa4990e7a24c9c0e19291/readme-assets/screenshots/new_service_promo_queue.png -------------------------------------------------------------------------------- /readme-assets/screenshots/queued_mail_appointment_confirm_email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sachintha-lk/CRM-laravel/dae81639d5e9697f62eaa4990e7a24c9c0e19291/readme-assets/screenshots/queued_mail_appointment_confirm_email.png -------------------------------------------------------------------------------- /readme-assets/screenshots/service-analytics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sachintha-lk/CRM-laravel/dae81639d5e9697f62eaa4990e7a24c9c0e19291/readme-assets/screenshots/service-analytics.png -------------------------------------------------------------------------------- /readme-assets/screenshots/services-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sachintha-lk/CRM-laravel/dae81639d5e9697f62eaa4990e7a24c9c0e19291/readme-assets/screenshots/services-page.png -------------------------------------------------------------------------------- /readme-assets/screenshots/view-a-service.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sachintha-lk/CRM-laravel/dae81639d5e9697f62eaa4990e7a24c9c0e19291/readme-assets/screenshots/view-a-service.png -------------------------------------------------------------------------------- /readme-assets/screenshots/view-appointment-customer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sachintha-lk/CRM-laravel/dae81639d5e9697f62eaa4990e7a24c9c0e19291/readme-assets/screenshots/view-appointment-customer.png -------------------------------------------------------------------------------- /resources/css/app.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | [x-cloak] { 6 | display: none; 7 | } 8 | -------------------------------------------------------------------------------- /resources/js/app.js: -------------------------------------------------------------------------------- 1 | import './bootstrap'; 2 | 3 | import Alpine from 'alpinejs'; 4 | import focus from '@alpinejs/focus'; 5 | window.Alpine = Alpine; 6 | 7 | Alpine.plugin(focus); 8 | 9 | Alpine.start(); 10 | -------------------------------------------------------------------------------- /resources/js/bootstrap.js: -------------------------------------------------------------------------------- 1 | /** 2 | * We'll load the axios HTTP library which allows us to easily issue requests 3 | * to our Laravel back-end. This library automatically handles sending the 4 | * CSRF token as a header based on the value of the "XSRF" token cookie. 5 | */ 6 | 7 | import axios from 'axios'; 8 | window.axios = axios; 9 | 10 | window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; 11 | 12 | /** 13 | * Echo exposes an expressive API for subscribing to channels and listening 14 | * for events that are broadcast by Laravel. Echo and event broadcasting 15 | * allows your team to easily build robust real-time web applications. 16 | */ 17 | 18 | // import Echo from 'laravel-echo'; 19 | 20 | // import Pusher from 'pusher-js'; 21 | // window.Pusher = Pusher; 22 | 23 | // window.Echo = new Echo({ 24 | // broadcaster: 'pusher', 25 | // key: import.meta.env.VITE_PUSHER_APP_KEY, 26 | // cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER ?? 'mt1', 27 | // wsHost: import.meta.env.VITE_PUSHER_HOST ? import.meta.env.VITE_PUSHER_HOST : `ws-${import.meta.env.VITE_PUSHER_APP_CLUSTER}.pusher.com`, 28 | // wsPort: import.meta.env.VITE_PUSHER_PORT ?? 80, 29 | // wssPort: import.meta.env.VITE_PUSHER_PORT ?? 443, 30 | // forceTLS: (import.meta.env.VITE_PUSHER_SCHEME ?? 'https') === 'https', 31 | // enabledTransports: ['ws', 'wss'], 32 | // }); 33 | -------------------------------------------------------------------------------- /resources/markdown/policy.md: -------------------------------------------------------------------------------- 1 | # Privacy Policy 2 | 3 | Privacy Policy - Salon Bliss 4 | 5 | At Salon Bliss, we value and respect your privacy. This Privacy Policy outlines how we collect, use, and protect your personal information when you use our website. 6 | 7 | 1. Information Collection 8 | 1.1. We may collect personal information such as your name, email address, phone number, and other relevant details when you interact with our website or book appointments. 9 | 1.2. We use cookies and similar technologies to gather information about your browsing activities on our site. 10 | 11 | 2. Use of Information 12 | 2.1. We use the information we collect to provide and improve our services, respond to your inquiries, and communicate with you about appointments or promotions. 13 | 2.2. Your personal information will not be shared with third parties without your consent, except as required by law. 14 | 15 | 3. Data Security 16 | 3.1. We implement security measures to protect your personal information from unauthorized access, disclosure, or alteration. 17 | 3.2. While we strive to safeguard your information, no method of transmission over the internet or electronic storage is completely secure. 18 | 19 | 4. Third-Party Links 20 | 4.1. Our website may contain links to third-party websites or services. We are not responsible for their privacy practices or the content they provide. 21 | 22 | 5. Children's Privacy 23 | 5.1. Our services are not intended for individuals under the age of 18. We do not knowingly collect personal information from children. 24 | 25 | 6. Changes to the Privacy Policy 26 | 6.1. We may update our Privacy Policy from time to time. Any changes will be posted on this page with a revised date. 27 | 28 | By using our website, you consent to the terms of this Privacy Policy. If you have any questions or concerns regarding our privacy practices, please contact us. 29 | -------------------------------------------------------------------------------- /resources/views/api/index.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | {{ __('API Tokens') }} 5 |

6 |
7 | 8 |
9 |
10 | @livewire('api.api-token-manager') 11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /resources/views/auth/confirm-password.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | {{ __('This is a secure area of the application. Please confirm your password before continuing.') }} 9 |
10 | 11 | 12 | 13 |
14 | @csrf 15 | 16 |
17 | 18 | 19 |
20 | 21 |
22 | 23 | {{ __('Confirm') }} 24 | 25 |
26 |
27 |
28 |
29 | -------------------------------------------------------------------------------- /resources/views/auth/forgot-password.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | {{ __('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.') }} 9 |
10 | 11 | @if (session('status')) 12 |
13 | {{ session('status') }} 14 |
15 | @endif 16 | 17 | 18 | 19 |
20 | @csrf 21 | 22 |
23 | 24 | 25 |
26 | 27 |
28 | 29 | {{ __('Email Password Reset Link') }} 30 | 31 |
32 |
33 |
34 |
35 | -------------------------------------------------------------------------------- /resources/views/auth/login.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | @if (session('status')) 11 |
12 | {{ session('status') }} 13 |
14 | @endif 15 | 16 | 17 | @if (session('errormsg')) 18 |
19 | {{ session('errormsg') }} 20 |
21 | @endif 22 | 23 |
24 | @csrf 25 | 26 |
27 | 28 | 29 |
30 | 31 |
32 | 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 | Don't have an account? 57 | Create an Account 58 |
59 |
60 |
61 |
62 | -------------------------------------------------------------------------------- /resources/views/auth/reset-password.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | @csrf 11 | 12 | 13 | 14 |
15 | 16 | 17 |
18 | 19 |
20 | 21 | 22 |
23 | 24 |
25 | 26 | 27 |
28 | 29 |
30 | 31 | {{ __('Reset Password') }} 32 | 33 |
34 |
35 |
36 |
37 | -------------------------------------------------------------------------------- /resources/views/auth/verify-email.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | {{ __('Before continuing, 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.') }} 9 |
10 | 11 | @if (session('status') == 'verification-link-sent') 12 |
13 | {{ __('A new verification link has been sent to the email address you provided in your profile settings.') }} 14 |
15 | @endif 16 | 17 |
18 |
19 | @csrf 20 | 21 |
22 | 23 | {{ __('Resend Verification Email') }} 24 | 25 |
26 |
27 | 28 |
29 | 33 | {{ __('Edit Profile') }} 34 | 35 |
36 | @csrf 37 | 38 | 41 |
42 |
43 |
44 |
45 |
46 | -------------------------------------------------------------------------------- /resources/views/components/action-message.blade.php: -------------------------------------------------------------------------------- 1 | @props(['on']) 2 | 3 |
merge(['class' => 'text-sm text-gray-600']) }}> 9 | {{ $slot->isEmpty() ? 'Saved.' : $slot }} 10 |
11 | -------------------------------------------------------------------------------- /resources/views/components/action-section.blade.php: -------------------------------------------------------------------------------- 1 |
merge(['class' => 'md:grid md:grid-cols-3 md:gap-6']) }}> 2 | 3 | {{ $title }} 4 | {{ $description }} 5 | 6 | 7 |
8 |
9 | {{ $content }} 10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /resources/views/components/application-mark.blade.php: -------------------------------------------------------------------------------- 1 |
2 | 3 |

Salon Bliss

4 |
5 | -------------------------------------------------------------------------------- /resources/views/components/authentication-card-logo.blade.php: -------------------------------------------------------------------------------- 1 | 2 | {{-- 3 | 4 | 5 | --}} 6 | 7 |

Salon Bliss

8 |
9 | -------------------------------------------------------------------------------- /resources/views/components/authentication-card.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{ $logo }} 4 |
5 | 6 |
7 | {{ $slot }} 8 |
9 |
10 | -------------------------------------------------------------------------------- /resources/views/components/button.blade.php: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /resources/views/components/checkbox.blade.php: -------------------------------------------------------------------------------- 1 | merge(['class' => 'rounded border-gray-300 text-pink-600 shadow-sm focus:ring-pink-500']) !!}> 2 | -------------------------------------------------------------------------------- /resources/views/components/confirmation-modal.blade.php: -------------------------------------------------------------------------------- 1 | @props(['id' => null, 'maxWidth' => null]) 2 | 3 | 4 |
5 |
6 |
7 | 8 | 9 | 10 |
11 | 12 |
13 |

14 | {{ $title }} 15 |

16 | 17 |
18 | {{ $content }} 19 |
20 |
21 |
22 |
23 | 24 |
25 | {{ $footer }} 26 |
27 |
28 | -------------------------------------------------------------------------------- /resources/views/components/confirms-password.blade.php: -------------------------------------------------------------------------------- 1 | @props(['title' => __('Confirm Password'), 'content' => __('For your security, please confirm your password to continue.'), 'button' => __('Confirm')]) 2 | 3 | @php 4 | $confirmableId = md5($attributes->wire('then')); 5 | @endphp 6 | 7 | wire('then') }} 9 | x-data 10 | x-ref="span" 11 | x-on:click="$wire.startConfirmingPassword('{{ $confirmableId }}')" 12 | x-on:password-confirmed.window="setTimeout(() => $event.detail.id === '{{ $confirmableId }}' && $refs.span.dispatchEvent(new CustomEvent('then', { bubbles: false })), 250);" 13 | > 14 | {{ $slot }} 15 | 16 | 17 | @once 18 | 19 | 20 | {{ $title }} 21 | 22 | 23 | 24 | {{ $content }} 25 | 26 |
27 | 31 | 32 | 33 |
34 |
35 | 36 | 37 | 38 | {{ __('Cancel') }} 39 | 40 | 41 | 42 | {{ $button }} 43 | 44 | 45 |
46 | @endonce 47 | -------------------------------------------------------------------------------- /resources/views/components/danger-button.blade.php: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /resources/views/components/dashboard/navlinks.blade.php: -------------------------------------------------------------------------------- 1 | @if(Auth::User()) 2 | 3 | {{-- Only admin can manage the users at the moment --}} 4 | {{-- @if(Auth::User()->role()->first()->name == 'Admin') 5 | 6 | {{ __('Manage Users') }} 7 | 8 | @endif --}} 9 | @endif 10 | -------------------------------------------------------------------------------- /resources/views/components/dialog-modal.blade.php: -------------------------------------------------------------------------------- 1 | @props(['id' => null, 'maxWidth' => null]) 2 | 3 | 4 |
5 |
6 | {{ $title }} 7 |
8 | 9 |
10 | {{ $content }} 11 |
12 |
13 | 14 |
15 | {{ $footer }} 16 |
17 |
18 | -------------------------------------------------------------------------------- /resources/views/components/dropdown-link.blade.php: -------------------------------------------------------------------------------- 1 | merge(['class' => 'block w-full px-4 py-2 text-left 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/dropdown.blade.php: -------------------------------------------------------------------------------- 1 | @props(['align' => 'right', 'width' => '48', 'contentClasses' => 'py-1 bg-white', 'dropdownClasses' => '']) 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 'none': 12 | case 'false': 13 | $alignmentClasses = ''; 14 | break; 15 | case 'right': 16 | default: 17 | $alignmentClasses = 'origin-top-right right-0'; 18 | break; 19 | } 20 | 21 | switch ($width) { 22 | case '48': 23 | $width = 'w-48'; 24 | break; 25 | } 26 | @endphp 27 | 28 |
29 |
30 | {{ $trigger }} 31 |
32 | 33 | 47 |
48 | -------------------------------------------------------------------------------- /resources/views/components/form-section.blade.php: -------------------------------------------------------------------------------- 1 | @props(['submit']) 2 | 3 |
merge(['class' => 'md:grid md:grid-cols-3 md:gap-6']) }}> 4 | 5 | {{ $title }} 6 | {{ $description }} 7 | 8 | 9 |
10 |
11 |
12 |
13 | {{ $form }} 14 |
15 |
16 | 17 | @if (isset($actions)) 18 |
19 | {{ $actions }} 20 |
21 | @endif 22 |
23 |
24 |
25 | -------------------------------------------------------------------------------- /resources/views/components/input-error.blade.php: -------------------------------------------------------------------------------- 1 | @props(['for']) 2 | 3 | @error($for) 4 |

merge(['class' => 'text-sm text-red-600']) }}>{{ $message }}

5 | @enderror 6 | -------------------------------------------------------------------------------- /resources/views/components/input.blade.php: -------------------------------------------------------------------------------- 1 | @props(['disabled' => false]) 2 | 3 | merge(['class' => 'border-gray-300 focus:border-pink-500 focus:ring-pink-500 rounded-md shadow-sm']) !!}> 4 | -------------------------------------------------------------------------------- /resources/views/components/label.blade.php: -------------------------------------------------------------------------------- 1 | @props(['value']) 2 | 3 | 6 | -------------------------------------------------------------------------------- /resources/views/components/modal.blade.php: -------------------------------------------------------------------------------- 1 | @props(['id', 'maxWidth']) 2 | 3 | @php 4 | $id = $id ?? md5($attributes->wire('model')); 5 | 6 | $maxWidth = [ 7 | 'sm' => 'sm:max-w-sm', 8 | 'md' => 'sm:max-w-md', 9 | 'lg' => 'sm:max-w-lg', 10 | 'xl' => 'sm:max-w-xl', 11 | '2xl' => 'sm:max-w-2xl', 12 | ][$maxWidth ?? '2xl']; 13 | @endphp 14 | 15 | 44 | -------------------------------------------------------------------------------- /resources/views/components/nav-link.blade.php: -------------------------------------------------------------------------------- 1 | @props(['active']) 2 | 3 | @php 4 | $classes = ($active ?? false) 5 | ? 'inline-flex items-center px-1 pt-1 border-b-2 border-pink-400 text-sm font-medium leading-5 text-gray-900 focus:outline-none focus:border-pink-700 transition duration-150 ease-in-out' 6 | : 'inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition duration-150 ease-in-out'; 7 | @endphp 8 | 9 | merge(['class' => $classes]) }}> 10 | {{ $slot }} 11 | 12 | -------------------------------------------------------------------------------- /resources/views/components/responsive-nav-link.blade.php: -------------------------------------------------------------------------------- 1 | @props(['active']) 2 | 3 | @php 4 | $classes = ($active ?? false) 5 | ? 'block w-full pl-3 pr-4 py-2 border-l-4 border-pink-400 text-left text-base font-medium text-pink-700 bg-pink-50 focus:outline-none focus:text-pink-800 focus:bg-pink-100 focus:border-pink-700 transition duration-150 ease-in-out' 6 | : 'block w-full pl-3 pr-4 py-2 border-l-4 border-transparent text-left 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 | -------------------------------------------------------------------------------- /resources/views/components/secondary-button.blade.php: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /resources/views/components/section-border.blade.php: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /resources/views/components/section-title.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
3 |

{{ $title }}

4 | 5 |

6 | {{ $description }} 7 |

8 |
9 | 10 |
11 | {{ $aside ?? '' }} 12 |
13 |
14 | -------------------------------------------------------------------------------- /resources/views/components/service-card.blade.php: -------------------------------------------------------------------------------- 1 | @props([ 2 | /** @var \mixed */ 3 | 'service' 4 | ]) 5 | 6 |
class(['mx-auto w-80 min-w-[300px] mt-5 pb-20 transform overflow-hidden rounded-lg bg-white shadow-md duration-300 hover:scale-105 hover:shadow-lg']) }}> 7 | Product Image 9 |
10 |

{{ $service->name}}

11 |

{{ $service->description}}

12 | 13 |
14 |
15 |
16 |

LKR {{ $service->price}}

17 | {{--

LKR 4,000.00

--}} 18 |
19 | {{--

10% off

--}} 20 |
21 | Book Now 22 |
23 |
24 |
25 | -------------------------------------------------------------------------------- /resources/views/components/switchable-team.blade.php: -------------------------------------------------------------------------------- 1 | @props(['team', 'component' => 'dropdown-link']) 2 | 3 |
4 | @method('PUT') 5 | @csrf 6 | 7 | 8 | 9 | 10 | 11 |
12 | @if (Auth::user()->isCurrentTeam($team)) 13 | 14 | 15 | 16 | @endif 17 | 18 |
{{ $team->name }}
19 |
20 |
21 |
22 | -------------------------------------------------------------------------------- /resources/views/components/validation-errors.blade.php: -------------------------------------------------------------------------------- 1 | @if ($errors->any()) 2 |
3 |
{{ __('Whoops! Something went wrong.') }}
4 | 5 |
    6 | @foreach ($errors->all() as $error) 7 |
  • {{ $error }}
  • 8 | @endforeach 9 |
10 |
11 | @endif 12 | -------------------------------------------------------------------------------- /resources/views/components/web/navlinks.blade.php: -------------------------------------------------------------------------------- 1 | {{-- Nav Links for the customer facing web --}} 2 | 3 | 4 | {{ __('Services') }} 5 | 6 | 7 | 8 | {{ __('Deals') }} 9 | 10 | {{-- 11 | 12 | {{ __('Manage Users') }} 13 | --}} -------------------------------------------------------------------------------- /resources/views/components/welcome.blade.php: -------------------------------------------------------------------------------- 1 | 2 | @php 3 | use App\Enums\UserRolesEnum; 4 | $role = UserRolesEnum::from(Auth::user()->role_id)->name; 5 | @endphp 6 |
7 | 8 | 9 | 10 |

11 | Welcome to the 12 | {{ $role}} 13 | Dashboard 14 |

15 |

16 | Manage your activity here. 17 |

18 |
19 | 20 |
21 | 22 | 23 |
24 | 25 |
26 | -------------------------------------------------------------------------------- /resources/views/dashboard/manage-appointments/index.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /resources/views/dashboard/manage-categories/index.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/views/dashboard/manage-deals/index.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/views/dashboard/manage-locations/index.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/views/dashboard/manage-services/create.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

5 | {{ __('Create Services') }} 6 |

7 |
8 | 9 | 10 | 11 | 12 |
-------------------------------------------------------------------------------- /resources/views/dashboard/manage-services/index.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /resources/views/emails/team-invitation.blade.php: -------------------------------------------------------------------------------- 1 | @component('mail::message') 2 | {{ __('You have been invited to join the :team team!', ['team' => $invitation->team->name]) }} 3 | 4 | @if (Laravel\Fortify\Features::enabled(Laravel\Fortify\Features::registration())) 5 | {{ __('If you do not have an account, you may create one by clicking the button below. After creating an account, you may click the invitation acceptance button in this email to accept the team invitation:') }} 6 | 7 | @component('mail::button', ['url' => route('register')]) 8 | {{ __('Create Account') }} 9 | @endcomponent 10 | 11 | {{ __('If you already have an account, you may accept this invitation by clicking the button below:') }} 12 | 13 | @else 14 | {{ __('You may accept this invitation by clicking the button below:') }} 15 | @endif 16 | 17 | 18 | @component('mail::button', ['url' => $acceptUrl]) 19 | {{ __('Accept Invitation') }} 20 | @endcomponent 21 | 22 | {{ __('If you did not expect to receive an invitation to this team, you may discard this email.') }} 23 | @endcomponent 24 | -------------------------------------------------------------------------------- /resources/views/layouts/app.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 | @vite(['resources/css/app.css', 'resources/js/app.js']) 21 | 22 | 23 | @livewireStyles 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | 32 | 33 | @isset($mainLogoRoute) 34 | {{ $mainLogoRoute }} 35 | @endisset 36 | 37 | 38 | 39 | 40 | @isset($navlinks) 41 | {{ $navlinks }} 42 | @endif 43 | 44 | 45 | 46 | 47 | @if (isset($header)) 48 |
49 |
50 | {{ $header }} 51 |
52 |
53 | @endif 54 | 55 | 56 |
57 | {{ $slot }} 58 |
59 |
60 | 61 | @stack('modals') 62 | 63 | @livewireScripts 64 | 65 | 66 | -------------------------------------------------------------------------------- /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 | @vite(['resources/css/app.css', 'resources/js/app.js']) 21 | 22 | 23 |
24 | {{ $slot }} 25 |
26 | 27 | 28 | -------------------------------------------------------------------------------- /resources/views/policy.blade.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 | 6 |
7 | 8 |
9 | {!! $policy !!} 10 |
11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /resources/views/profile/delete-user-form.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ __('Delete Account') }} 4 | 5 | 6 | 7 | {{ __('Permanently delete your account.') }} 8 | 9 | 10 | 11 |
12 | {{ __('Once your account is deleted, all of its resources and data will be permanently deleted. Before deleting your account, please download any data or information that you wish to retain.') }} 13 |
14 | 15 |
16 | 17 | {{ __('Delete Account') }} 18 | 19 |
20 | 21 | 22 | 23 | 24 | {{ __('Delete Account') }} 25 | 26 | 27 | 28 | {{ __('Are you sure you want to delete your account? Once your account is deleted, all of its resources and data will be permanently deleted. Please enter your password to confirm you would like to permanently delete your account.') }} 29 | 30 |
31 | 37 | 38 | 39 |
40 |
41 | 42 | 43 | 44 | {{ __('Cancel') }} 45 | 46 | 47 | 48 | {{ __('Delete Account') }} 49 | 50 | 51 |
52 |
53 |
54 | -------------------------------------------------------------------------------- /resources/views/profile/show.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | {{ __('Profile') }} 5 |

6 |
7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 | @if (Laravel\Fortify\Features::canUpdateProfileInformation()) 15 | @livewire('profile.update-profile-information-form') 16 | 17 | 18 | @endif 19 | 20 | @if (Laravel\Fortify\Features::enabled(Laravel\Fortify\Features::updatePasswords())) 21 |
22 | @livewire('profile.update-password-form') 23 |
24 | 25 | 26 | @endif 27 | 28 | @if (Laravel\Fortify\Features::canManageTwoFactorAuthentication()) 29 |
30 | @livewire('profile.two-factor-authentication-form') 31 |
32 | 33 | 34 | @endif 35 | 36 |
37 | @livewire('profile.logout-other-browser-sessions-form') 38 |
39 | 40 | @if (Laravel\Jetstream\Jetstream::hasAccountDeletionFeatures()) 41 | 42 | 43 |
44 | @livewire('profile.delete-user-form') 45 |
46 | @endif 47 |
48 |
49 |
50 | -------------------------------------------------------------------------------- /resources/views/profile/update-password-form.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ __('Update Password') }} 4 | 5 | 6 | 7 | {{ __('Ensure your account is using a long, random password to stay secure.') }} 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 |
16 | 17 |
18 | 19 | 20 | 21 |
22 | 23 |
24 | 25 | 26 | 27 |
28 |
29 | 30 | 31 | 32 | {{ __('Saved.') }} 33 | 34 | 35 | 36 | {{ __('Save') }} 37 | 38 | 39 |
40 | -------------------------------------------------------------------------------- /resources/views/terms.blade.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 | 6 |
7 | 8 |
9 | {!! $terms !!} 10 |
11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/html/button.blade.php: -------------------------------------------------------------------------------- 1 | @props([ 2 | 'url', 3 | 'color' => 'primary', 4 | 'align' => 'center', 5 | ]) 6 | 7 | 8 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/html/footer.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/html/header.blade.php: -------------------------------------------------------------------------------- 1 | @props(['url']) 2 | 3 | 4 | 5 | @if (trim($slot) === 'Laravel') 6 | 7 | @else 8 | 9 |
10 | {{ $slot }} 11 | @endif 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/html/layout.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ config('app.name') }} 5 | 6 | 7 | 8 | 9 | 26 | 27 | 28 | 29 | 30 | 31 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/html/message.blade.php: -------------------------------------------------------------------------------- 1 | 2 | {{-- Header --}} 3 | 4 | 5 | {{ config('app.name') }} 6 | 7 | 8 | 9 | {{-- Body --}} 10 | {{ $slot }} 11 | 12 | {{-- Subcopy --}} 13 | @isset($subcopy) 14 | 15 | 16 | {{ $subcopy }} 17 | 18 | 19 | @endisset 20 | 21 | {{-- Footer --}} 22 | 23 | 24 | © {{ date('Y') }} {{ config('app.name') }}. @lang('All rights reserved.') 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/html/panel.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/html/subcopy.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/html/table.blade.php: -------------------------------------------------------------------------------- 1 |
2 | {{ Illuminate\Mail\Markdown::parse($slot) }} 3 |
4 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/text/button.blade.php: -------------------------------------------------------------------------------- 1 | {{ $slot }}: {{ $url }} 2 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/text/footer.blade.php: -------------------------------------------------------------------------------- 1 | {{ $slot }} 2 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/text/header.blade.php: -------------------------------------------------------------------------------- 1 | [{{ $slot }}]({{ $url }}) 2 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/text/layout.blade.php: -------------------------------------------------------------------------------- 1 | {!! strip_tags($header) !!} 2 | 3 | {!! strip_tags($slot) !!} 4 | @isset($subcopy) 5 | 6 | {!! strip_tags($subcopy) !!} 7 | @endisset 8 | 9 | {!! strip_tags($footer) !!} 10 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/text/message.blade.php: -------------------------------------------------------------------------------- 1 | 2 | {{-- Header --}} 3 | 4 | 5 | {{ config('app.name') }} 6 | 7 | 8 | 9 | {{-- Body --}} 10 | {{ $slot }} 11 | 12 | {{-- Subcopy --}} 13 | @isset($subcopy) 14 | 15 | 16 | {{ $subcopy }} 17 | 18 | 19 | @endisset 20 | 21 | {{-- Footer --}} 22 | 23 | 24 | © {{ date('Y') }} {{ config('app.name') }}. @lang('All rights reserved.') 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/text/panel.blade.php: -------------------------------------------------------------------------------- 1 | {{ $slot }} 2 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/text/subcopy.blade.php: -------------------------------------------------------------------------------- 1 | {{ $slot }} 2 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/text/table.blade.php: -------------------------------------------------------------------------------- 1 | {{ $slot }} 2 | -------------------------------------------------------------------------------- /resources/views/web/deals.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | {{ __('Deals') }} 5 |

6 |
7 | 8 |
9 | 10 | 11 | @if($deals->count() > 0) 12 | @foreach($deals as $deal) 13 | @if($deal->is_hidden == false) 14 | 15 |
16 | 17 |
{{ $deal->name }}
18 |
19 |

{{ $deal->description }}

20 | 21 | View Offer 22 | 23 | 24 |
25 | 26 | 27 | 28 | @endif 29 | @endforeach 30 | @else 31 |
32 |
33 | 34 | 35 | 36 | 40 | 41 | 42 |
38 | No Deals Found 39 |
43 |
44 |
45 | @endif 46 |
47 |
-------------------------------------------------------------------------------- /resources/views/web/services.blade.php: -------------------------------------------------------------------------------- 1 | 2 | {{-- --}} 3 | {{--

--}} 4 | {{-- {{ __('Services') }}--}} 5 | {{--

--}} 6 | {{--
--}} 7 | {{--
--}} 8 | {{-- @foreach ($services as $service)--}} 9 | {{-- @if($service->is_hidden == false)--}} 10 | {{--
--}} 11 | {{-- Product Image--}} 12 | {{--
--}} 13 | {{--

{{ $service->name}}

--}} 14 | {{--

{{ $service->description}}

--}} 15 | {{-- --}} 16 | {{--
--}} 17 | {{--
--}} 18 | {{--
--}} 19 | {{--

LKR {{ $service->price}}

--}} 20 | {{-- --}}{{--

LKR 4,000.00

--}} 21 | {{--
--}} 22 | {{--

10% off

--}} 23 | {{--
--}} 24 | {{-- Book Now--}} 25 | {{--
--}} 26 | {{--
--}} 27 | {{--
--}} 28 | {{-- @endif--}} 29 | {{-- @endforeach--}} 30 | 31 | {{--
--}} 32 | {{-- --}} 33 | 34 | 35 |
36 | -------------------------------------------------------------------------------- /routes/api.php: -------------------------------------------------------------------------------- 1 | get('/user', function (Request $request) { 18 | return $request->user(); 19 | }); 20 | 21 | 22 | //Route::get('/hello', function () { 23 | // return response()->json(['message' => 'Hello World!'], 200); 24 | //}); 25 | 26 | 27 | 28 | Route::post('/tokens/create', function (Request $request) { 29 | $token = $request->user()->createToken($request->token_name); 30 | 31 | return ['token' => $token->plainTextToken]; 32 | }); 33 | 34 | Route::middleware([ 35 | 'auth:sanctum', 36 | 'validateRole:Admin,Employee' 37 | ] 38 | )->group( 39 | function () { 40 | 41 | Route::prefix('services')->group( function () { 42 | Route::get('/', [\App\Http\Controllers\ServicesAPI::class, 'index'])->name('api-services.index'); 43 | Route::get('/{id}', [\App\Http\Controllers\ServicesAPI::class, 'show'])->name('api-services.show'); 44 | Route::post('/', [\App\Http\Controllers\ServicesAPI::class, 'store'])->name('api-services.store'); 45 | Route::put('/{id}', [\App\Http\Controllers\ServicesAPI::class, 'update'])->name('api-services.update'); 46 | Route::delete('/{id}', [\App\Http\Controllers\ServicesAPI::class, 'destroy'])->name('api-services.destroy'); 47 | }); 48 | 49 | Route::prefix('customers')->group( function () { 50 | Route::get('/', [\App\Http\Controllers\CustomerAPI::class, 'index'])->name('api-customers.index'); 51 | Route::get('/{id}', [\App\Http\Controllers\CustomerAPI::class, 'show'])->name('api-customers.show'); 52 | Route::post('/', [\App\Http\Controllers\CustomerAPI::class, 'store'])->name('api-customers.store'); 53 | Route::put('/{id}', [\App\Http\Controllers\CustomerAPI::class, 'update'])->name('api-customers.update'); 54 | Route::delete('/{id}', [\App\Http\Controllers\CustomerAPI::class, 'destroy'])->name('api-customers.destroy'); 55 | 56 | }); 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | } 65 | ); 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /routes/channels.php: -------------------------------------------------------------------------------- 1 | id === (int) $id; 18 | }); 19 | -------------------------------------------------------------------------------- /routes/console.php: -------------------------------------------------------------------------------- 1 | comment(Inspiring::quote()); 19 | })->purpose('Display an inspiring quote'); 20 | -------------------------------------------------------------------------------- /storage/app/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !public/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /storage/app/public/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/debugbar/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/.gitignore: -------------------------------------------------------------------------------- 1 | compiled.php 2 | config.php 3 | down 4 | events.scanned.php 5 | maintenance.php 6 | routes.php 7 | routes.scanned.php 8 | schedule-* 9 | services.json 10 | -------------------------------------------------------------------------------- /storage/framework/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !data/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /storage/framework/cache/data/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/sessions/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/testing/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/views/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | const defaultTheme = require("tailwindcss/defaultTheme"); 2 | 3 | /** @type {import('tailwindcss').Config} */ 4 | module.exports = { 5 | content: [ 6 | "./vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php", 7 | "./vendor/laravel/jetstream/**/*.blade.php", 8 | "./storage/framework/views/*.php", 9 | "./resources/views/**/*.blade.php", 10 | ], 11 | 12 | theme: { 13 | extend: { 14 | fontFamily: { 15 | // sans: ["Figtree", ...defaultTheme.fontFamily.sans], 16 | sans: ["K2D", ...defaultTheme.fontFamily.sans], 17 | }, 18 | }, 19 | }, 20 | 21 | plugins: [ 22 | require("@tailwindcss/forms"), 23 | require("@tailwindcss/typography"), 24 | require('@tailwindcss/aspect-ratio'), 25 | ], 26 | }; 27 | -------------------------------------------------------------------------------- /tests/CreatesApplication.php: -------------------------------------------------------------------------------- 1 | make(Kernel::class)->bootstrap(); 18 | 19 | return $app; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/Feature/ApiTokenPermissionsTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('API support is not enabled.'); 21 | 22 | return; 23 | } 24 | 25 | $this->actingAs($user = User::factory()->withPersonalTeam()->create()); 26 | 27 | $token = $user->tokens()->create([ 28 | 'name' => 'Test Token', 29 | 'token' => Str::random(40), 30 | 'abilities' => ['create', 'read'], 31 | ]); 32 | 33 | Livewire::test(ApiTokenManager::class) 34 | ->set(['managingPermissionsFor' => $token]) 35 | ->set(['updateApiTokenForm' => [ 36 | 'permissions' => [ 37 | 'delete', 38 | 'missing-permission', 39 | ], 40 | ]]) 41 | ->call('updateApiToken'); 42 | 43 | $this->assertTrue($user->fresh()->tokens->first()->can('delete')); 44 | $this->assertFalse($user->fresh()->tokens->first()->can('read')); 45 | $this->assertFalse($user->fresh()->tokens->first()->can('missing-permission')); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tests/Feature/AuthenticationTest.php: -------------------------------------------------------------------------------- 1 | get('/login'); 17 | 18 | $response->assertStatus(200); 19 | } 20 | 21 | public function test_users_can_authenticate_using_the_login_screen(): void 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(): void 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/BrowserSessionsTest.php: -------------------------------------------------------------------------------- 1 | actingAs($user = User::factory()->create()); 18 | 19 | Livewire::test(LogoutOtherBrowserSessionsForm::class) 20 | ->set('password', 'password') 21 | ->call('logoutOtherBrowserSessions') 22 | ->assertSuccessful(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/Feature/CreateApiTokenTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('API support is not enabled.'); 20 | 21 | return; 22 | } 23 | 24 | $this->actingAs($user = User::factory()->withPersonalTeam()->create()); 25 | 26 | Livewire::test(ApiTokenManager::class) 27 | ->set(['createApiTokenForm' => [ 28 | 'name' => 'Test Token', 29 | 'permissions' => [ 30 | 'read', 31 | 'update', 32 | ], 33 | ]]) 34 | ->call('createApiToken'); 35 | 36 | $this->assertCount(1, $user->fresh()->tokens); 37 | $this->assertEquals('Test Token', $user->fresh()->tokens->first()->name); 38 | $this->assertTrue($user->fresh()->tokens->first()->can('read')); 39 | $this->assertFalse($user->fresh()->tokens->first()->can('delete')); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/Feature/DeleteAccountTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('Account deletion is not enabled.'); 20 | 21 | return; 22 | } 23 | 24 | $this->actingAs($user = User::factory()->create()); 25 | 26 | $component = Livewire::test(DeleteUserForm::class) 27 | ->set('password', 'password') 28 | ->call('deleteUser'); 29 | 30 | $this->assertNull($user->fresh()); 31 | } 32 | 33 | public function test_correct_password_must_be_provided_before_account_can_be_deleted(): void 34 | { 35 | if (! Features::hasAccountDeletionFeatures()) { 36 | $this->markTestSkipped('Account deletion is not enabled.'); 37 | 38 | return; 39 | } 40 | 41 | $this->actingAs($user = User::factory()->create()); 42 | 43 | Livewire::test(DeleteUserForm::class) 44 | ->set('password', 'wrong-password') 45 | ->call('deleteUser') 46 | ->assertHasErrors(['password']); 47 | 48 | $this->assertNotNull($user->fresh()); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/Feature/DeleteApiTokenTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('API support is not enabled.'); 21 | 22 | return; 23 | } 24 | 25 | $this->actingAs($user = User::factory()->withPersonalTeam()->create()); 26 | 27 | $token = $user->tokens()->create([ 28 | 'name' => 'Test Token', 29 | 'token' => Str::random(40), 30 | 'abilities' => ['create', 'read'], 31 | ]); 32 | 33 | Livewire::test(ApiTokenManager::class) 34 | ->set(['apiTokenIdBeingDeleted' => $token->id]) 35 | ->call('deleteApiToken'); 36 | 37 | $this->assertCount(0, $user->fresh()->tokens); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/Feature/EmailVerificationTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('Email verification not enabled.'); 22 | 23 | return; 24 | } 25 | 26 | $user = User::factory()->withPersonalTeam()->unverified()->create(); 27 | 28 | $response = $this->actingAs($user)->get('/email/verify'); 29 | 30 | $response->assertStatus(200); 31 | } 32 | 33 | public function test_email_can_be_verified(): void 34 | { 35 | if (! Features::enabled(Features::emailVerification())) { 36 | $this->markTestSkipped('Email verification not enabled.'); 37 | 38 | return; 39 | } 40 | 41 | Event::fake(); 42 | 43 | $user = User::factory()->unverified()->create(); 44 | 45 | $verificationUrl = URL::temporarySignedRoute( 46 | 'verification.verify', 47 | now()->addMinutes(60), 48 | ['id' => $user->id, 'hash' => sha1($user->email)] 49 | ); 50 | 51 | $response = $this->actingAs($user)->get($verificationUrl); 52 | 53 | Event::assertDispatched(Verified::class); 54 | 55 | $this->assertTrue($user->fresh()->hasVerifiedEmail()); 56 | $response->assertRedirect(RouteServiceProvider::HOME.'?verified=1'); 57 | } 58 | 59 | public function test_email_can_not_verified_with_invalid_hash(): void 60 | { 61 | if (! Features::enabled(Features::emailVerification())) { 62 | $this->markTestSkipped('Email verification not enabled.'); 63 | 64 | return; 65 | } 66 | 67 | $user = User::factory()->unverified()->create(); 68 | 69 | $verificationUrl = URL::temporarySignedRoute( 70 | 'verification.verify', 71 | now()->addMinutes(60), 72 | ['id' => $user->id, 'hash' => sha1('wrong-email')] 73 | ); 74 | 75 | $this->actingAs($user)->get($verificationUrl); 76 | 77 | $this->assertFalse($user->fresh()->hasVerifiedEmail()); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /tests/Feature/ExampleTest.php: -------------------------------------------------------------------------------- 1 | get('/'); 16 | 17 | $response->assertStatus(200); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/Feature/PasswordConfirmationTest.php: -------------------------------------------------------------------------------- 1 | withPersonalTeam()->create(); 17 | 18 | $response = $this->actingAs($user)->get('/user/confirm-password'); 19 | 20 | $response->assertStatus(200); 21 | } 22 | 23 | public function test_password_can_be_confirmed(): void 24 | { 25 | $user = User::factory()->create(); 26 | 27 | $response = $this->actingAs($user)->post('/user/confirm-password', [ 28 | 'password' => 'password', 29 | ]); 30 | 31 | $response->assertRedirect(); 32 | $response->assertSessionHasNoErrors(); 33 | } 34 | 35 | public function test_password_is_not_confirmed_with_invalid_password(): void 36 | { 37 | $user = User::factory()->create(); 38 | 39 | $response = $this->actingAs($user)->post('/user/confirm-password', [ 40 | 'password' => 'wrong-password', 41 | ]); 42 | 43 | $response->assertSessionHasErrors(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/Feature/ProfileInformationTest.php: -------------------------------------------------------------------------------- 1 | actingAs($user = User::factory()->create()); 18 | 19 | $component = Livewire::test(UpdateProfileInformationForm::class); 20 | 21 | $this->assertEquals($user->name, $component->state['name']); 22 | $this->assertEquals($user->email, $component->state['email']); 23 | } 24 | 25 | public function test_profile_information_can_be_updated(): void 26 | { 27 | $this->actingAs($user = User::factory()->create()); 28 | 29 | Livewire::test(UpdateProfileInformationForm::class) 30 | ->set('state', ['name' => 'Test Name', 'email' => 'test@example.com']) 31 | ->call('updateProfileInformation'); 32 | 33 | $this->assertEquals('Test Name', $user->fresh()->name); 34 | $this->assertEquals('test@example.com', $user->fresh()->email); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/Feature/RegistrationTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('Registration support is not enabled.'); 19 | 20 | return; 21 | } 22 | 23 | $response = $this->get('/register'); 24 | 25 | $response->assertStatus(200); 26 | } 27 | 28 | public function test_registration_screen_cannot_be_rendered_if_support_is_disabled(): void 29 | { 30 | if (Features::enabled(Features::registration())) { 31 | $this->markTestSkipped('Registration support is enabled.'); 32 | 33 | return; 34 | } 35 | 36 | $response = $this->get('/register'); 37 | 38 | $response->assertStatus(404); 39 | } 40 | 41 | public function test_new_users_can_register(): void 42 | { 43 | if (! Features::enabled(Features::registration())) { 44 | $this->markTestSkipped('Registration support is not enabled.'); 45 | 46 | return; 47 | } 48 | 49 | $response = $this->post('/register', [ 50 | 'name' => 'Test User', 51 | 'email' => 'test@example.com', 52 | 'password' => 'password', 53 | 'password_confirmation' => 'password', 54 | 'terms' => Jetstream::hasTermsAndPrivacyPolicyFeature(), 55 | ]); 56 | 57 | $this->assertAuthenticated(); 58 | $response->assertRedirect(RouteServiceProvider::HOME); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tests/Feature/UpdatePasswordTest.php: -------------------------------------------------------------------------------- 1 | actingAs($user = User::factory()->create()); 19 | 20 | Livewire::test(UpdatePasswordForm::class) 21 | ->set('state', [ 22 | 'current_password' => 'password', 23 | 'password' => 'new-password', 24 | 'password_confirmation' => 'new-password', 25 | ]) 26 | ->call('updatePassword'); 27 | 28 | $this->assertTrue(Hash::check('new-password', $user->fresh()->password)); 29 | } 30 | 31 | public function test_current_password_must_be_correct(): void 32 | { 33 | $this->actingAs($user = User::factory()->create()); 34 | 35 | Livewire::test(UpdatePasswordForm::class) 36 | ->set('state', [ 37 | 'current_password' => 'wrong-password', 38 | 'password' => 'new-password', 39 | 'password_confirmation' => 'new-password', 40 | ]) 41 | ->call('updatePassword') 42 | ->assertHasErrors(['current_password']); 43 | 44 | $this->assertTrue(Hash::check('password', $user->fresh()->password)); 45 | } 46 | 47 | public function test_new_passwords_must_match(): void 48 | { 49 | $this->actingAs($user = User::factory()->create()); 50 | 51 | Livewire::test(UpdatePasswordForm::class) 52 | ->set('state', [ 53 | 'current_password' => 'password', 54 | 'password' => 'new-password', 55 | 'password_confirmation' => 'wrong-password', 56 | ]) 57 | ->call('updatePassword') 58 | ->assertHasErrors(['password']); 59 | 60 | $this->assertTrue(Hash::check('password', $user->fresh()->password)); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | assertTrue(true); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import laravel, { refreshPaths } from 'laravel-vite-plugin'; 3 | 4 | export default defineConfig({ 5 | plugins: [ 6 | laravel({ 7 | input: [ 8 | 'resources/css/app.css', 9 | 'resources/js/app.js', 10 | ], 11 | refresh: [ 12 | ...refreshPaths, 13 | 'app/Http/Livewire/**', 14 | ], 15 | }), 16 | ], 17 | }); 18 | --------------------------------------------------------------------------------