├── .editorconfig ├── .env.example ├── .gitattributes ├── .gitignore ├── README.md ├── app ├── Actions │ ├── Fortify │ │ ├── CreateNewUser.php │ │ ├── PasswordValidationRules.php │ │ ├── ResetUserPassword.php │ │ ├── UpdateUserPassword.php │ │ └── UpdateUserProfileInformation.php │ └── Jetstream │ │ └── DeleteUser.php ├── Console │ └── Kernel.php ├── Enums │ ├── OrderStatus.php │ └── PaymentStatus.php ├── Exceptions │ └── Handler.php ├── Filament │ └── Resources │ │ ├── BrandResource.php │ │ ├── BrandResource │ │ └── Pages │ │ │ ├── CreateBrand.php │ │ │ ├── EditBrand.php │ │ │ ├── ListBrands.php │ │ │ └── ViewBrand.php │ │ ├── CategoryResource.php │ │ ├── CategoryResource │ │ └── Pages │ │ │ ├── CreateCategory.php │ │ │ ├── EditCategory.php │ │ │ ├── ListCategories.php │ │ │ └── ViewCategory.php │ │ ├── OrderResource.php │ │ ├── OrderResource │ │ ├── Pages │ │ │ ├── CreateOrder.php │ │ │ ├── EditOrder.php │ │ │ ├── ListOrders.php │ │ │ └── ViewOrder.php │ │ ├── RelationManagers │ │ │ ├── ItemsRelationManager.php │ │ │ └── PaymentsRelationManager.php │ │ └── Widgets │ │ │ └── OrdersStatsOverview.php │ │ ├── ProductResource.php │ │ ├── ProductResource │ │ └── Pages │ │ │ ├── CreateProduct.php │ │ │ ├── EditProduct.php │ │ │ ├── ListProducts.php │ │ │ └── ViewProduct.php │ │ ├── UserResource.php │ │ └── UserResource │ │ ├── Pages │ │ ├── CreateUser.php │ │ ├── EditUser.php │ │ ├── ListUsers.php │ │ └── ViewUser.php │ │ └── RelationManagers │ │ └── OrdersRelationManager.php ├── Http │ ├── Controllers │ │ ├── Controller.php │ │ ├── OrderController.php │ │ ├── PaymentController.php │ │ ├── ProductController.php │ │ └── SiteController.php │ ├── Kernel.php │ └── Middleware │ │ ├── Authenticate.php │ │ ├── EncryptCookies.php │ │ ├── PreventRequestsDuringMaintenance.php │ │ ├── RedirectIfAuthenticated.php │ │ ├── TrimStrings.php │ │ ├── TrustHosts.php │ │ ├── TrustProxies.php │ │ ├── ValidateSignature.php │ │ └── VerifyCsrfToken.php ├── Livewire │ ├── AddToCart.php │ ├── AddToWishlist.php │ ├── Cart │ │ ├── Count.php │ │ └── Show.php │ ├── Checkout.php │ ├── Order │ │ └── Count.php │ └── Wishlist │ │ ├── Count.php │ │ └── Show.php ├── Models │ ├── Brand.php │ ├── Cart.php │ ├── Category.php │ ├── CategoryProduct.php │ ├── Order.php │ ├── OrderDetail.php │ ├── OrderItem.php │ ├── Payment.php │ ├── Product.php │ ├── Role.php │ ├── User.php │ └── Wishlist.php ├── Observers │ └── UserObserver.php ├── Providers │ ├── AppServiceProvider.php │ ├── AuthServiceProvider.php │ ├── BroadcastServiceProvider.php │ ├── EventServiceProvider.php │ ├── Filament │ │ └── AdminPanelProvider.php │ ├── FortifyServiceProvider.php │ ├── JetstreamServiceProvider.php │ └── RouteServiceProvider.php └── View │ └── Components │ ├── AppLayout.php │ ├── GuestLayout.php │ ├── Home │ ├── Cart.php │ ├── Category.php │ ├── Checkout.php │ ├── Collection.php │ ├── Featured.php │ ├── HeroSection.php │ └── NewArrival.php │ └── Layout │ ├── Footer.php │ └── Navbar.php ├── artisan ├── bootstrap ├── app.php └── cache │ └── .gitignore ├── composer.json ├── composer.lock ├── config ├── app.php ├── auth.php ├── broadcasting.php ├── cache.php ├── cors.php ├── curator.php ├── database.php ├── filament-spatie-roles-permissions.php ├── filament-tiptap-editor.php ├── filesystems.php ├── fortify.php ├── hashing.php ├── jetstream.php ├── logging.php ├── mail.php ├── paystack.php ├── paytabs.php ├── permission.php ├── queue.php ├── sanctum.php ├── services.php ├── session.php ├── toaster.php └── view.php ├── database ├── .gitignore ├── factories │ ├── ProductFactory.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_10_17_193429_create_sessions_table.php │ ├── 2023_10_17_225458_create_media_table.php │ ├── 2023_10_18_131627_create_brands_table.php │ ├── 2023_10_18_151743_create_categories_table.php │ ├── 2023_10_18_172310_create_products_table.php │ ├── 2023_10_18_195602_create_category_products_table.php │ ├── 2023_11_06_105713_create_carts_table.php │ ├── 2023_11_06_215205_create_orders_table.php │ ├── 2023_11_07_092848_create_order_items_table.php │ ├── 2023_11_07_093331_create_order_details_table.php │ ├── 2023_11_07_151337_create_wishlists_table.php │ ├── 2023_11_07_175328_add_order_id_to_order_details_table.php │ ├── 2023_11_07_175545_add_tracking_no_to_orders_table.php │ ├── 2023_11_07_182611_add_soft_deletes_to_orders_table.php │ ├── 2023_12_01_214039_add_details_to_orders_table.php │ ├── 2023_12_29_134112_create_payments_table.php │ ├── 2023_12_29_221111_add_soft_deletes_to_users_table.php │ ├── 2023_12_29_230613_add_channel_to_payments_table.php │ ├── 2023_12_30_114858_create_permission_tables.php │ └── 2024_02_04_173648_create_notifications_table.php └── seeders │ ├── AdminUserSeeder.php │ ├── BrandSeeder.php │ ├── CategorySeeder.php │ ├── DatabaseSeeder.php │ ├── ProductSeeder.php │ └── RoleSeeder.php ├── package-lock.json ├── package.json ├── phpunit.xml ├── postcss.config.js ├── public ├── .htaccess ├── css │ ├── awcodes │ │ ├── curator │ │ │ └── curator.css │ │ └── tiptap-editor │ │ │ └── tiptap.css │ └── filament │ │ ├── filament │ │ └── app.css │ │ ├── forms │ │ └── forms.css │ │ └── support │ │ └── support.css ├── favicon.ico ├── index.php ├── js │ ├── awcodes │ │ ├── curator │ │ │ └── components │ │ │ │ └── curation.js │ │ └── tiptap-editor │ │ │ └── components │ │ │ └── tiptap.js │ └── filament │ │ ├── filament │ │ ├── app.js │ │ └── echo.js │ │ ├── forms │ │ └── components │ │ │ ├── color-picker.js │ │ │ ├── date-time-picker.js │ │ │ ├── file-upload.js │ │ │ ├── key-value.js │ │ │ ├── markdown-editor.js │ │ │ ├── rich-editor.js │ │ │ ├── select.js │ │ │ ├── tags-input.js │ │ │ └── textarea.js │ │ ├── notifications │ │ └── notifications.js │ │ ├── support │ │ ├── async-alpine.js │ │ └── support.js │ │ ├── tables │ │ └── components │ │ │ └── table.js │ │ └── widgets │ │ └── components │ │ ├── chart.js │ │ └── stats-overview │ │ └── stat │ │ └── chart.js └── robots.txt ├── resources ├── css │ ├── app.css │ └── filament │ │ └── admin │ │ ├── tailwind.config.js │ │ └── theme.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 │ ├── dialog-modal.blade.php │ ├── dropdown-link.blade.php │ ├── dropdown.blade.php │ ├── form-section.blade.php │ ├── home │ │ ├── cart.blade.php │ │ ├── category.blade.php │ │ ├── checkout.blade.php │ │ ├── collection.blade.php │ │ ├── featured.blade.php │ │ ├── hero-section.blade.php │ │ ├── new-arrival.blade.php │ │ └── wishlist.blade.php │ ├── input-error.blade.php │ ├── input.blade.php │ ├── label.blade.php │ ├── layout │ │ ├── footer.blade.php │ │ └── navbar.blade.php │ ├── modal.blade.php │ ├── nav-link.blade.php │ ├── order │ │ ├── index.blade.php │ │ ├── order-item.blade.php │ │ ├── order-status.blade.php │ │ ├── payment-status.blade.php │ │ └── view.blade.php │ ├── product-item.blade.php │ ├── product │ │ └── view.blade.php │ ├── responsive-nav-link.blade.php │ ├── secondary-button.blade.php │ ├── section-border.blade.php │ ├── section-title.blade.php │ ├── switchable-team.blade.php │ ├── validation-errors.blade.php │ └── welcome.blade.php │ ├── dashboard.blade.php │ ├── emails │ └── team-invitation.blade.php │ ├── home.blade.php │ ├── layouts │ ├── app.blade.php │ └── guest.blade.php │ ├── livewire │ ├── add-to-cart.blade.php │ ├── add-to-wishlist.blade.php │ ├── cart │ │ ├── count.blade.php │ │ └── show.blade.php │ ├── checkout.blade.php │ ├── order │ │ └── count.blade.php │ └── wishlist │ │ ├── count.blade.php │ │ └── show.blade.php │ ├── navigation-menu.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 ├── routes ├── api.php ├── channels.php ├── console.php └── web.php ├── storage ├── app │ ├── .gitignore │ └── public │ │ └── .gitignore ├── framework │ ├── .gitignore │ ├── cache │ │ ├── .gitignore │ │ └── data │ │ │ └── .gitignore │ ├── sessions │ │ └── .gitignore │ ├── testing │ │ └── .gitignore │ └── views │ │ └── .gitignore └── logs │ └── .gitignore ├── tailwind.config.js ├── tests ├── 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 /.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 = false 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="Laracom" 2 | APP_ENV=local 3 | APP_KEY= 4 | APP_DEBUG=true 5 | APP_DOMAIN=laracom.test 6 | APP_URL="http://${APP_DOMAIN}" 7 | 8 | # Subdomains 9 | ADMIN_SUBDOMAIN="admin.${APP_DOMAIN}" 10 | 11 | LOG_CHANNEL=stack 12 | LOG_DEPRECATIONS_CHANNEL=null 13 | LOG_LEVEL=debug 14 | 15 | DB_CONNECTION=mysql 16 | DB_HOST=127.0.0.1 17 | DB_PORT=3306 18 | DB_DATABASE=laracom 19 | DB_USERNAME=root 20 | DB_PASSWORD= 21 | 22 | BROADCAST_DRIVER=log 23 | CACHE_DRIVER=file 24 | FILESYSTEM_DISK=local 25 | QUEUE_CONNECTION=sync 26 | SESSION_DRIVER=database 27 | SESSION_LIFETIME=120 28 | 29 | MEMCACHED_HOST=127.0.0.1 30 | 31 | REDIS_HOST=127.0.0.1 32 | REDIS_PASSWORD=null 33 | REDIS_PORT=6379 34 | 35 | MAIL_MAILER=smtp 36 | MAIL_HOST=mailpit 37 | MAIL_PORT=1025 38 | MAIL_USERNAME=null 39 | MAIL_PASSWORD=null 40 | MAIL_ENCRYPTION=null 41 | MAIL_FROM_ADDRESS="hello@example.com" 42 | MAIL_FROM_NAME="${APP_NAME}" 43 | 44 | AWS_ACCESS_KEY_ID= 45 | AWS_SECRET_ACCESS_KEY= 46 | AWS_DEFAULT_REGION=us-east-1 47 | AWS_BUCKET= 48 | AWS_USE_PATH_STYLE_ENDPOINT=false 49 | 50 | PUSHER_APP_ID= 51 | PUSHER_APP_KEY= 52 | PUSHER_APP_SECRET= 53 | PUSHER_HOST= 54 | PUSHER_PORT=443 55 | PUSHER_SCHEME=https 56 | PUSHER_APP_CLUSTER=mt1 57 | 58 | VITE_APP_NAME="${APP_NAME}" 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 | 65 | PAYSTACK_PUBLIC_KEY= 66 | PAYSTACK_SECRET_KEY= 67 | PAYSTACK_PAYMENT_URL= 68 | MERCHANT_EMAIL= 69 | 70 | VIVA_API_KEY= 71 | VIVA_MERCHANT_ID= 72 | VIVA_ENVIRONMENT= 73 | VIVA_CLIENT_ID= 74 | VIVA_CLIENT_SECRET= 75 | VIVA_ISV_PARTNER_ID= 76 | VIVA_ISV_PARTNER_API_KEY= 77 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## About Project 2 | 3 | # Plugins 4 | 5 | - [Laravel JetStream](https://jetstream.laravel.com/introduction.html). 6 | 7 | # Tech Stack 8 | 9 | - [Laravel](https://laravel.com). 10 | - [Livewire](https://livewire.laravel.com). 11 | - [TailwindCSS](https://tailwindcss.com). 12 | - [FilamentPHP V3](https://filamentphp.com). 13 | 14 | ## Installation 15 | - Clone the repository 16 | ```bash 17 | git clone https://github.com/njugunamwangi/laracom.git 18 | ``` 19 | - On the root of the directory, copy and paste .env.example onto .env and configure the database accordingly 20 | ```bash 21 | copy .env.example .env 22 | ``` 23 | 24 | - Install composer dependencies bu running composer install 25 | ```bash 26 | composer install 27 | ``` 28 | - Install npm dependencies 29 | ```bash 30 | npm install 31 | ``` 32 | 33 | - Run migrations and seed admin user 34 | ```bash 35 | php artisan migrate --seed 36 | ``` 37 | 38 | - Generate laravel application key using 39 | ```bash 40 | php artisan key:generate 41 | ``` 42 | 43 | - Run laravel application using 44 | ```bash 45 | php artisan serve 46 | ``` 47 | - Run react application using 48 | ```bash 49 | npm run dev 50 | ``` 51 | 52 | - Storage 53 | ```bash 54 | php artisan storage:link 55 | ``` 56 | ## Prerequisites 57 | 58 | - Admin credentials 59 | ```bash 60 | email: admin@admin.com 61 | password: password 62 | ``` 63 | 64 | - Admin url 65 | ```bash 66 | http://admin.laracom.test 67 | ``` 68 | 69 | ## License 70 | 71 | [MIT](https://choosealicense.com/licenses/mit/) 72 | 73 | -------------------------------------------------------------------------------- /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 | 'terms' => Jetstream::hasTermsAndPrivacyPolicyFeature() ? ['accepted', 'required'] : '', 28 | ])->validate(); 29 | 30 | $user = User::create([ 31 | 'name' => $input['name'], 32 | 'email' => $input['email'], 33 | 'password' => Hash::make($input['password']), 34 | ]); 35 | 36 | $user->assignRole(Role::IS_CUSTOMER); 37 | 38 | return $user; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /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 | 'photo' => ['nullable', 'mimes:jpg,jpeg,png', 'max:1024'], 24 | ])->validateWithBag('updateProfileInformation'); 25 | 26 | if (isset($input['photo'])) { 27 | $user->updateProfilePhoto($input['photo']); 28 | } 29 | 30 | if ($input['email'] !== $user->email && 31 | $user instanceof MustVerifyEmail) { 32 | $this->updateVerifiedUser($user, $input); 33 | } else { 34 | $user->forceFill([ 35 | 'name' => $input['name'], 36 | 'email' => $input['email'], 37 | ])->save(); 38 | } 39 | } 40 | 41 | /** 42 | * Update the given verified user's profile information. 43 | * 44 | * @param array $input 45 | */ 46 | protected function updateVerifiedUser(User $user, array $input): void 47 | { 48 | $user->forceFill([ 49 | 'name' => $input['name'], 50 | 'email' => $input['email'], 51 | 'email_verified_at' => null, 52 | ])->save(); 53 | 54 | $user->sendEmailVerificationNotification(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /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/OrderStatus.php: -------------------------------------------------------------------------------- 1 | 'gray', 16 | self::Processing => 'primary', 17 | self::Delivered => 'success', 18 | self::Cancelled => 'danger', 19 | }; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/Enums/PaymentStatus.php: -------------------------------------------------------------------------------- 1 | 'primary', 15 | self::Paid => 'success', 16 | self::Failed => 'warning', 17 | }; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /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/Filament/Resources/BrandResource/Pages/CreateBrand.php: -------------------------------------------------------------------------------- 1 | getResource()::getUrl('index'); 16 | } 17 | 18 | protected function getCreatedNotification(): ?Notification 19 | { 20 | return Notification::make() 21 | ->success() 22 | ->title('Brand created') 23 | ->body('The brand has been created successfully.'); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/Filament/Resources/BrandResource/Pages/EditBrand.php: -------------------------------------------------------------------------------- 1 | previousUrl ?? $this->getResource()::getUrl('index'); 27 | } 28 | 29 | protected function getSavedNotification(): ?Notification 30 | { 31 | return Notification::make() 32 | ->success() 33 | ->title('Brand updated') 34 | ->body('The brand has been saved successfully.'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/Filament/Resources/BrandResource/Pages/ListBrands.php: -------------------------------------------------------------------------------- 1 | getResource()::getUrl('index'); 16 | } 17 | 18 | protected function getCreatedNotification(): ?Notification 19 | { 20 | return Notification::make() 21 | ->success() 22 | ->title('Category created') 23 | ->body('The category has been created successfully.'); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/Filament/Resources/CategoryResource/Pages/EditCategory.php: -------------------------------------------------------------------------------- 1 | previousUrl ?? $this->getResource()::getUrl('index'); 27 | } 28 | 29 | protected function getSavedNotification(): ?Notification 30 | { 31 | return Notification::make() 32 | ->success() 33 | ->title('Category updated') 34 | ->body('The category has been saved successfully.'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/Filament/Resources/CategoryResource/Pages/ListCategories.php: -------------------------------------------------------------------------------- 1 | Tab::make(), 35 | 'pending' => Tab::make() 36 | ->modifyQueryUsing(fn (Builder $query) => $query->where('order_status', OrderStatus::Pending)), 37 | 'Processing' => Tab::make() 38 | ->modifyQueryUsing(fn (Builder $query) => $query->where('order_status', OrderStatus::Processing)), 39 | 'Delivered' => Tab::make() 40 | ->modifyQueryUsing(fn (Builder $query) => $query->where('order_status', OrderStatus::Delivered)), 41 | 'Cancelled' => Tab::make() 42 | ->modifyQueryUsing(fn (Builder $query) => $query->where('order_status', OrderStatus::Cancelled)), 43 | ]; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/Filament/Resources/OrderResource/Pages/ViewOrder.php: -------------------------------------------------------------------------------- 1 | schema([ 19 | Forms\Components\TextInput::make('product') 20 | ->required() 21 | ->maxLength(255), 22 | ]); 23 | } 24 | 25 | public function table(Table $table): Table 26 | { 27 | return $table 28 | ->recordTitleAttribute('product') 29 | ->columns([ 30 | Tables\Columns\TextColumn::make('product.product'), 31 | Tables\Columns\TextColumn::make('quantity'), 32 | Tables\Columns\TextColumn::make('unit_price') 33 | ->money('Kes'), 34 | ]) 35 | ->filters([ 36 | // 37 | ]) 38 | ->headerActions([ 39 | Tables\Actions\CreateAction::make(), 40 | ]) 41 | ->actions([ 42 | Tables\Actions\EditAction::make(), 43 | Tables\Actions\DeleteAction::make(), 44 | ]) 45 | ->bulkActions([ 46 | Tables\Actions\BulkActionGroup::make([ 47 | Tables\Actions\DeleteBulkAction::make(), 48 | ]), 49 | ]); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/Filament/Resources/OrderResource/RelationManagers/PaymentsRelationManager.php: -------------------------------------------------------------------------------- 1 | schema([ 19 | Forms\Components\TextInput::make('id') 20 | ->required() 21 | ->maxLength(255), 22 | ]); 23 | } 24 | 25 | public function table(Table $table): Table 26 | { 27 | return $table 28 | ->recordTitleAttribute('id') 29 | ->columns([ 30 | Tables\Columns\TextColumn::make('reference'), 31 | Tables\Columns\TextColumn::make('ip_address'), 32 | Tables\Columns\TextColumn::make('paymentId') 33 | ->label('Payment ID'), 34 | Tables\Columns\TextColumn::make('currency'), 35 | Tables\Columns\TextColumn::make('amount'), 36 | Tables\Columns\TextColumn::make('channel'), 37 | Tables\Columns\TextColumn::make('gateway_response'), 38 | ]) 39 | ->filters([ 40 | // 41 | ]) 42 | ->headerActions([ 43 | Tables\Actions\CreateAction::make(), 44 | ]) 45 | ->actions([ 46 | Tables\Actions\EditAction::make(), 47 | Tables\Actions\DeleteAction::make(), 48 | ]) 49 | ->bulkActions([ 50 | Tables\Actions\BulkActionGroup::make([ 51 | Tables\Actions\DeleteBulkAction::make(), 52 | ]), 53 | ]); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/Filament/Resources/OrderResource/Widgets/OrdersStatsOverview.php: -------------------------------------------------------------------------------- 1 | icon('heroicon-o-shopping-bag') 17 | ->description('Total number of orders'), 18 | Stat::make('Total Amount', Order::sum('total_price')) 19 | ->icon('heroicon-o-credit-card') 20 | ->description('Total amount payable'), 21 | Stat::make('Total Revenue', Order::query() 22 | ->where('payment_status', PaymentStatus::Paid) 23 | ->sum('total_price')) 24 | ->icon('heroicon-o-banknotes') 25 | ->description('Total amount paid'), 26 | ]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/Filament/Resources/ProductResource/Pages/CreateProduct.php: -------------------------------------------------------------------------------- 1 | id(); 15 | 16 | return $data; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/Filament/Resources/ProductResource/Pages/EditProduct.php: -------------------------------------------------------------------------------- 1 | id(); 26 | 27 | return $data; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/Filament/Resources/ProductResource/Pages/ListProducts.php: -------------------------------------------------------------------------------- 1 | schema([ 19 | Forms\Components\TextInput::make('id') 20 | ->required() 21 | ->maxLength(255), 22 | ]); 23 | } 24 | 25 | public function table(Table $table): Table 26 | { 27 | return $table 28 | ->recordTitleAttribute('id') 29 | ->columns([ 30 | Tables\Columns\TextColumn::make('tracking_no'), 31 | Tables\Columns\TextColumn::make('order_id'), 32 | Tables\Columns\TextColumn::make('order_status'), 33 | Tables\Columns\TextColumn::make('payment_status'), 34 | Tables\Columns\TextColumn::make('total_price') 35 | ->money('KES'), 36 | ]) 37 | ->filters([ 38 | // 39 | ]) 40 | ->headerActions([ 41 | Tables\Actions\CreateAction::make(), 42 | ]) 43 | ->actions([ 44 | Tables\Actions\EditAction::make(), 45 | Tables\Actions\DeleteAction::make(), 46 | ]) 47 | ->bulkActions([ 48 | Tables\Actions\BulkActionGroup::make([ 49 | Tables\Actions\DeleteBulkAction::make(), 50 | ]), 51 | ]); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/Http/Controllers/Controller.php: -------------------------------------------------------------------------------- 1 | user()->id; 16 | 17 | $orders = Order::query() 18 | ->where('user_id', '=', $user) 19 | ->get(); 20 | 21 | $title = 'Orders'; 22 | 23 | return view('components.order.index', compact('orders', 'title')); 24 | } 25 | 26 | return abort(403, 'Forbidden'); 27 | } 28 | 29 | public function show(Order $order, Request $request) 30 | { 31 | if (Auth::check()) { 32 | 33 | $user = $request->user()->id; 34 | 35 | $title = $order->order_id; 36 | 37 | if ($order->user_id != $user) { 38 | return abort(403, 'You cannot view this order'); 39 | } 40 | 41 | return view('components.order.view', compact('order', 'title')); 42 | } 43 | 44 | return abort(403, 'Forbidden'); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/Http/Controllers/ProductController.php: -------------------------------------------------------------------------------- 1 | status) { 33 | throw new NotFoundHttpException; 34 | } 35 | 36 | return view('components.product.view', compact('product')); 37 | } 38 | 39 | /** 40 | * Update the specified resource in storage. 41 | */ 42 | public function update(Request $request, string $id) 43 | { 44 | // 45 | } 46 | 47 | /** 48 | * Remove the specified resource from storage. 49 | */ 50 | public function destroy(string $id) 51 | { 52 | // 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/Http/Controllers/SiteController.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/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/Livewire/AddToCart.php: -------------------------------------------------------------------------------- 1 | product = $product; 18 | } 19 | 20 | public function render() 21 | { 22 | return view('livewire.add-to-cart'); 23 | } 24 | 25 | public function addToCart(int $productId) 26 | { 27 | if (! Auth::check()) { 28 | Toaster::info('Please login to proceed'); 29 | } else { 30 | 31 | if ($this->product->where('id', $productId)->where('status', 1)->exists()) { 32 | 33 | $model = Cart::query() 34 | ->where('user_id', '=', auth()->user()->id) 35 | ->where('product_id', '=', $productId) 36 | ->first(); 37 | 38 | if (! $model) { 39 | 40 | Cart::create([ 41 | 'user_id' => auth()->user()->id, 42 | 'product_id' => $productId, 43 | 'quantity' => 1, 44 | ]); 45 | $this->dispatch('CartUpdated'); 46 | 47 | Toaster::success('Product added to cart'); 48 | } else { 49 | Toaster::warning('Product already added to cart.'); 50 | } 51 | } else { 52 | Toaster::danger('Product does not exist.'); 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/Livewire/AddToWishlist.php: -------------------------------------------------------------------------------- 1 | product = $product; 18 | } 19 | 20 | public function render() 21 | { 22 | return view('livewire.add-to-wishlist'); 23 | } 24 | 25 | public function addToWishlist(int $productId) 26 | { 27 | if (! Auth::check()) { 28 | Toaster::info('Please login to proceed.'); 29 | } else { 30 | 31 | if ($this->product->where('id', $productId)->where('status', 1)->exists()) { 32 | 33 | $model = Wishlist::query() 34 | ->where('user_id', '=', auth()->user()->id) 35 | ->where('product_id', '=', $productId) 36 | ->first(); 37 | 38 | if (! $model) { 39 | 40 | Wishlist::create([ 41 | 'user_id' => auth()->user()->id, 42 | 'product_id' => $productId, 43 | ]); 44 | $this->dispatch('WishlistUpdated'); 45 | 46 | Toaster::success('Product added to wishlist.'); 47 | } else { 48 | Toaster::warning('Product already added to wishlist.'); 49 | } 50 | } else { 51 | Toaster::danger('Product does not exist.'); 52 | 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/Livewire/Cart/Count.php: -------------------------------------------------------------------------------- 1 | 'cartCount']; 14 | 15 | public function cartCount() 16 | { 17 | if (Auth::check()) { 18 | return $this->cartCount = Cart::query() 19 | ->where('user_id', '=', auth()->user()->id) 20 | ->count(); 21 | } else { 22 | return $this->cartCount = 0; 23 | } 24 | } 25 | 26 | public function render() 27 | { 28 | $this->cartCount = $this->cartCount(); 29 | 30 | return view('livewire.cart.count', ['cartCount' => $this->cartCount]); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/Livewire/Order/Count.php: -------------------------------------------------------------------------------- 1 | orderCount = $this->orderCount(); 16 | 17 | return view('livewire.order.count', ['orderCount' => $this->orderCount]); 18 | } 19 | 20 | public function orderCount() 21 | { 22 | if (Auth::check()) { 23 | return $this->orderCount = Order::query() 24 | ->where('user_id', '=', auth()->user()->id) 25 | ->count(); 26 | } else { 27 | return $this->orderCount = 0; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/Livewire/Wishlist/Count.php: -------------------------------------------------------------------------------- 1 | 'wishlistCount']; 14 | 15 | public function wishlistCount() 16 | { 17 | if (Auth::check()) { 18 | return $this->wishlistCount = Wishlist::query() 19 | ->where('user_id', '=', auth()->user()->id) 20 | ->count(); 21 | } else { 22 | return $this->wishlistCount = 0; 23 | } 24 | } 25 | 26 | public function render() 27 | { 28 | $this->wishlistCount = $this->wishlistCount(); 29 | 30 | return view('livewire.wishlist.count', ['wishlistCount' => $this->wishlistCount]); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/Livewire/Wishlist/Show.php: -------------------------------------------------------------------------------- 1 | wishlist = Wishlist::query() 17 | ->where('user_id', '=', auth()->user()->id) 18 | ->get(); 19 | } else { 20 | $this->wishlist = null; 21 | } 22 | 23 | return view('livewire.wishlist.show', ['wishlist' => $this->wishlist]); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/Models/Cart.php: -------------------------------------------------------------------------------- 1 | belongsTo(Product::class, 'product_id', 'id'); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/Models/CategoryProduct.php: -------------------------------------------------------------------------------- 1 | 'datetime', 31 | 'order_status' => OrderStatus::class, 32 | 'payment_status' => PaymentStatus::class, 33 | ]; 34 | 35 | public function user(): BelongsTo 36 | { 37 | return $this->belongsTo(User::class, 'user_id'); 38 | } 39 | 40 | public function items(): HasMany 41 | { 42 | return $this->hasMany(OrderItem::class, 'order_id', 'id'); 43 | } 44 | 45 | public function details(): HasOne 46 | { 47 | return $this->hasOne(OrderDetail::class, 'order_id', 'id'); 48 | } 49 | 50 | public function date() 51 | { 52 | return $this->created_at->format('F jS, Y'); 53 | } 54 | 55 | public function payments(): HasMany 56 | { 57 | return $this->hasMany(Payment::class); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/Models/OrderDetail.php: -------------------------------------------------------------------------------- 1 | belongsTo(Order::class, 'order_id'); 23 | } 24 | 25 | public function product(): BelongsTo 26 | { 27 | return $this->belongsTo(Product::class, 'product_id'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/Models/Payment.php: -------------------------------------------------------------------------------- 1 | belongsTo(Order::class); 18 | } 19 | 20 | public function user(): BelongsTo 21 | { 22 | return $this->belongsTo(User::class); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/Models/Product.php: -------------------------------------------------------------------------------- 1 | generateSlugsFrom('product') 26 | ->saveSlugsTo('slug'); 27 | } 28 | 29 | public function image(): BelongsTo 30 | { 31 | return $this->belongsTo(Media::class, 'image_id'); 32 | } 33 | 34 | public function productImage(): BelongsTo 35 | { 36 | return $this->belongsTo(Media::class, 'image_id', 'id'); 37 | } 38 | 39 | public function createdBy(): BelongsTo 40 | { 41 | return $this->belongsTo(User::class, 'created_by'); 42 | } 43 | 44 | public function updatedBy(): BelongsTo 45 | { 46 | return $this->belongsTo(User::class, 'updated_by'); 47 | } 48 | 49 | public function deletedBy(): BelongsTo 50 | { 51 | return $this->belongsTo(User::class, 'deleted_by'); 52 | } 53 | 54 | public function brand(): BelongsTo 55 | { 56 | return $this->belongsTo(Brand::class, 'brand_id'); 57 | } 58 | 59 | public function categories(): BelongsToMany 60 | { 61 | return $this->belongsToMany(Category::class); 62 | } 63 | 64 | public function getDiscount() 65 | { 66 | $diff = $this->list_price - $this->retail_price; 67 | 68 | $percent = $diff / $this->list_price; 69 | 70 | return '-'.round($percent * 100).'%'; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /app/Models/Role.php: -------------------------------------------------------------------------------- 1 | belongsTo(Product::class, 'product_id', 'id'); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/Observers/UserObserver.php: -------------------------------------------------------------------------------- 1 | get(); 18 | 19 | foreach ($recipients as $recipient) { 20 | Notification::make() 21 | ->title('New user registration') 22 | ->warning() 23 | ->icon('heroicon-o-user-plus') 24 | ->body($user->name.' created an account') 25 | ->actions([ 26 | Action::make('markAsRead') 27 | ->button() 28 | ->markAsRead(), 29 | ]) 30 | ->sendToDatabase($recipient); 31 | } 32 | } 33 | 34 | /** 35 | * Handle the User "updated" event. 36 | */ 37 | public function updated(User $user): void 38 | { 39 | // 40 | } 41 | 42 | /** 43 | * Handle the User "deleted" event. 44 | */ 45 | public function deleted(User $user): void 46 | { 47 | // 48 | } 49 | 50 | /** 51 | * Handle the User "restored" event. 52 | */ 53 | public function restored(User $user): void 54 | { 55 | // 56 | } 57 | 58 | /** 59 | * Handle the User "force deleted" event. 60 | */ 61 | public function forceDeleted(User $user): void 62 | { 63 | // 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /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 | > 18 | */ 19 | protected $listen = [ 20 | Registered::class => [ 21 | SendEmailVerificationNotification::class, 22 | ], 23 | ]; 24 | 25 | /** 26 | * Register any events for your application. 27 | */ 28 | public function boot(): void 29 | { 30 | User::observe(UserObserver::class); 31 | } 32 | 33 | /** 34 | * Determine if events and listeners should be automatically discovered. 35 | */ 36 | public function shouldDiscoverEvents(): bool 37 | { 38 | return false; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/Providers/FortifyServiceProvider.php: -------------------------------------------------------------------------------- 1 | input(Fortify::username())).'|'.$request->ip()); 38 | 39 | return Limit::perMinute(5)->by($throttleKey); 40 | }); 41 | 42 | RateLimiter::for('two-factor', function (Request $request) { 43 | return Limit::perMinute(5)->by($request->session()->get('login.id')); 44 | }); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /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()); 29 | }); 30 | 31 | $this->routes(function () { 32 | Route::middleware('api') 33 | ->prefix('api') 34 | ->group(base_path('routes/api.php')); 35 | 36 | Route::middleware('web') 37 | ->group(base_path('routes/web.php')); 38 | }); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/View/Components/AppLayout.php: -------------------------------------------------------------------------------- 1 | whereNotNull('parent_id') 26 | ->inRandomOrder() 27 | ->limit(5) 28 | ->get(); 29 | 30 | return view('components.home.category', compact('categories')); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/View/Components/Home/Checkout.php: -------------------------------------------------------------------------------- 1 | whereNull('parent_id') 27 | ->limit(3) 28 | ->get(); 29 | 30 | return view('components.home.collection', compact('collections')); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/View/Components/Home/Featured.php: -------------------------------------------------------------------------------- 1 | where('status', '=', 1) 27 | ->orderByDesc('created_at') 28 | ->limit(4) 29 | ->get(); 30 | 31 | return view('components.home.new-arrival', compact('newArrivals')); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/View/Components/Layout/Footer.php: -------------------------------------------------------------------------------- 1 | whereNull('parent_id') 27 | ->get(); 28 | 29 | return view('components.layout.navbar', compact('categories')); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /artisan: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | make(Illuminate\Contracts\Console\Kernel::class); 34 | 35 | $status = $kernel->handle( 36 | $input = new Symfony\Component\Console\Input\ArgvInput, 37 | new Symfony\Component\Console\Output\ConsoleOutput 38 | ); 39 | 40 | /* 41 | |-------------------------------------------------------------------------- 42 | | Shutdown The Application 43 | |-------------------------------------------------------------------------- 44 | | 45 | | Once Artisan has finished running, we will fire off the shutdown events 46 | | so that any final work may be done by the application before we shut 47 | | down the process. This is the last thing to happen to the request. 48 | | 49 | */ 50 | 51 | $kernel->terminate($input, $status); 52 | 53 | exit($status); 54 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/curator.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'image/jpeg', 6 | 'image/png', 7 | 'image/webp', 8 | 'image/svg+xml', 9 | 'application/pdf', 10 | ], 11 | 'cloud_disks' => [ 12 | 's3', 13 | 'cloudinary', 14 | 'imgix', 15 | ], 16 | 'curation_formats' => [ 17 | 'jpg', 18 | 'jpeg', 19 | 'webp', 20 | 'png', 21 | 'avif', 22 | ], 23 | 'curation_presets' => [ 24 | \Awcodes\Curator\Curations\ThumbnailPreset::class, 25 | ], 26 | 'directory' => 'media', 27 | 'disk' => env('FILAMENT_FILESYSTEM_DISK', 'public'), 28 | 'glide' => [ 29 | 'server' => \Awcodes\Curator\Glide\DefaultServerFactory::class, 30 | 'fallbacks' => [], 31 | 'route_path' => 'curator', 32 | ], 33 | 'image_crop_aspect_ratio' => '16:9', 34 | 'image_resize_mode' => 'force', 35 | 'image_resize_target_height' => 720, 36 | 'image_resize_target_width' => 480, 37 | 'is_limited_to_directory' => false, 38 | 'is_tenant_aware' => true, 39 | 'max_size' => 5000, 40 | 'model' => \Awcodes\Curator\Models\Media::class, 41 | 'min_size' => 0, 42 | 'path_generator' => null, 43 | 'resources' => [ 44 | 'label' => 'Media', 45 | 'plural_label' => 'Media', 46 | 'navigation_group' => null, 47 | 'navigation_icon' => 'heroicon-o-photo', 48 | 'navigation_sort' => null, 49 | 'navigation_count_badge' => false, 50 | 'resource' => \Awcodes\Curator\Resources\MediaResource::class, 51 | ], 52 | 'should_preserve_filenames' => false, 53 | 'should_register_navigation' => true, 54 | 'visibility' => 'public', 55 | ]; 56 | -------------------------------------------------------------------------------- /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/paystack.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | return [ 13 | 14 | /** 15 | * Public Key From Paystack Dashboard 16 | */ 17 | 'publicKey' => getenv('PAYSTACK_PUBLIC_KEY'), 18 | 19 | /** 20 | * Secret Key From Paystack Dashboard 21 | */ 22 | 'secretKey' => getenv('PAYSTACK_SECRET_KEY'), 23 | 24 | /** 25 | * Paystack Payment URL 26 | */ 27 | 'paymentUrl' => getenv('PAYSTACK_PAYMENT_URL'), 28 | 29 | /** 30 | * Optional email address of the merchant 31 | */ 32 | 'merchantEmail' => getenv('MERCHANT_EMAIL'), 33 | 34 | ]; 35 | -------------------------------------------------------------------------------- /config/paytabs.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | return [ 13 | 14 | /* 15 | |-------------------------------------------------------------------------- 16 | | Merchant profile id 17 | |-------------------------------------------------------------------------- 18 | | 19 | | Your merchant profile id , you can find the profile id on your PayTabs Merchant’s Dashboard- profile. 20 | | 21 | */ 22 | 23 | 'profile_id' => env('paytabs_profile_id', null), 24 | 25 | /* 26 | |-------------------------------------------------------------------------- 27 | | Server Key 28 | |-------------------------------------------------------------------------- 29 | | 30 | | You can find the Server key on your PayTabs Merchant’s Dashboard - Developers - Key management. 31 | | 32 | */ 33 | 34 | 'server_key' => env('paytabs_server_key', null), 35 | 36 | /* 37 | |-------------------------------------------------------------------------- 38 | | Currency 39 | |-------------------------------------------------------------------------- 40 | | 41 | | The currency you registered in with PayTabs account 42 | you must pass value from this array ['AED','EGP','SAR','OMR','JOD','US'] 43 | | 44 | */ 45 | 46 | 'currency' => env('paytabs_currency', null), 47 | 48 | /* 49 | |-------------------------------------------------------------------------- 50 | | Region 51 | |-------------------------------------------------------------------------- 52 | | 53 | | The region you registered in with PayTabs 54 | you must pass value from this array ['ARE','EGY','SAU','OMN','JOR','GLOBAL'] 55 | | 56 | */ 57 | 58 | 'region' => env('paytabs_region', null), 59 | 60 | ]; 61 | -------------------------------------------------------------------------------- /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 | 'viva' => [ 35 | 'api_key' => env('VIVA_API_KEY'), 36 | 'merchant_id' => env('VIVA_MERCHANT_ID'), 37 | 'environment' => env('VIVA_ENVIRONMENT', 'production'), 38 | 'client_id' => env('VIVA_CLIENT_ID'), 39 | 'client_secret' => env('VIVA_CLIENT_SECRET'), 40 | 'isv_partner_id' => env('VIVA_ISV_PARTNER_ID'), 41 | 'isv_partner_api_key' => env('VIVA_ISV_PARTNER_API_KEY'), 42 | ], 43 | 44 | ]; 45 | -------------------------------------------------------------------------------- /config/toaster.php: -------------------------------------------------------------------------------- 1 | true, 13 | 14 | /** 15 | * The vertical alignment of the toast container. 16 | * 17 | * Supported: "bottom", "middle" or "top" 18 | */ 19 | 'alignment' => 'bottom', 20 | 21 | /** 22 | * Allow users to close toast messages prematurely. 23 | * 24 | * Supported: true | false 25 | */ 26 | 'closeable' => true, 27 | 28 | /** 29 | * The on-screen duration of each toast. 30 | * 31 | * Minimum: 3000 (in milliseconds) 32 | */ 33 | 'duration' => 3000, 34 | 35 | /** 36 | * The horizontal position of each toast. 37 | * 38 | * Supported: "center", "left" or "right" 39 | */ 40 | 'position' => 'right', 41 | 42 | /** 43 | * Whether messages passed as translation keys should be translated automatically. 44 | * 45 | * Supported: true | false 46 | */ 47 | 'translate' => true, 48 | ]; 49 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /database/factories/ProductFactory.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class ProductFactory extends Factory 14 | { 15 | /** 16 | * Define the model's default state. 17 | * 18 | * @return array 19 | */ 20 | public function definition(): array 21 | { 22 | return [ 23 | 'product' => $product = $this->faker->name, 24 | 'slug' => Str::slug($product), 25 | 'list_price' => $list_price = rand(1800, 2000), 26 | 'retail_price' => $list_price - rand(200, 350), 27 | 'status' => 1, 28 | 'brand_id' => $this->faker->randomElement(Brand::get()->pluck('id')), 29 | ]; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /database/factories/UserFactory.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class UserFactory extends Factory 15 | { 16 | /** 17 | * Define the model's default state. 18 | * 19 | * @return array 20 | */ 21 | public function definition(): array 22 | { 23 | return [ 24 | 'name' => $this->faker->name(), 25 | 'email' => $this->faker->unique()->safeEmail(), 26 | 'email_verified_at' => now(), 27 | 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 28 | 'two_factor_secret' => null, 29 | 'two_factor_recovery_codes' => null, 30 | 'remember_token' => Str::random(10), 31 | 'profile_photo_path' => null, 32 | 'current_team_id' => null, 33 | ]; 34 | } 35 | 36 | /** 37 | * Indicate that the model's email address should be unverified. 38 | */ 39 | public function unverified(): static 40 | { 41 | return $this->state(function (array $attributes) { 42 | return [ 43 | 'email_verified_at' => null, 44 | ]; 45 | }); 46 | } 47 | 48 | /** 49 | * Indicate that the user should have a personal team. 50 | */ 51 | public function withPersonalTeam(?callable $callback = null): static 52 | { 53 | if (! Features::hasTeamFeatures()) { 54 | return $this->state([]); 55 | } 56 | 57 | return $this->has( 58 | Team::factory() 59 | ->state(fn (array $attributes, User $user) => [ 60 | 'name' => $user->name.'\'s Team', 61 | 'user_id' => $user->id, 62 | 'personal_team' => true, 63 | ]) 64 | ->when(is_callable($callback), $callback), 65 | 'ownedTeams' 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /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->rememberToken(); 21 | $table->foreignId('current_team_id')->nullable(); 22 | $table->string('profile_photo_path', 2048)->nullable(); 23 | $table->timestamps(); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | */ 30 | public function down(): void 31 | { 32 | Schema::dropIfExists('users'); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /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_10_17_193429_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_10_17_225458_create_media_table.php: -------------------------------------------------------------------------------- 1 | getTable(), function (Blueprint $table) { 12 | $table->id(); 13 | $table->string('disk')->default('public'); 14 | $table->string('directory')->default('media'); 15 | $table->string('visibility')->default('public'); 16 | $table->string('name'); 17 | $table->string('path'); 18 | $table->unsignedInteger('width')->nullable(); 19 | $table->unsignedInteger('height')->nullable(); 20 | $table->unsignedInteger('size')->nullable(); 21 | $table->string('type')->default('image'); 22 | $table->string('ext'); 23 | $table->string('alt')->nullable(); 24 | $table->string('title')->nullable(); 25 | $table->text('description')->nullable(); 26 | $table->text('caption')->nullable(); 27 | $table->text('exif')->nullable(); 28 | $table->longText('curations')->nullable(); 29 | $table->timestamps(); 30 | }); 31 | } 32 | 33 | public function down(): void 34 | { 35 | Schema::dropIfExists(app(config('curator.model'))->getTable()); 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /database/migrations/2023_10_18_131627_create_brands_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->string('brand'); 17 | $table->string('slug')->unique(); 18 | $table->string('website')->unique(); 19 | $table->integer('featured_image_id')->nullable(); 20 | $table->longText('description')->nullable(); 21 | $table->softDeletes(); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | */ 28 | public function down(): void 29 | { 30 | Schema::dropIfExists('brands'); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /database/migrations/2023_10_18_151743_create_categories_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->string('category')->unique(); 17 | $table->string('slug')->unique(); 18 | $table->integer('image_id')->nullable(); 19 | $table->foreignId('parent_id')->nullable()->constrained('categories'); 20 | $table->longText('description')->nullable(); 21 | $table->softDeletes(); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | */ 28 | public function down(): void 29 | { 30 | Schema::dropIfExists('categories'); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /database/migrations/2023_10_18_172310_create_products_table.php: -------------------------------------------------------------------------------- 1 | id(); 17 | $table->integer('image_id')->nullable(); 18 | $table->string('product', 2000); 19 | $table->string('model_name', 2000)->nullable(); 20 | $table->string('model_number', 2000)->nullable(); 21 | $table->string('slug', 2000); 22 | $table->longText('description')->nullable(); 23 | $table->decimal('list_price', 10, 2); 24 | $table->decimal('retail_price', 10, 2); 25 | $table->boolean('status'); 26 | $table->longText('warranty')->nullable(); 27 | $table->foreignIdFor(User::class, 'created_by')->nullable(); 28 | $table->foreignIdFor(User::class, 'updated_by')->nullable(); 29 | $table->foreignIdFor(User::class, 'deleted_by')->nullable(); 30 | $table->foreignId('brand_id')->references('id')->on('brands'); 31 | $table->string('meta_title', 2000)->nullable(); 32 | $table->string('meta_description', 2000)->nullable(); 33 | $table->softDeletes(); 34 | $table->timestamps(); 35 | }); 36 | } 37 | 38 | /** 39 | * Reverse the migrations. 40 | */ 41 | public function down(): void 42 | { 43 | Schema::dropIfExists('products'); 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /database/migrations/2023_10_18_195602_create_category_products_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->foreignId('category_id')->constrained('categories'); 17 | $table->foreignId('product_id')->constrained('products'); 18 | }); 19 | } 20 | 21 | /** 22 | * Reverse the migrations. 23 | */ 24 | public function down(): void 25 | { 26 | Schema::dropIfExists('category_product'); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2023_11_06_105713_create_carts_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->foreignId('user_id')->constrained()->onDelete('cascade')->onUpdate('cascade'); 17 | $table->foreignId('product_id')->constrained()->onDelete('cascade')->onUpdate('cascade'); 18 | $table->integer('quantity')->unsigned(); 19 | $table->timestamps(); 20 | }); 21 | } 22 | 23 | /** 24 | * Reverse the migrations. 25 | */ 26 | public function down(): void 27 | { 28 | Schema::dropIfExists('carts'); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /database/migrations/2023_11_06_215205_create_orders_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->foreignId('user_id')->constrained(); 17 | $table->decimal('total_price', 10, 2); 18 | $table->string('payment_method', 45)->nullable(); 19 | $table->string('payment_status', 45); 20 | $table->timestamps(); 21 | }); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | */ 27 | public function down(): void 28 | { 29 | Schema::dropIfExists('orders'); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /database/migrations/2023_11_07_092848_create_order_items_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->foreignId('order_id')->constrained(); 17 | $table->foreignId('product_id')->constrained(); 18 | $table->integer('quantity'); 19 | $table->decimal('unit_price', 10, 2); 20 | $table->timestamps(); 21 | }); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | */ 27 | public function down(): void 28 | { 29 | Schema::dropIfExists('order_items'); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /database/migrations/2023_11_07_093331_create_order_details_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->string('name'); 17 | $table->string('email'); 18 | $table->string('phone'); 19 | $table->string('address1', 255); 20 | $table->string('address2', 255)->nullable(); 21 | $table->string('city', 255); 22 | $table->string('state', 45); 23 | $table->string('zipcode', 45); 24 | $table->timestamps(); 25 | }); 26 | } 27 | 28 | /** 29 | * Reverse the migrations. 30 | */ 31 | public function down(): void 32 | { 33 | Schema::dropIfExists('order_details'); 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /database/migrations/2023_11_07_151337_create_wishlists_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->foreignId('user_id')->constrained(); 17 | $table->foreignId('product_id')->constrained(); 18 | $table->timestamps(); 19 | }); 20 | } 21 | 22 | /** 23 | * Reverse the migrations. 24 | */ 25 | public function down(): void 26 | { 27 | Schema::dropIfExists('wishlists'); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /database/migrations/2023_11_07_175328_add_order_id_to_order_details_table.php: -------------------------------------------------------------------------------- 1 | foreignId('order_id')->constrained(); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('order_details', function (Blueprint $table) { 25 | $table->dropColumn('order_id'); 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2023_11_07_175545_add_tracking_no_to_orders_table.php: -------------------------------------------------------------------------------- 1 | string('tracking_no', 2000); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('orders', function (Blueprint $table) { 25 | $table->dropColumn('tracking_no'); 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2023_11_07_182611_add_soft_deletes_to_orders_table.php: -------------------------------------------------------------------------------- 1 | softDeletes(); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('orders', function (Blueprint $table) { 25 | $table->dropSoftDeletes(); 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2023_12_01_214039_add_details_to_orders_table.php: -------------------------------------------------------------------------------- 1 | string('order_id'); 16 | $table->string('order_status'); 17 | }); 18 | } 19 | 20 | /** 21 | * Reverse the migrations. 22 | */ 23 | public function down(): void 24 | { 25 | Schema::table('orders', function (Blueprint $table) { 26 | $table->dropColumn(['order_status', 'order_id']); 27 | }); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /database/migrations/2023_12_29_134112_create_payments_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->foreignId('order_id')->constrained(); 17 | $table->foreignId('user_id')->constrained(); 18 | $table->string('message'); 19 | $table->string('paymentId'); 20 | $table->string('status'); 21 | $table->string('domain'); 22 | $table->string('gateway_response'); 23 | $table->string('reference'); 24 | $table->string('currency'); 25 | $table->string('ip_address'); 26 | $table->decimal('amount', 10, 2); 27 | $table->timestamps(); 28 | }); 29 | } 30 | 31 | /** 32 | * Reverse the migrations. 33 | */ 34 | public function down(): void 35 | { 36 | Schema::dropIfExists('payments'); 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /database/migrations/2023_12_29_221111_add_soft_deletes_to_users_table.php: -------------------------------------------------------------------------------- 1 | softDeletes(); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('users', function (Blueprint $table) { 25 | $table->dropSoftDeletes(); 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2023_12_29_230613_add_channel_to_payments_table.php: -------------------------------------------------------------------------------- 1 | string('channel'); 16 | }); 17 | } 18 | 19 | /** 20 | * Reverse the migrations. 21 | */ 22 | public function down(): void 23 | { 24 | Schema::table('payments', function (Blueprint $table) { 25 | $table->dropColumn('channel'); 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2024_02_04_173648_create_notifications_table.php: -------------------------------------------------------------------------------- 1 | uuid('id')->primary(); 16 | $table->string('type'); 17 | $table->morphs('notifiable'); 18 | $table->text('data'); 19 | $table->timestamp('read_at')->nullable(); 20 | $table->timestamps(); 21 | }); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | */ 27 | public function down(): void 28 | { 29 | Schema::dropIfExists('notifications'); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /database/seeders/AdminUserSeeder.php: -------------------------------------------------------------------------------- 1 | 'Admin', 18 | 'email' => 'admin@admin.com', 19 | 'password' => bcrypt('password'), 20 | ]); 21 | 22 | $user->assignRole(Role::IS_ADMIN); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /database/seeders/BrandSeeder.php: -------------------------------------------------------------------------------- 1 | 'Adidas', 19 | 'website' => 'https://adidas.com', 20 | ], 21 | [ 22 | 'brand' => 'Nike', 23 | 'website' => 'https://nike.com', 24 | ], 25 | [ 26 | 'brand' => 'Louis Vuitton', 27 | 'website' => 'https://eu.louisvuitton.com/eng-e1/homepage', 28 | ], 29 | [ 30 | 'brand' => 'Samsung', 31 | 'website' => 'https://samsung.com', 32 | ], 33 | [ 34 | 'brand' => 'LG', 35 | 'website' => 'https://lg.com', 36 | ], 37 | [ 38 | 'brand' => 'HP', 39 | 'website' => 'https://www.hp.com/us-en/home.html', 40 | ], 41 | [ 42 | 'brand' => 'Lenovo', 43 | 'website' => 'https://www.lenovo.com/us/en/', 44 | ], 45 | ]; 46 | 47 | foreach ($brands as $brand) { 48 | Brand::create([ 49 | 'brand' => $brandName = $brand['brand'], 50 | 'slug' => Str::slug($brandName), 51 | 'website' => $brand['website'], 52 | ]); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /database/seeders/CategorySeeder.php: -------------------------------------------------------------------------------- 1 | $category, 21 | 'slug' => Str::slug($category), 22 | ]); 23 | } 24 | 25 | // Fashion subcategories 26 | $fashion = ['Men\'s Fashion', 'Women\'s Fashion', 'Kid\'s Fashion']; 27 | 28 | foreach ($fashion as $subCategory) { 29 | Category::create([ 30 | 'category' => $subCategory, 31 | 'slug' => Str::slug($subCategory), 32 | 'parent_id' => 1, 33 | ]); 34 | } 35 | 36 | // Appliances subcategories 37 | $appliances = ['Large Appliance', 'Small Appliance', 'Cooking Appliance']; 38 | 39 | foreach ($appliances as $subCategory) { 40 | Category::create([ 41 | 'category' => $subCategory, 42 | 'slug' => Str::slug($subCategory), 43 | 'parent_id' => 2, 44 | ]); 45 | } 46 | 47 | // Computing subcategories 48 | $computing = ['Laptops', 'Computers & Accessories', 'Computer Components', 'Computer Data Storage', 'Phones & Tablets']; 49 | 50 | foreach ($computing as $subCategory) { 51 | Category::create([ 52 | 'category' => $subCategory, 53 | 'slug' => Str::slug($subCategory), 54 | 'parent_id' => 3, 55 | ]); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /database/seeders/DatabaseSeeder.php: -------------------------------------------------------------------------------- 1 | call(RoleSeeder::class); 16 | $this->call(AdminUserSeeder::class); 17 | $this->call(CategorySeeder::class); 18 | $this->call(BrandSeeder::class); 19 | $this->call(ProductSeeder::class); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /database/seeders/ProductSeeder.php: -------------------------------------------------------------------------------- 1 | create() 19 | ->each(function (Product $product) { 20 | $categories = Category::all()->random(4)->pluck('id'); 21 | 22 | $product->categories()->attach($categories); 23 | }); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /database/seeders/RoleSeeder.php: -------------------------------------------------------------------------------- 1 | 'Admin']); 16 | Role::create(['name' => 'Customer']); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "type": "module", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "vite build" 7 | }, 8 | "devDependencies": { 9 | "@tailwindcss/forms": "^0.5.7", 10 | "@tailwindcss/typography": "^0.5.10", 11 | "autoprefixer": "^10.4.17", 12 | "axios": "^1.1.2", 13 | "cropperjs": "^1.6.1", 14 | "laravel-vite-plugin": "^0.8.0", 15 | "postcss": "^8.4.33", 16 | "postcss-nesting": "^12.0.2", 17 | "tailwindcss": "^3.4.1", 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 | export default { 2 | plugins: { 3 | 'postcss-import': {}, 4 | 'tailwindcss/nesting': 'postcss-nesting', 5 | tailwindcss: {}, 6 | autoprefixer: {}, 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /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/njugunamwangi/laracom/b8a097c6e5a73d7970e6acd0676f93aaaf986735/public/favicon.ico -------------------------------------------------------------------------------- /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/js/filament/forms/components/key-value.js: -------------------------------------------------------------------------------- 1 | function r({state:i}){return{state:i,rows:[],shouldUpdateRows:!0,init:function(){this.updateRows(),this.rows.length<=0?this.rows.push({key:"",value:""}):this.updateState(),this.$watch("state",(t,e)=>{let s=o=>o===null?0:Array.isArray(o)?o.length:typeof o!="object"?0:Object.keys(o).length;s(t)===0&&s(e)===0||this.updateRows()})},addRow:function(){this.rows.push({key:"",value:""}),this.updateState()},deleteRow:function(t){this.rows.splice(t,1),this.rows.length<=0&&this.addRow(),this.updateState()},reorderRows:function(t){let e=Alpine.raw(this.rows),s=e.splice(t.oldIndex,1)[0];e.splice(t.newIndex,0,s),this.rows=e,this.updateState()},updateRows:function(){if(!this.shouldUpdateRows){this.shouldUpdateRows=!0;return}let t=[];for(let[e,s]of Object.entries(this.state??{}))t.push({key:e,value:s});this.rows=t},updateState:function(){let t={};this.rows.forEach(e=>{e.key===""||e.key===null||(t[e.key]=e.value)}),this.shouldUpdateRows=!1,this.state=t}}}export{r as default}; 2 | -------------------------------------------------------------------------------- /public/js/filament/forms/components/tags-input.js: -------------------------------------------------------------------------------- 1 | function i({state:a,splitKeys:n}){return{newTag:"",state:a,createTag:function(){if(this.newTag=this.newTag.trim(),this.newTag!==""){if(this.state.includes(this.newTag)){this.newTag="";return}this.state.push(this.newTag),this.newTag=""}},deleteTag:function(t){this.state=this.state.filter(e=>e!==t)},reorderTags:function(t){let e=this.state.splice(t.oldIndex,1)[0];this.state.splice(t.newIndex,0,e),this.state=[...this.state]},input:{["x-on:blur"]:"createTag()",["x-model"]:"newTag",["x-on:keydown"](t){["Enter",...n].includes(t.key)&&(t.preventDefault(),t.stopPropagation(),this.createTag())},["x-on:paste"](){this.$nextTick(()=>{if(n.length===0){this.createTag();return}let t=n.map(e=>e.replace(/[/\-\\^$*+?.()|[\]{}]/g,"\\$&")).join("|");this.newTag.split(new RegExp(t,"g")).forEach(e=>{this.newTag=e,this.createTag()})})}}}}export{i as default}; 2 | -------------------------------------------------------------------------------- /public/js/filament/forms/components/textarea.js: -------------------------------------------------------------------------------- 1 | function t({initialHeight:e}){return{init:function(){this.render()},render:function(){this.$el.scrollHeight>0&&(this.$el.style.height=e+"rem",this.$el.style.height=this.$el.scrollHeight+"px")}}}export{t as default}; 2 | -------------------------------------------------------------------------------- /public/js/filament/tables/components/table.js: -------------------------------------------------------------------------------- 1 | function c(){return{collapsedGroups:[],isLoading:!1,selectedRecords:[],shouldCheckUniqueSelection:!0,init:function(){this.$wire.$on("deselectAllTableRecords",()=>this.deselectAllRecords()),this.$watch("selectedRecords",()=>{if(!this.shouldCheckUniqueSelection){this.shouldCheckUniqueSelection=!0;return}this.selectedRecords=[...new Set(this.selectedRecords)],this.shouldCheckUniqueSelection=!1})},mountBulkAction:function(e){this.$wire.set("selectedTableRecords",this.selectedRecords,!1),this.$wire.mountTableBulkAction(e)},toggleSelectRecordsOnPage:function(){let e=this.getRecordsOnPage();if(this.areRecordsSelected(e)){this.deselectRecords(e);return}this.selectRecords(e)},toggleSelectRecordsInGroup:async function(e){if(this.isLoading=!0,this.areRecordsSelected(this.getRecordsInGroupOnPage(e))){this.deselectRecords(await this.$wire.getGroupedSelectableTableRecordKeys(e));return}this.selectRecords(await this.$wire.getGroupedSelectableTableRecordKeys(e)),this.isLoading=!1},getRecordsInGroupOnPage:function(e){let s=[];for(let t of this.$root.getElementsByClassName("fi-ta-record-checkbox"))t.dataset.group===e&&s.push(t.value);return s},getRecordsOnPage:function(){let e=[];for(let s of this.$root.getElementsByClassName("fi-ta-record-checkbox"))e.push(s.value);return e},selectRecords:function(e){for(let s of e)this.isRecordSelected(s)||this.selectedRecords.push(s)},deselectRecords:function(e){for(let s of e){let t=this.selectedRecords.indexOf(s);t!==-1&&this.selectedRecords.splice(t,1)}},selectAllRecords:async function(){this.isLoading=!0,this.selectedRecords=await this.$wire.getAllSelectableTableRecordKeys(),this.isLoading=!1},deselectAllRecords:function(){this.selectedRecords=[]},isRecordSelected:function(e){return this.selectedRecords.includes(e)},areRecordsSelected:function(e){return e.every(s=>this.isRecordSelected(s))},toggleCollapseGroup:function(e){if(this.isGroupCollapsed(e)){this.collapsedGroups.splice(this.collapsedGroups.indexOf(e),1);return}this.collapsedGroups.push(e)},isGroupCollapsed:function(e){return this.collapsedGroups.includes(e)},resetCollapsedGroups:function(){this.collapsedGroups=[]}}}export{c as default}; 2 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /resources/css/app.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | [x-cloak] { 6 | display: none; 7 | } 8 | -------------------------------------------------------------------------------- /resources/css/filament/admin/tailwind.config.js: -------------------------------------------------------------------------------- 1 | import preset from '../../../../vendor/filament/filament/tailwind.config.preset' 2 | 3 | export default { 4 | presets: [preset], 5 | content: [ 6 | './app/Filament/**/*.php', 7 | './resources/views/filament/**/*.blade.php', 8 | './vendor/filament/**/*.blade.php', 9 | './vendor/awcodes/filament-tiptap-editor/resources/**/*.blade.php', 10 | ], 11 | } 12 | -------------------------------------------------------------------------------- /resources/css/filament/admin/theme.css: -------------------------------------------------------------------------------- 1 | @import '/vendor/filament/filament/resources/css/theme.css'; 2 | @import '/node_modules/cropperjs/dist/cropper.css'; 3 | @import '/vendor/awcodes/filament-curator/resources/css/plugin.css'; 4 | @import '/vendor/awcodes/filament-tiptap-editor/resources/css/plugin.css'; 5 | 6 | @config 'tailwind.config.js'; 7 | -------------------------------------------------------------------------------- /resources/js/app.js: -------------------------------------------------------------------------------- 1 | import './bootstrap'; 2 | import '../../vendor/masmerise/livewire-toaster/resources/js'; 3 | -------------------------------------------------------------------------------- /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 | Edit this file to define the privacy policy for your application. 4 | -------------------------------------------------------------------------------- /resources/markdown/terms.md: -------------------------------------------------------------------------------- 1 | # Terms of Service 2 | 3 | Edit this file to define the terms of service for your application. 4 | -------------------------------------------------------------------------------- /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 | @if (session('status')) 10 |
11 | {{ session('status') }} 12 |
13 | @endif 14 | 15 |
16 | @csrf 17 | 18 |
19 | 20 | 21 |
22 | 23 |
24 | 25 | 26 |
27 | 28 |
29 | 33 |
34 | 35 |
36 | @if (Route::has('password.request')) 37 | 38 | {{ __('Forgot your password?') }} 39 | 40 | @endif 41 | 42 | 43 | {{ __('Log in') }} 44 | 45 |
46 |
47 |
48 |
49 | -------------------------------------------------------------------------------- /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/components/action-message.blade.php: -------------------------------------------------------------------------------- 1 | @props(['on']) 2 | 3 |
merge(['class' => 'text-sm text-gray-600 dark:text-gray-400']) }}> 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 | 4 | 5 | -------------------------------------------------------------------------------- /resources/views/components/authentication-card-logo.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /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 dark:bg-gray-900 border-gray-300 dark:border-gray-700 text-indigo-600 shadow-sm focus:ring-indigo-500 dark:focus:ring-indigo-600 dark:focus:ring-offset-gray-800']) !!}> 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/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 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 focus:outline-none focus:bg-gray-100 dark:focus:bg-gray-800 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 dark:bg-gray-700', '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/home/cart.blade.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 |

Your Cart

6 |
7 | 8 | 9 |
10 |
11 |
12 | -------------------------------------------------------------------------------- /resources/views/components/home/checkout.blade.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 5 |
6 |

Checkout

7 |
8 | 9 | 10 | 11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /resources/views/components/home/collection.blade.php: -------------------------------------------------------------------------------- 1 |
2 |

Shop by Collection

3 |

Each season, we collaborate with world-class designers to create a collection inspired by the natural world.

4 | 5 | 19 |
-------------------------------------------------------------------------------- /resources/views/components/home/featured.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 |
7 |
8 |

9 | Level up 10 | your desk 11 |

12 |

Make your desk beautiful and organized. Post a picture to social media and watch it get more likes than life-changing announcements. Reflect on the shallow nature of existence. At least you have a really nice desk setup.

13 | Shop Workspace 14 |
15 |
16 |
17 |
-------------------------------------------------------------------------------- /resources/views/components/home/hero-section.blade.php: -------------------------------------------------------------------------------- 1 |
2 | 3 | 6 | 7 | 8 |
9 |

10 | New arrivals are here 11 |

12 |

13 | The new arrivals have, well, newly arrived. Check out the latest options from our summer small-batch release while they're still in stock. 14 |

15 | 16 | Shop New Arrivals 17 | 18 |
19 |
-------------------------------------------------------------------------------- /resources/views/components/home/new-arrival.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | New Arrivals 5 |

6 | 7 |
8 | 9 | @foreach($newArrivals as $product) 10 | 11 | @endforeach 12 | 13 | 14 |
15 |
16 |
17 | 18 | -------------------------------------------------------------------------------- /resources/views/components/home/wishlist.blade.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 5 |
6 |

Wishlist

7 |
8 | 9 | 10 | 11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /resources/views/components/input-error.blade.php: -------------------------------------------------------------------------------- 1 | @props(['for']) 2 | 3 | @error($for) 4 |

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

5 | @enderror 6 | -------------------------------------------------------------------------------- /resources/views/components/input.blade.php: -------------------------------------------------------------------------------- 1 | @props(['disabled' => false]) 2 | 3 | merge(['class' => 'border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 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-indigo-400 dark:border-indigo-600 text-sm font-medium leading-5 text-gray-900 dark:text-gray-100 focus:outline-none focus:border-indigo-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 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 hover:border-gray-300 dark:hover:border-gray-700 focus:outline-none focus:text-gray-700 dark:focus:text-gray-300 focus:border-gray-300 dark:focus:border-gray-700 transition duration-150 ease-in-out'; 7 | @endphp 8 | 9 | merge(['class' => $classes]) }}> 10 | {{ $slot }} 11 | 12 | -------------------------------------------------------------------------------- /resources/views/components/order/order-item.blade.php: -------------------------------------------------------------------------------- 1 | 2 |
3 | {{ $item->product->product }} 6 | 7 |
8 |
9 |

10 | {{ $item->product->product }} 11 |

12 |
13 |
14 |
15 |
16 |
Quantity:
17 |
{{ $item->quantity }}
18 |
19 |
20 |
Unit Price:
21 |
Kes {{ number_format($item->unit_price) }}
22 |
23 | 24 |
25 |
Sub Total:
26 |
Kes {{ number_format($item->unit_price * $item->quantity) }}
27 |
28 |
29 |
30 |
31 |
32 | -------------------------------------------------------------------------------- /resources/views/components/order/order-status.blade.php: -------------------------------------------------------------------------------- 1 | @if($status == App\Enums\OrderStatus::Pending) 2 | 4 | Order {{ $status }} 5 | 6 | @elseif($status == App\Enums\OrderStatus::Delivered) 7 | 9 | Order {{ $status }} 10 | 11 | @elseif($status == App\Enums\OrderStatus::Processing) 12 | 14 | Order {{ $status }} 15 | 16 | @elseif($status == App\Enums\OrderStatus::Cancelled) 17 | 19 | Order {{$status}} 20 | 21 | @endif 22 | -------------------------------------------------------------------------------- /resources/views/components/order/payment-status.blade.php: -------------------------------------------------------------------------------- 1 | @if($status == App\Enums\PaymentStatus::Paid) 2 | Payment Successful 3 | @elseif($status == App\Enums\PaymentStatus::Failed) 4 | Payment Failed 5 | @else 6 | Not Paid 7 | @endif 8 | -------------------------------------------------------------------------------- /resources/views/components/product-item.blade.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 | {{ $product->product }} 9 |
10 |
11 |

12 | {{ $product->product }} 13 |

14 |

{{ $product->brand->brand }}

15 |
16 |
17 | 18 |

19 | Kes {{ number_format($product->retail_price, 2) }} 20 |

21 |
22 |
23 |
24 | 25 |
26 |
27 | -------------------------------------------------------------------------------- /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-indigo-400 dark:border-indigo-600 text-left text-base font-medium text-indigo-700 dark:text-indigo-300 bg-indigo-50 dark:bg-indigo-900/50 focus:outline-none focus:text-indigo-800 dark:focus:text-indigo-200 focus:bg-indigo-100 dark:focus:bg-indigo-900 focus:border-indigo-700 dark:focus:border-indigo-300 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 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-700 hover:border-gray-300 dark:hover:border-gray-600 focus:outline-none focus:text-gray-800 dark:focus:text-gray-200 focus:bg-gray-50 dark:focus:bg-gray-700 focus:border-gray-300 dark:focus:border-gray-600 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/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/dashboard.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |

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

6 |
7 | 8 |
9 |
10 |
11 | 12 |
13 |
14 |
15 |
16 | -------------------------------------------------------------------------------- /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/home.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 |
25 | -------------------------------------------------------------------------------- /resources/views/layouts/app.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {{ $title ? $title .' | '. config('app.name', 'Laravel') : config('app.name', 'Laravel') }} 9 | 10 | 11 | 12 | 13 | 14 | 15 | @vite(['resources/css/app.css', 'resources/js/app.js']) 16 | 17 | 18 | @livewireStyles 19 | 20 | 21 |
22 | 23 | 24 | 25 | {{ $slot }} 26 | 27 | 28 | 29 |
30 | 31 | 32 | @livewireScripts 33 | 34 | 35 | -------------------------------------------------------------------------------- /resources/views/layouts/guest.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {{ config('app.name', 'Laravel') }} 9 | 10 | 11 | 12 | 13 | 14 | 15 | @vite(['resources/css/app.css', 'resources/js/app.js']) 16 | 17 | 18 | @livewireStyles 19 | 20 | 21 |
22 | {{ $slot }} 23 |
24 | 25 | @livewireScripts 26 | 27 | 28 | -------------------------------------------------------------------------------- /resources/views/livewire/add-to-cart.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 6 | Add to bag 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /resources/views/livewire/add-to-wishlist.blade.php: -------------------------------------------------------------------------------- 1 | 4 | 9 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /resources/views/livewire/cart/count.blade.php: -------------------------------------------------------------------------------- 1 |
2 | {{ ($cartCount) }} 3 |
4 | -------------------------------------------------------------------------------- /resources/views/livewire/order/count.blade.php: -------------------------------------------------------------------------------- 1 |
2 | {{ $orderCount }} 3 |
4 | -------------------------------------------------------------------------------- /resources/views/livewire/wishlist/count.blade.php: -------------------------------------------------------------------------------- 1 |
2 | {{ $wishlistCount }} 3 |
4 | -------------------------------------------------------------------------------- /resources/views/livewire/wishlist/show.blade.php: -------------------------------------------------------------------------------- 1 |
2 | 3 | @if($wishlist && $wishlist->count() > 0) 4 | @foreach($wishlist as $product) 5 | 6 | @endforeach 7 | @else 8 | You have not added any products 9 | @endif 10 | 11 | 12 |
13 | -------------------------------------------------------------------------------- /resources/views/policy.blade.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 | 6 |
7 | 8 |
9 | {!! $policy !!} 10 |
11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /resources/views/profile/show.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |

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

6 |
7 | 8 |
9 |
10 | @if (Laravel\Fortify\Features::canUpdateProfileInformation()) 11 | @livewire('profile.update-profile-information-form') 12 | 13 | 14 | @endif 15 | 16 | @if (Laravel\Fortify\Features::enabled(Laravel\Fortify\Features::updatePasswords())) 17 |
18 | @livewire('profile.update-password-form') 19 |
20 | 21 | 22 | @endif 23 | 24 | @if (Laravel\Fortify\Features::canManageTwoFactorAuthentication()) 25 |
26 | @livewire('profile.two-factor-authentication-form') 27 |
28 | 29 | 30 | @endif 31 | 32 |
33 | @livewire('profile.logout-other-browser-sessions-form') 34 |
35 | 36 | @if (Laravel\Jetstream\Jetstream::hasAccountDeletionFeatures()) 37 | 38 | 39 |
40 | @livewire('profile.delete-user-form') 41 |
42 | @endif 43 |
44 |
45 |
46 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /routes/api.php: -------------------------------------------------------------------------------- 1 | get('/user', function (Request $request) { 18 | return $request->user(); 19 | }); 20 | -------------------------------------------------------------------------------- /routes/channels.php: -------------------------------------------------------------------------------- 1 | id === (int) $id; 18 | }); 19 | -------------------------------------------------------------------------------- /routes/console.php: -------------------------------------------------------------------------------- 1 | comment(Inspiring::quote()); 19 | })->purpose('Display an inspiring quote'); 20 | -------------------------------------------------------------------------------- /routes/web.php: -------------------------------------------------------------------------------- 1 | group(function () { 29 | Route::get('/dashboard', function () { 30 | return view('dashboard'); 31 | })->name('dashboard'); 32 | }); 33 | 34 | Route::get('/cart', [SiteController::class, 'cart'])->name('cart'); 35 | Route::get('/checkout', [SiteController::class, 'checkout'])->name('checkout'); 36 | Route::get('/orders', [OrderController::class, 'index'])->name('orders'); 37 | Route::get('/order/{order:tracking_no}', [OrderController::class, 'show'])->name('order'); 38 | Route::get('/wishlist', [SiteController::class, 'wishlist'])->name('wishlist'); 39 | 40 | Route::post('/pay', [PaymentController::class, 'redirectToGateway'])->name('pay'); 41 | Route::get('/payment/callback', [PaymentController::class, 'handleGatewayCallback']); 42 | 43 | Route::get('/{product:slug}', [ProductController::class, 'show'])->name('product'); 44 | -------------------------------------------------------------------------------- /storage/app/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !public/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /storage/app/public/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/.gitignore: -------------------------------------------------------------------------------- 1 | compiled.php 2 | config.php 3 | down 4 | events.scanned.php 5 | maintenance.php 6 | routes.php 7 | routes.scanned.php 8 | schedule-* 9 | services.json 10 | -------------------------------------------------------------------------------- /storage/framework/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !data/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /storage/framework/cache/data/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/sessions/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/testing/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/views/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | import defaultTheme from 'tailwindcss/defaultTheme'; 2 | import forms from '@tailwindcss/forms'; 3 | import typography from '@tailwindcss/typography'; 4 | 5 | /** @type {import('tailwindcss').Config} */ 6 | export default { 7 | content: [ 8 | './vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php', 9 | './vendor/laravel/jetstream/**/*.blade.php', 10 | './storage/framework/views/*.php', 11 | './resources/views/**/*.blade.php', 12 | './vendor/awcodes/filament-curator/resources/**/*.blade.php', 13 | './vendor/masmerise/livewire-toaster/resources/views/*.blade.php', 14 | './vendor/awcodes/filament-curator/resources/**/*.blade.php', 15 | ], 16 | 17 | theme: { 18 | extend: { 19 | fontFamily: { 20 | sans: ['Figtree', ...defaultTheme.fontFamily.sans], 21 | }, 22 | }, 23 | }, 24 | 25 | plugins: [forms, typography], 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/ExampleTest.php: -------------------------------------------------------------------------------- 1 | get('/'); 16 | 17 | $response->assertStatus(200); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/Feature/PasswordConfirmationTest.php: -------------------------------------------------------------------------------- 1 | withPersonalTeam()->create(); 16 | 17 | $response = $this->actingAs($user)->get('/user/confirm-password'); 18 | 19 | $response->assertStatus(200); 20 | } 21 | 22 | public function test_password_can_be_confirmed(): void 23 | { 24 | $user = User::factory()->create(); 25 | 26 | $response = $this->actingAs($user)->post('/user/confirm-password', [ 27 | 'password' => 'password', 28 | ]); 29 | 30 | $response->assertRedirect(); 31 | $response->assertSessionHasNoErrors(); 32 | } 33 | 34 | public function test_password_is_not_confirmed_with_invalid_password(): void 35 | { 36 | $user = User::factory()->create(); 37 | 38 | $response = $this->actingAs($user)->post('/user/confirm-password', [ 39 | 'password' => 'wrong-password', 40 | ]); 41 | 42 | $response->assertSessionHasErrors(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /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 | 'resources/css/filament/admin/theme.css', 11 | ], 12 | refresh: [ 13 | ...refreshPaths, 14 | 'app/Livewire/**', 15 | 'app/Filament/**', 16 | ], 17 | }), 18 | ], 19 | }); 20 | --------------------------------------------------------------------------------