├── database
├── .gitignore
├── seeders
│ ├── BrandSeeder.php
│ ├── PromoCodeSeeder.php
│ ├── OrderSeeder.php
│ ├── RoleSeeder.php
│ ├── SettingSeeder.php
│ ├── DatabaseSeeder.php
│ ├── GoodSeeder.php
│ └── UserSeeder.php
├── factories
│ ├── BrandFactory.php
│ ├── TagFactory.php
│ ├── CategoryFactory.php
│ ├── PropertyFactory.php
│ ├── OrderItemFactory.php
│ ├── ReviewFactory.php
│ ├── UserAddressFactory.php
│ ├── PromoCodeFactory.php
│ ├── OrderRecipientFactory.php
│ ├── OrderFactory.php
│ ├── UserFactory.php
│ └── GoodFactory.php
└── migrations
│ ├── 2023_02_22_220210_create_tags_table.php
│ ├── 2023_02_22_220207_create_brands_table.php
│ ├── 2023_02_22_220203_create_cities_table.php
│ ├── 2023_02_22_220202_create_states_table.php
│ ├── 2023_02_22_220213_create_good_tag_table.php
│ ├── 2023_02_22_220214_create_good_user_table.php
│ ├── 2023_02_22_220211_create_properties_table.php
│ ├── 2023_02_22_220206_create_settings_table.php
│ ├── 2023_02_22_220212_create_good_property_table.php
│ ├── 2023_02_22_220216_create_cart_items_table.php
│ ├── 0001_01_01_000001_create_cache_table.php
│ ├── 2019_05_03_000003_create_subscription_items_table.php
│ ├── 2023_02_22_220205_create_user_socials_table.php
│ ├── 2023_02_22_220219_create_order_items_table.php
│ ├── 2023_02_22_220221_create_order_payments_table.php
│ ├── 2023_02_22_220201_create_countries_table.php
│ ├── 2019_05_03_000002_create_subscriptions_table.php
│ ├── 2023_02_22_220222_create_media_table.php
│ ├── 2023_02_22_220208_create_categories_table.php
│ ├── 2023_02_22_220220_create_order_recipients_table.php
│ ├── 2023_02_22_220215_create_reviews_table.php
│ ├── 2023_02_22_220217_create_promo_codes_table.php
│ ├── 2023_02_22_220204_create_user_addresses_table.php
│ ├── 2023_02_22_220218_create_orders_table.php
│ └── 2023_02_22_220209_create_goods_table.php
├── bootstrap
├── cache
│ └── .gitignore
├── providers.php
└── app.php
├── storage
├── logs
│ └── .gitignore
├── app
│ ├── public
│ │ └── .gitignore
│ └── .gitignore
├── debugbar
│ └── .gitignore
└── framework
│ ├── testing
│ └── .gitignore
│ ├── views
│ └── .gitignore
│ ├── cache
│ ├── data
│ │ └── .gitignore
│ └── .gitignore
│ ├── sessions
│ └── .gitignore
│ └── .gitignore
├── public
├── js
│ └── filament
│ │ ├── tables
│ │ └── tables.js
│ │ └── forms
│ │ └── components
│ │ ├── textarea.js
│ │ ├── tags-input.js
│ │ └── key-value.js
├── robots.txt
├── favicon.ico
├── favicon-16x16.png
├── favicon-32x32.png
├── apple-touch-icon.png
├── static
│ └── cards
│ │ ├── 1.jpeg
│ │ ├── 10.jpeg
│ │ ├── 11.jpeg
│ │ ├── 12.jpeg
│ │ ├── 13.jpeg
│ │ ├── 14.jpeg
│ │ ├── 15.jpeg
│ │ ├── 16.jpeg
│ │ ├── 17.jpeg
│ │ ├── 18.jpeg
│ │ ├── 19.jpeg
│ │ ├── 2.jpeg
│ │ ├── 20.jpeg
│ │ ├── 21.jpeg
│ │ ├── 22.jpeg
│ │ ├── 23.jpeg
│ │ ├── 24.jpeg
│ │ ├── 25.jpeg
│ │ ├── 3.jpeg
│ │ ├── 4.jpeg
│ │ ├── 5.jpeg
│ │ ├── 6.jpeg
│ │ ├── 7.jpeg
│ │ ├── 8.jpeg
│ │ └── 9.jpeg
├── android-chrome-192x192.png
├── android-chrome-512x512.png
├── vendor
│ ├── horizon
│ │ ├── img
│ │ │ └── favicon.png
│ │ └── mix-manifest.json
│ ├── log-viewer
│ │ ├── img
│ │ │ ├── bmc.png
│ │ │ ├── bmc-logo.png
│ │ │ ├── log-viewer-128.png
│ │ │ ├── log-viewer-32.png
│ │ │ └── log-viewer-64.png
│ │ ├── mix-manifest.json
│ │ └── app.js.LICENSE.txt
│ └── telescope
│ │ ├── favicon.ico
│ │ └── mix-manifest.json
├── site.webmanifest
├── index.php
└── .htaccess
├── resources
├── js
│ ├── types
│ │ ├── vite-env.d.ts
│ │ ├── global.d.ts
│ │ └── index.d.ts
│ ├── Components
│ │ ├── InputError.vue
│ │ ├── InputLabel.vue
│ │ ├── DropdownLink.vue
│ │ ├── TextInput.vue
│ │ ├── Checkbox.vue
│ │ ├── PrimaryButton.vue
│ │ ├── DangerButton.vue
│ │ ├── NavLink.vue
│ │ ├── SecondaryButton.vue
│ │ ├── ResponsiveNavLink.vue
│ │ └── Rating.vue
│ ├── composables
│ │ └── format.ts
│ ├── Layouts
│ │ └── GuestLayout.vue
│ ├── ssr.ts
│ ├── Pages
│ │ ├── Profile
│ │ │ └── Edit.vue
│ │ └── Auth
│ │ │ └── ConfirmPassword.vue
│ ├── bootstrap.ts
│ └── app.ts
├── css
│ └── app.css
└── views
│ ├── mail
│ └── user
│ │ └── password.blade.php
│ ├── filament
│ ├── forms
│ │ └── components
│ │ │ └── _rating-item.blade.php
│ ├── tables
│ │ └── components
│ │ │ └── rating-column.blade.php
│ ├── widgets
│ │ └── log-viewer-widget.blade.php
│ └── pages
│ │ ├── addresses.blade.php
│ │ └── profile.blade.php
│ └── app.blade.php
├── .prettierignore
├── tests
├── Unit
│ └── ExampleTest.php
├── Feature
│ ├── ExampleTest.php
│ └── Auth
│ │ ├── RegistrationTest.php
│ │ ├── PasswordConfirmationTest.php
│ │ ├── AuthenticationTest.php
│ │ ├── PasswordUpdateTest.php
│ │ ├── EmailVerificationTest.php
│ │ └── PasswordResetTest.php
├── TestCase.php
├── CreatesApplication.php
└── Pest.php
├── postcss.config.js
├── docker
├── general
│ ├── supervisord.conf
│ └── cron
├── dev
│ └── www.conf
└── nginx
│ └── Dockerfile
├── jsconfig.json
├── .gitattributes
├── routes
├── console.php
└── api.php
├── app
├── Filament
│ ├── Widgets
│ │ ├── HorizonWidget.php
│ │ └── LogViewerWidget.php
│ └── Resources
│ │ ├── GoodResource
│ │ └── Pages
│ │ │ ├── CreateGood.php
│ │ │ ├── ListGoods.php
│ │ │ └── EditGood.php
│ │ ├── OrderResource
│ │ └── Pages
│ │ │ ├── CreateOrder.php
│ │ │ ├── EditOrder.php
│ │ │ └── ListOrders.php
│ │ ├── ReviewResource
│ │ └── Pages
│ │ │ ├── CreateReview.php
│ │ │ ├── EditReview.php
│ │ │ └── ListReviews.php
│ │ ├── PropertyResource
│ │ └── Pages
│ │ │ ├── CreateProperty.php
│ │ │ ├── EditProperty.php
│ │ │ └── ListProperties.php
│ │ ├── PromoCodeResource
│ │ └── Pages
│ │ │ ├── CreatePromoCode.php
│ │ │ ├── EditPromoCode.php
│ │ │ └── ListPromoCodes.php
│ │ ├── TagResource
│ │ └── Pages
│ │ │ ├── CreateTag.php
│ │ │ ├── ListTags.php
│ │ │ └── EditTag.php
│ │ ├── BrandResource
│ │ └── Pages
│ │ │ ├── CreateBrand.php
│ │ │ ├── ListBrands.php
│ │ │ └── EditBrand.php
│ │ ├── SettingResource
│ │ └── Pages
│ │ │ ├── CreateSetting.php
│ │ │ ├── ListSettings.php
│ │ │ └── EditSetting.php
│ │ ├── CategoryResource
│ │ └── Pages
│ │ │ ├── CreateCategory.php
│ │ │ ├── ListCategories.php
│ │ │ └── EditCategory.php
│ │ ├── CityResource
│ │ └── Pages
│ │ │ ├── ListCities.php
│ │ │ └── ViewCity.php
│ │ ├── StateResource
│ │ └── Pages
│ │ │ ├── ListStates.php
│ │ │ └── ViewState.php
│ │ ├── CountryResource
│ │ └── Pages
│ │ │ ├── ListCountries.php
│ │ │ └── ViewCountry.php
│ │ └── UserResource
│ │ ├── Pages
│ │ ├── ListUsers.php
│ │ └── CreateUser.php
│ │ ├── Widgets
│ │ └── UsersOverview.php
│ │ └── RelationManagers
│ │ └── SocialsRelationManager.php
├── Models
│ ├── GoodTag.php
│ ├── GoodUser.php
│ ├── Tag.php
│ ├── OrderRecipient.php
│ ├── GoodProperty.php
│ ├── Setting.php
│ ├── OrderItem.php
│ ├── State.php
│ ├── City.php
│ ├── OrderPayment.php
│ ├── Property.php
│ ├── PromoCode.php
│ ├── CartItem.php
│ ├── Brand.php
│ ├── Review.php
│ ├── UserSocial.php
│ ├── UserAddress.php
│ ├── Country.php
│ └── Order.php
├── Enums
│ ├── PromoCode.php
│ ├── UserProvider.php
│ ├── UserGender.php
│ ├── UserStatus.php
│ ├── OrderPayment.php
│ ├── UserRole.php
│ ├── OrderDelivery.php
│ ├── GoodStatus.php
│ └── OrderStatus.php
├── Notifications
│ └── SendVerifyWithQueueNotification.php
├── Http
│ ├── Middleware
│ │ ├── VerifyCsrfToken.php
│ │ └── HandleInertiaRequests.php
│ ├── Controllers
│ │ ├── Controller.php
│ │ ├── Main
│ │ │ └── IndexController.php
│ │ ├── Auth
│ │ │ ├── EmailVerificationPromptController.php
│ │ │ ├── EmailVerificationNotificationController.php
│ │ │ ├── PasswordController.php
│ │ │ ├── VerifyEmailController.php
│ │ │ ├── ConfirmablePasswordController.php
│ │ │ ├── AuthenticatedSessionController.php
│ │ │ ├── PasswordResetLinkController.php
│ │ │ └── RegisteredUserController.php
│ │ └── ProfileController.php
│ ├── Resources
│ │ ├── StateResource.php
│ │ ├── CountryResource.php
│ │ ├── PropertyResource.php
│ │ ├── CityResource.php
│ │ ├── BrandResource.php
│ │ ├── CategoryResource.php
│ │ ├── CartResource.php
│ │ ├── OrderItemResource.php
│ │ ├── ReviewResource.php
│ │ ├── UserAddressResource.php
│ │ ├── OrderResource.php
│ │ └── GoodResource.php
│ └── Requests
│ │ ├── ProfileUpdateRequest.php
│ │ └── Profile
│ │ ├── ProfileUpdateRequest.php
│ │ └── AddressRequest.php
├── Console
│ └── Kernel.php
├── Providers
│ ├── AppServiceProvider.php
│ ├── EventServiceProvider.php
│ ├── HorizonServiceProvider.php
│ └── RouteServiceProvider.php
├── Mail
│ └── User
│ │ └── PasswordMail.php
└── Jobs
│ └── StoreUserJob.php
├── .prettierrc.json
├── .editorconfig
├── .dockerignore
├── artisan
├── .gitignore
├── pint.json
├── lang
└── en
│ ├── pagination.php
│ ├── auth.php
│ └── passwords.php
├── tsconfig.json
├── vite.config.js
├── phpunit.xml
├── config
├── filament.php
└── services.php
├── .env.example
└── package.json
/database/.gitignore:
--------------------------------------------------------------------------------
1 | *.sqlite*
2 |
--------------------------------------------------------------------------------
/bootstrap/cache/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/logs/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/public/js/filament/tables/tables.js:
--------------------------------------------------------------------------------
1 | (()=>{})();
2 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Disallow:
3 |
--------------------------------------------------------------------------------
/storage/app/public/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/debugbar/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/app/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !public/
3 | !.gitignore
4 |
--------------------------------------------------------------------------------
/storage/framework/testing/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/framework/views/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/framework/cache/data/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/framework/sessions/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/storage/framework/cache/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !data/
3 | !.gitignore
4 |
--------------------------------------------------------------------------------
/resources/js/types/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/resources/css/app.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fordiquez/laravel-store/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/bootstrap/providers.php:
--------------------------------------------------------------------------------
1 | toBeTrue();
5 | });
6 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {}
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/public/vendor/horizon/img/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fordiquez/laravel-store/HEAD/public/vendor/horizon/img/favicon.png
--------------------------------------------------------------------------------
/public/vendor/log-viewer/img/bmc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fordiquez/laravel-store/HEAD/public/vendor/log-viewer/img/bmc.png
--------------------------------------------------------------------------------
/public/vendor/telescope/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fordiquez/laravel-store/HEAD/public/vendor/telescope/favicon.ico
--------------------------------------------------------------------------------
/public/vendor/log-viewer/img/bmc-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fordiquez/laravel-store/HEAD/public/vendor/log-viewer/img/bmc-logo.png
--------------------------------------------------------------------------------
/public/vendor/log-viewer/img/log-viewer-128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fordiquez/laravel-store/HEAD/public/vendor/log-viewer/img/log-viewer-128.png
--------------------------------------------------------------------------------
/public/vendor/log-viewer/img/log-viewer-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fordiquez/laravel-store/HEAD/public/vendor/log-viewer/img/log-viewer-32.png
--------------------------------------------------------------------------------
/public/vendor/log-viewer/img/log-viewer-64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fordiquez/laravel-store/HEAD/public/vendor/log-viewer/img/log-viewer-64.png
--------------------------------------------------------------------------------
/docker/general/supervisord.conf:
--------------------------------------------------------------------------------
1 | [supervisord]
2 | nodaemon=true
3 |
4 | [program:cron]
5 | command=/usr/sbin/cron -l 2 -f
6 | autostart=true
7 | autorestart=true
8 |
--------------------------------------------------------------------------------
/tests/Feature/ExampleTest.php:
--------------------------------------------------------------------------------
1 | get('/');
5 |
6 | $response->assertStatus(200);
7 | });
8 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "paths": {
5 | "@/*": ["resources/js/*"]
6 | }
7 | },
8 | "exclude": ["node_modules", "public"]
9 | }
10 |
--------------------------------------------------------------------------------
/tests/TestCase.php:
--------------------------------------------------------------------------------
1 | /proc/1/fd/1 2>/proc/1/fd/2
3 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 |
3 | *.blade.php diff=html
4 | *.css diff=css
5 | *.html diff=html
6 | *.md diff=markdown
7 | *.php diff=php
8 |
9 | /.github export-ignore
10 | CHANGELOG.md export-ignore
11 | .styleci.yml export-ignore
12 |
--------------------------------------------------------------------------------
/routes/console.php:
--------------------------------------------------------------------------------
1 | comment(Inspiring::quote());
8 | })->purpose('Display an inspiring quote');
9 |
--------------------------------------------------------------------------------
/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/vendor/telescope/mix-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "/app.js": "/app.js?id=7049e92a398e816f8cd53a915eaea592",
3 | "/app-dark.css": "/app-dark.css?id=1ea407db56c5163ae29311f1f38eb7b9",
4 | "/app.css": "/app.css?id=de4c978567bfd90b38d186937dee5ccf"
5 | }
6 |
--------------------------------------------------------------------------------
/app/Filament/Widgets/HorizonWidget.php:
--------------------------------------------------------------------------------
1 |
2 | # Your account has been successfully created! Your password: {{ $password }}
3 |
4 | Log In
5 |
6 | Thanks for registration,
7 | {{ config('app.name') }}
8 |
9 |
--------------------------------------------------------------------------------
/app/Enums/UserGender.php:
--------------------------------------------------------------------------------
1 | count(22)->create();
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/resources/js/Components/InputError.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 | {{ message }}
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app/Enums/OrderPayment.php:
--------------------------------------------------------------------------------
1 | count(10)->create();
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/resources/views/filament/forms/components/_rating-item.blade.php:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | indent_size = 4
7 | indent_style = space
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
14 | [*.{yml,yaml}]
15 | indent_size = 2
16 |
17 | [docker-compose.yml]
18 | indent_size = 4
19 |
--------------------------------------------------------------------------------
/app/Filament/Resources/GoodResource/Pages/CreateGood.php:
--------------------------------------------------------------------------------
1 | count(10)->hasOrderItems(rand(1, 5))->create();
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/app/Filament/Resources/ReviewResource/Pages/CreateReview.php:
--------------------------------------------------------------------------------
1 |
2 | defineProps<{
3 | value?: string
4 | }>()
5 |
6 |
7 |
8 |
12 |
13 |
--------------------------------------------------------------------------------
/app/Notifications/SendVerifyWithQueueNotification.php:
--------------------------------------------------------------------------------
1 | handleCommand(new ArgvInput);
14 |
15 | exit($status);
16 |
--------------------------------------------------------------------------------
/app/Http/Middleware/VerifyCsrfToken.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | protected $except = [
15 | //
16 | ];
17 | }
18 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Controller.php:
--------------------------------------------------------------------------------
1 | 'boolean',
16 | ];
17 | }
18 |
--------------------------------------------------------------------------------
/public/vendor/horizon/mix-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "/app.js": "/app.js?id=b4f3f08e60211bd6948ec35e5e9de9a1",
3 | "/app-dark.css": "/app-dark.css?id=15c72df05e2b1147fa3e4b0670cfb435",
4 | "/app.css": "/app.css?id=4d6a1a7fe095eedc2cb2a4ce822ea8a5",
5 | "/img/favicon.png": "/img/favicon.png?id=1542bfe8a0010dcbee710da13cce367f",
6 | "/img/horizon.svg": "/img/horizon.svg?id=904d5b5185fefb09035384e15bfca765",
7 | "/img/sprite.svg": "/img/sprite.svg?id=afc4952b74895bdef3ab4ebe9adb746f"
8 | }
9 |
--------------------------------------------------------------------------------
/tests/CreatesApplication.php:
--------------------------------------------------------------------------------
1 | make(Kernel::class)->bootstrap();
18 |
19 | return $app;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/Filament/Resources/TagResource/Pages/CreateTag.php:
--------------------------------------------------------------------------------
1 | previousUrl ?? $this->getResource()::getUrl('index');
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/database/factories/BrandFactory.php:
--------------------------------------------------------------------------------
1 | fake()->company,
16 | 'slug' => fn (array $attributes) => str($attributes['name'])->slug(),
17 | ];
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/app/Enums/GoodStatus.php:
--------------------------------------------------------------------------------
1 | previousUrl ?? $this->getResource()::getUrl('index');
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/resources/js/composables/format.ts:
--------------------------------------------------------------------------------
1 | export function useFormat() {
2 | const formatMoney = (value: any, format = 'ua', asCurrency = true, currency = 'USD') => {
3 | return new Intl.NumberFormat(format, {
4 | style: asCurrency ? 'currency' : 'decimal',
5 | currency
6 | }).format(value)
7 | }
8 |
9 | const formatCardNumber = (cardNumber: any) => (cardNumber ? cardNumber.match(/.{1,4}/g).join(' ') : '')
10 |
11 | return { formatMoney, formatCardNumber }
12 | }
13 |
--------------------------------------------------------------------------------
/app/Filament/Resources/SettingResource/Pages/CreateSetting.php:
--------------------------------------------------------------------------------
1 | previousUrl ?? $this->getResource()::getUrl('index');
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/app/Filament/Resources/TagResource/Pages/ListTags.php:
--------------------------------------------------------------------------------
1 | previousUrl ?? $this->getResource()::getUrl('index');
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/app/Filament/Resources/CityResource/Pages/ListCities.php:
--------------------------------------------------------------------------------
1 | fake()->unique()->colorName(),
17 | 'slug' => fn (array $attributes) => Str::slug($attributes['name']),
18 | ];
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/resources/js/Components/DropdownLink.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/app/Filament/Resources/CountryResource/Pages/ListCountries.php:
--------------------------------------------------------------------------------
1 | handleRequest(Request::capture());
18 |
--------------------------------------------------------------------------------
/app/Http/Resources/StateResource.php:
--------------------------------------------------------------------------------
1 | $this->id,
16 | 'name' => $this->name,
17 |
18 | 'country' => new CountryResource($this->whenLoaded('country')),
19 | ];
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/Http/Resources/CountryResource.php:
--------------------------------------------------------------------------------
1 | $this->id,
16 | 'name' => $this->name,
17 | 'iso2' => $this->iso2,
18 |
19 | 'flag' => $this->flag,
20 | ];
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/resources/views/filament/tables/components/rating-column.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 | @for ($i = $getMinValue(); $i <= $getMaxValue(); $i++)
4 | -
7 | @include('filament.forms.components._rating-item', [
8 | 'component' => $i <= $getState() ? $getSelectedIcon() : $getIcon(),
9 | ])
10 |
11 | @endfor
12 |
13 |
14 |
--------------------------------------------------------------------------------
/resources/views/filament/widgets/log-viewer-widget.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Log Viewer
5 |
6 |
7 |
8 | {{ \Composer\InstalledVersions::getPrettyVersion('opcodesio/log-viewer') }}
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/Models/GoodProperty.php:
--------------------------------------------------------------------------------
1 | belongsTo(Property::class);
17 | }
18 |
19 | public function good(): BelongsTo
20 | {
21 | return $this->belongsTo(Good::class);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/Filament/Resources/SettingResource/Pages/ListSettings.php:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ $this->form }}
4 |
5 |
6 |
7 |
8 |
9 | Cancel
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/resources/views/filament/pages/profile.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ $this->form }}
4 |
5 |
6 |
7 |
8 |
9 | Cancel
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/routes/api.php:
--------------------------------------------------------------------------------
1 | group(function () {
7 | Route::get('countries', 'countries')->name('api.locations.countries');
8 | Route::get('{country}/states', 'states')->name('api.locations.states');
9 | Route::get('{state}/cities', 'cities')->name('api.locations.cities');
10 | Route::get('categories', 'categories')->name('api.categories');
11 | Route::post('verify-promo-code/key', 'verifyPromoCode')->name('api.verify-promo-code');
12 | });
13 |
--------------------------------------------------------------------------------
/app/Filament/Resources/GoodResource/Pages/EditGood.php:
--------------------------------------------------------------------------------
1 | rand(),
16 | 'type' => $type,
17 | 'title' => $title,
18 | 'text' => $text,
19 | ]);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/lang/en/pagination.php:
--------------------------------------------------------------------------------
1 | '« Previous',
16 | 'next' => 'Next »',
17 | ];
18 |
--------------------------------------------------------------------------------
/app/Http/Resources/PropertyResource.php:
--------------------------------------------------------------------------------
1 | $this->id,
16 | 'name' => $this->name,
17 | 'slug' => $this->slug,
18 | 'filterable' => $this->filterable,
19 | 'value' => $this->pivot->value,
20 | ];
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/Http/Resources/CityResource.php:
--------------------------------------------------------------------------------
1 | $this->id,
16 | 'name' => $this->name,
17 |
18 | 'country' => new CountryResource($this->whenLoaded('country')),
19 | 'state' => new StateResource($this->whenLoaded('state')),
20 | ];
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/database/factories/CategoryFactory.php:
--------------------------------------------------------------------------------
1 | fake()->unique()->jobTitle(),
16 | 'slug' => fn (array $attributes) => str($attributes['title'])->slug(),
17 | 'description' => fake()->sentence(rand(2, 10)),
18 | 'is_active' => true,
19 | ];
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/Filament/Resources/UserResource/Pages/ListUsers.php:
--------------------------------------------------------------------------------
1 | get('/register');
7 |
8 | $response->assertStatus(200);
9 | });
10 |
11 | test('new users can register', function () {
12 | $response = $this->post('/register', [
13 | 'name' => 'Test User',
14 | 'email' => 'test@example.com',
15 | 'password' => 'password',
16 | 'password_confirmation' => 'password',
17 | ]);
18 |
19 | $this->assertAuthenticated();
20 | $response->assertRedirect(RouteServiceProvider::HOME);
21 | });
22 |
--------------------------------------------------------------------------------
/database/seeders/RoleSeeder.php:
--------------------------------------------------------------------------------
1 | filter(fn (string $role) => $role !== UserRole::SUPER_ADMIN)
16 | ->each(fn (string $role) => Role::create(['name' => $role, 'guard_name' => 'web']));
17 |
18 | User::where('id', '>', 1)->get()->each(fn (User $user) => $user->assignRole(UserRole::CUSTOMER));
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/app/Models/OrderItem.php:
--------------------------------------------------------------------------------
1 | 'integer',
14 | 'unit_price' => 'float',
15 | ];
16 |
17 | public function order(): BelongsTo
18 | {
19 | return $this->belongsTo(Order::class);
20 | }
21 |
22 | public function good(): BelongsTo
23 | {
24 | return $this->belongsTo(Good::class);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/Models/State.php:
--------------------------------------------------------------------------------
1 | 'boolean',
15 | ];
16 |
17 | public function country(): BelongsTo
18 | {
19 | return $this->belongsTo(Country::class);
20 | }
21 |
22 | public function cities(): HasMany
23 | {
24 | return $this->hasMany(City::class);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/Filament/Resources/TagResource/Pages/EditTag.php:
--------------------------------------------------------------------------------
1 | previousUrl ?? $this->getResource()::getUrl('index');
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/Http/Resources/BrandResource.php:
--------------------------------------------------------------------------------
1 | $this->id,
16 | 'name' => $this->name,
17 | 'slug' => $this->slug,
18 | 'url' => $this->url,
19 | 'logo' => $this->logo,
20 |
21 | 'goods' => GoodResource::collection($this->whenLoaded('goods')),
22 | ];
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true,
4 | "module": "ESNext",
5 | "moduleResolution": "bundler",
6 | "jsx": "preserve",
7 | "strict": true,
8 | "isolatedModules": true,
9 | "target": "ESNext",
10 | "esModuleInterop": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "noEmit": true,
13 | "skipLibCheck": true,
14 | "paths": {
15 | "@/*": ["./resources/js/*"],
16 | "ziggy-js": ["./vendor/tightenco/ziggy"]
17 | }
18 | },
19 | "include": ["resources/js/**/*.ts", "resources/js/**/*.d.ts", "resources/js/**/*.vue"]
20 | }
21 |
--------------------------------------------------------------------------------
/app/Filament/Resources/BrandResource/Pages/EditBrand.php:
--------------------------------------------------------------------------------
1 | previousUrl ?? $this->getResource()::getUrl('index');
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/Filament/Resources/UserResource/Widgets/UsersOverview.php:
--------------------------------------------------------------------------------
1 | count()),
17 | Stat::make('Women', User::whereGender(UserGender::FEMALE)->count()),
18 | ];
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/database/factories/PropertyFactory.php:
--------------------------------------------------------------------------------
1 | Category::inRandomOrder()->value('id'),
17 | 'filterable' => fake()->boolean(77),
18 | 'name' => fake()->unique()->sentence(rand(1, 5)),
19 | 'slug' => fn (array $attributes) => str($attributes['name'])->slug(),
20 | ];
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/Filament/Resources/SettingResource/Pages/EditSetting.php:
--------------------------------------------------------------------------------
1 | previousUrl ?? $this->getResource()::getUrl('index');
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/database/factories/OrderItemFactory.php:
--------------------------------------------------------------------------------
1 | Good::inRandomOrder()->value('id'),
18 | 'order_id' => Order::inRandomOrder()->value('id'),
19 | 'quantity' => fake()->numberBetween(1, 5),
20 | 'unit_price' => fake()->numberBetween(100, 10000),
21 | ];
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/Enums/OrderStatus.php:
--------------------------------------------------------------------------------
1 | previousUrl ?? $this->getResource()::getUrl('index');
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import laravel, { refreshPaths } from 'laravel-vite-plugin'
3 | import vue from '@vitejs/plugin-vue'
4 |
5 | export default defineConfig({
6 | plugins: [
7 | laravel({
8 | input: ['resources/css/app.css', 'resources/js/app.ts'],
9 | ssr: ['resources/css/app.css', 'resources/js/app.ts'],
10 | refresh: [...refreshPaths, 'app/Livewire/**']
11 | }),
12 | vue({
13 | template: {
14 | transformAssetUrls: {
15 | base: null,
16 | includeAbsolute: false
17 | }
18 | }
19 | })
20 | ]
21 | })
22 |
--------------------------------------------------------------------------------
/app/Models/City.php:
--------------------------------------------------------------------------------
1 | 'boolean',
15 | ];
16 |
17 | public function state(): BelongsTo
18 | {
19 | return $this->belongsTo(State::class);
20 | }
21 |
22 | public function country(): HasOneThrough
23 | {
24 | return $this->hasOneThrough(Country::class, State::class, 'id', 'id', 'state_id', 'country_id');
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/Http/Requests/ProfileUpdateRequest.php:
--------------------------------------------------------------------------------
1 |
15 | */
16 | public function rules(): array
17 | {
18 | return [
19 | 'name' => ['required', 'string', 'max:255'],
20 | 'email' => ['required', 'string', 'lowercase', 'email', 'max:255', Rule::unique(User::class)->ignore($this->user()->id)],
21 | ];
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/lang/en/auth.php:
--------------------------------------------------------------------------------
1 | 'These credentials do not match our records.',
16 | 'password' => 'The provided password is incorrect.',
17 | 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.',
18 | ];
19 |
--------------------------------------------------------------------------------
/resources/js/Components/TextInput.vue:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
23 |
24 |
--------------------------------------------------------------------------------
/app/Console/Kernel.php:
--------------------------------------------------------------------------------
1 | command('inspire')->hourly();
16 | $schedule->command('cache:prune-stale-tags')->hourly();
17 | }
18 |
19 | /**
20 | * Register the commands for the application.
21 | */
22 | protected function commands(): void
23 | {
24 | $this->load(__DIR__ . '/Commands');
25 |
26 | require base_path('routes/console.php');
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/Models/OrderPayment.php:
--------------------------------------------------------------------------------
1 |
2 | import { computed } from 'vue'
3 |
4 | const emit = defineEmits(['update:checked'])
5 |
6 | const props = defineProps<{
7 | checked: boolean
8 | value?: any
9 | }>()
10 |
11 | const proxyChecked = computed({
12 | get() {
13 | return props.checked
14 | },
15 |
16 | set(val) {
17 | emit('update:checked', val)
18 | }
19 | })
20 |
21 |
22 |
23 |
29 |
30 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Main/IndexController.php:
--------------------------------------------------------------------------------
1 | subcategories)) {
19 | return to_route('goods.index', $category);
20 | }
21 |
22 | return inertia('Index/Category', [
23 | 'category' => new CategoryResource($category),
24 | 'breadcrumbs' => Category::breadcrumbs($category),
25 | ]);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/EmailVerificationPromptController.php:
--------------------------------------------------------------------------------
1 | user()->hasVerifiedEmail()
20 | ? redirect()->intended(RouteServiceProvider::HOME)
21 | : Inertia::render('Auth/VerifyEmail', ['status' => session('status')]);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/database/migrations/2023_02_22_220210_create_tags_table.php:
--------------------------------------------------------------------------------
1 | id();
16 | $table->string('name', 50);
17 | $table->string('slug', 50)->unique();
18 | $table->timestamps();
19 | });
20 | }
21 |
22 | /**
23 | * Reverse the migrations.
24 | */
25 | public function down(): void
26 | {
27 | Schema::dropIfExists('tags');
28 | }
29 | };
30 |
--------------------------------------------------------------------------------
/app/Models/Property.php:
--------------------------------------------------------------------------------
1 | 'boolean',
18 | ];
19 |
20 | public function category(): BelongsTo
21 | {
22 | return $this->belongsTo(Category::class);
23 | }
24 |
25 | public function goods(): BelongsToMany
26 | {
27 | return $this->belongsToMany(Good::class)->withPivot('value');
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/resources/js/Layouts/GuestLayout.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/EmailVerificationNotificationController.php:
--------------------------------------------------------------------------------
1 | user()->hasVerifiedEmail()) {
18 | return redirect()->intended(RouteServiceProvider::HOME);
19 | }
20 |
21 | $request->user()->sendEmailVerificationNotification();
22 |
23 | return back()->with('status', 'verification-link-sent');
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/database/seeders/SettingSeeder.php:
--------------------------------------------------------------------------------
1 | 'site', 'name' => 'Site currency', 'key' => 'currency', 'value' => '₴'],
14 | ['group' => 'delivery', 'name' => 'Courier', 'key' => 'courier', 'value' => 130],
15 | ['group' => 'delivery', 'name' => 'Meest', 'key' => 'meest', 'value' => 40],
16 | ['group' => 'delivery', 'name' => 'UkrPoshta', 'key' => 'ukrposhta', 'value' => 30],
17 | ['group' => 'delivery', 'name' => 'Nova Poshta', 'key' => 'nova_poshta', 'value' => 60],
18 | ], ['group', 'key'], ['value']);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/database/seeders/DatabaseSeeder.php:
--------------------------------------------------------------------------------
1 | call([
20 | SettingSeeder::class,
21 | LocationSeeder::class,
22 | UserSeeder::class,
23 | BrandSeeder::class,
24 | CategorySeeder::class,
25 | GoodSeeder::class,
26 | PromoCodeSeeder::class,
27 | OrderSeeder::class,
28 | ]);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/lang/en/passwords.php:
--------------------------------------------------------------------------------
1 | 'Your password has been reset!',
16 | 'sent' => 'We have emailed your password reset link!',
17 | 'throttled' => 'Please wait before retrying.',
18 | 'token' => 'This password reset token is invalid.',
19 | 'user' => "We can't find a user with that email address.",
20 | ];
21 |
--------------------------------------------------------------------------------
/database/migrations/2023_02_22_220207_create_brands_table.php:
--------------------------------------------------------------------------------
1 | id();
16 | $table->string('name', 100);
17 | $table->string('slug', 100)->unique();
18 | $table->string('url')->nullable();
19 | $table->timestamps();
20 | });
21 | }
22 |
23 | /**
24 | * Reverse the migrations.
25 | */
26 | public function down(): void
27 | {
28 | Schema::dropIfExists('brands');
29 | }
30 | };
31 |
--------------------------------------------------------------------------------
/app/Http/Resources/CategoryResource.php:
--------------------------------------------------------------------------------
1 | $this->id,
16 | 'title' => $this->title,
17 | 'slug' => $this->slug,
18 | 'description' => $this->description,
19 | 'thumbnail' => $this->thumbnail,
20 | 'is_active' => $this->is_active,
21 | 'is_navigational' => $this->is_navigational,
22 | 'subcategories' => $this->subcategories()->count() ? $this::loopCategories($this->subcategories) : null,
23 | ];
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/Http/Resources/CartResource.php:
--------------------------------------------------------------------------------
1 |
16 | */
17 | public function toArray(Request $request): array
18 | {
19 | [$goods, $cartItems] = $this->resource;
20 |
21 | return [
22 | 'count' => Cart::getCount(),
23 | 'total' => $goods->reduce(fn (?float $carry, Good $good) => $carry + $good->price * $cartItems[$good->id]['quantity']),
24 | 'items' => $cartItems,
25 | 'goods' => GoodResource::collection($goods),
26 | ];
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/public/vendor/log-viewer/app.js.LICENSE.txt:
--------------------------------------------------------------------------------
1 | /*!
2 | * The buffer module from node.js, for the browser.
3 | *
4 | * @author Feross Aboukhadijeh
5 | * @license MIT
6 | */
7 |
8 | /*! #__NO_SIDE_EFFECTS__ */
9 |
10 | /*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh */
11 |
12 | /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
13 |
14 | /**
15 | * @license
16 | * Lodash
17 | * Copyright OpenJS Foundation and other contributors
18 | * Released under MIT license
19 | * Based on Underscore.js 1.8.3
20 | * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
21 | */
22 |
--------------------------------------------------------------------------------
/app/Models/PromoCode.php:
--------------------------------------------------------------------------------
1 | \App\Enums\PromoCode::class,
27 | 'used_times' => 'integer',
28 | 'starts_at' => 'datetime',
29 | 'expires_at' => 'datetime',
30 | 'greater_than' => 'float',
31 | 'is_active' => 'boolean',
32 | 'is_public' => 'boolean',
33 | ];
34 | }
35 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/app/Models/CartItem.php:
--------------------------------------------------------------------------------
1 | 'integer',
17 | 'unit_price' => 'float',
18 | ];
19 |
20 | public function user(): BelongsTo
21 | {
22 | return $this->belongsTo(User::class);
23 | }
24 |
25 | public function good(): BelongsTo
26 | {
27 | return $this->belongsTo(Good::class);
28 | }
29 |
30 | public function unitPrice(): Attribute
31 | {
32 | return Attribute::get(fn () => $this->good->price);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/database/factories/ReviewFactory.php:
--------------------------------------------------------------------------------
1 | User::query()->inRandomOrder()->value('id'),
18 | 'good_id' => Good::query()->inRandomOrder()->value('id'),
19 | 'is_buyer' => fake()->boolean,
20 | 'content' => fake()->paragraph(10),
21 | 'advantages' => fake()->sentence(10),
22 | 'disadvantages' => fake()->sentence(10),
23 | 'rating' => fake()->numberBetween(1, 5),
24 | 'ip_address' => fake()->ipv4,
25 | ];
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/docker/nginx/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM nginx:alpine
2 |
3 | # set main params
4 | ARG BUILD_ARGUMENT_ENV=dev
5 | ENV ENV=$BUILD_ARGUMENT_ENV
6 |
7 | RUN ln -sf /dev/stdout /var/log/nginx/access.log && \
8 | ln -sf /dev/stderr /var/log/nginx/error.log && \
9 | rm -rf /etc/nginx/conf.d/*
10 |
11 | # install openssl
12 | RUN apk add --update openssl && \
13 | rm -rf /var/cache/apk/*
14 |
15 | # create folder for certificates
16 | RUN mkdir -p /etc/nginx/certificates
17 |
18 | # generate certificates
19 | # TODO: change it and make additional logic for production environment
20 | RUN openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/nginx/certificates/key.pem -out /etc/nginx/certificates/cert.pem -subj "/C=AT/ST=Vienna/L=Vienna/O=Security/OU=Development/CN=example.com"
21 |
22 | # put nginx config
23 | COPY ./$BUILD_ARGUMENT_ENV/nginx.conf /etc/nginx/conf.d/default.conf
24 |
--------------------------------------------------------------------------------
/resources/js/Components/PrimaryButton.vue:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
27 |
28 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/PasswordController.php:
--------------------------------------------------------------------------------
1 | validate([
19 | 'current_password' => ['required', 'current_password'],
20 | 'password' => ['required', Password::defaults(), 'confirmed'],
21 | ]);
22 |
23 | $request->user()->update([
24 | 'password' => Hash::make($validated['password']),
25 | ]);
26 |
27 | return back();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/Http/Resources/OrderItemResource.php:
--------------------------------------------------------------------------------
1 | $this->id,
16 | 'quantity' => $this->quantity,
17 | 'unit_price' => $this->unit_price,
18 | 'created_at' => $this->created_at,
19 | 'updated_at' => $this->updated_at,
20 |
21 | 'order_id' => $this->order_id,
22 | 'good_id' => $this->good_id,
23 |
24 | 'good' => new GoodResource($this->whenLoaded('good')),
25 | 'order' => new OrderResource($this->whenLoaded('order')),
26 | ];
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/Models/Brand.php:
--------------------------------------------------------------------------------
1 | hasMany(Good::class);
23 | }
24 |
25 | protected function logo(): Attribute
26 | {
27 | return Attribute::get(fn () => $this->hasMedia('logo') ? $this->getFirstMediaUrl('logo') : url('static/not-found.svg'));
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/Providers/AppServiceProvider.php:
--------------------------------------------------------------------------------
1 | user() && $request->user()->email === User::ADMIN_EMAIL && $request->user()->hasVerifiedEmail();
20 | });
21 | }
22 |
23 | /**
24 | * Bootstrap any application services.
25 | */
26 | public function boot(): void
27 | {
28 | JsonResource::withoutWrapping();
29 | Model::shouldBeStrict(false);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/database/seeders/GoodSeeder.php:
--------------------------------------------------------------------------------
1 | count(22)->create();
15 |
16 | $properties = PropertyFactory::new()->count(10)->create();
17 |
18 | GoodFactory::new()->count(222)
19 | ->hasReviews(rand(1, 3))
20 | ->hasAttached($tags->toQuery()->inRandomOrder()->take(rand(1, 5))->get())
21 | ->hasAttached($properties, fn () => [
22 | 'property_id' => $properties->toQuery()->inRandomOrder()->value('id'),
23 | 'value' => fake()->unique()->sentence(rand(1, 3)),
24 | ])
25 | ->create();
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/Models/Review.php:
--------------------------------------------------------------------------------
1 | 'boolean',
27 | 'rating' => 'integer',
28 | ];
29 |
30 | public function user(): BelongsTo
31 | {
32 | return $this->belongsTo(User::class);
33 | }
34 |
35 | public function good(): BelongsTo
36 | {
37 | return $this->belongsTo(Good::class);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/database/migrations/2023_02_22_220203_create_cities_table.php:
--------------------------------------------------------------------------------
1 | id();
17 | $table->foreignIdFor(State::class)->constrained()->cascadeOnUpdate()->cascadeOnDelete();
18 | $table->string('name');
19 | $table->boolean('is_active')->default(true);
20 | $table->timestamps();
21 | });
22 | }
23 |
24 | /**
25 | * Reverse the migrations.
26 | */
27 | public function down(): void
28 | {
29 | Schema::dropIfExists('cities');
30 | }
31 | };
32 |
--------------------------------------------------------------------------------
/database/migrations/2023_02_22_220202_create_states_table.php:
--------------------------------------------------------------------------------
1 | id();
17 | $table->foreignIdFor(Country::class)->constrained()->cascadeOnUpdate()->cascadeOnDelete();
18 | $table->string('name');
19 | $table->boolean('is_active')->default(true);
20 | $table->timestamps();
21 | });
22 | }
23 |
24 | /**
25 | * Reverse the migrations.
26 | */
27 | public function down(): void
28 | {
29 | Schema::dropIfExists('states');
30 | }
31 | };
32 |
--------------------------------------------------------------------------------
/app/Models/UserSocial.php:
--------------------------------------------------------------------------------
1 | UserProvider::class,
17 | ];
18 |
19 | public function user(): BelongsTo
20 | {
21 | return $this->belongsTo(User::class);
22 | }
23 |
24 | public static function getGithubUsername(string $username): array
25 | {
26 | $username = Str::wordCount($username) > 1 ? Str::of($username) : Str::of($username)->append(' user');
27 |
28 | return Arr::map($username->explode(' ', 2)->all(), fn ($value) => Str::limit($value, 50, null));
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/database/migrations/2023_02_22_220213_create_good_tag_table.php:
--------------------------------------------------------------------------------
1 | id();
18 | $table->foreignIdFor(Good::class)->constrained()->cascadeOnUpdate()->cascadeOnDelete();
19 | $table->foreignIdFor(Tag::class)->constrained()->cascadeOnUpdate()->cascadeOnDelete();
20 | $table->timestamps();
21 | });
22 | }
23 |
24 | /**
25 | * Reverse the migrations.
26 | */
27 | public function down(): void
28 | {
29 | Schema::dropIfExists('good_tag');
30 | }
31 | };
32 |
--------------------------------------------------------------------------------
/database/migrations/2023_02_22_220214_create_good_user_table.php:
--------------------------------------------------------------------------------
1 | id();
18 | $table->foreignIdFor(Good::class)->constrained()->cascadeOnUpdate()->cascadeOnDelete();
19 | $table->foreignIdFor(User::class)->constrained()->cascadeOnUpdate()->cascadeOnDelete();
20 | $table->timestamps();
21 | });
22 | }
23 |
24 | /**
25 | * Reverse the migrations.
26 | */
27 | public function down(): void
28 | {
29 | Schema::dropIfExists('good_user');
30 | }
31 | };
32 |
--------------------------------------------------------------------------------
/resources/js/Components/DangerButton.vue:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
27 |
28 |
--------------------------------------------------------------------------------
/tests/Feature/Auth/PasswordConfirmationTest.php:
--------------------------------------------------------------------------------
1 | create();
7 |
8 | $response = $this->actingAs($user)->get('/confirm-password');
9 |
10 | $response->assertStatus(200);
11 | });
12 |
13 | test('password can be confirmed', function () {
14 | $user = User::factory()->create();
15 |
16 | $response = $this->actingAs($user)->post('/confirm-password', [
17 | 'password' => 'password',
18 | ]);
19 |
20 | $response->assertRedirect();
21 | $response->assertSessionHasNoErrors();
22 | });
23 |
24 | test('password is not confirmed with invalid password', function () {
25 | $user = User::factory()->create();
26 |
27 | $response = $this->actingAs($user)->post('/confirm-password', [
28 | 'password' => 'wrong-password',
29 | ]);
30 |
31 | $response->assertSessionHasErrors();
32 | });
33 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/VerifyEmailController.php:
--------------------------------------------------------------------------------
1 | user()->hasVerifiedEmail()) {
19 | return redirect()->intended(RouteServiceProvider::HOME . '?verified=1');
20 | }
21 |
22 | if ($request->user()->markEmailAsVerified()) {
23 | event(new Verified($request->user()));
24 | }
25 |
26 | return redirect()->intended(RouteServiceProvider::HOME . '?verified=1');
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/database/migrations/2023_02_22_220211_create_properties_table.php:
--------------------------------------------------------------------------------
1 | id();
17 | $table->foreignIdFor(Category::class)->constrained()->cascadeOnUpdate()->cascadeOnDelete();
18 | $table->string('name');
19 | $table->string('slug')->unique();
20 | $table->boolean('filterable');
21 | $table->timestamps();
22 | });
23 | }
24 |
25 | /**
26 | * Reverse the migrations.
27 | */
28 | public function down(): void
29 | {
30 | Schema::dropIfExists('properties');
31 | }
32 | };
33 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/database/migrations/2023_02_22_220206_create_settings_table.php:
--------------------------------------------------------------------------------
1 | id();
16 | $table->string('group', 50);
17 | $table->string('name', 50)->nullable();
18 | $table->string('details')->nullable();
19 | $table->string('key', 50);
20 | $table->text('value');
21 | $table->timestamps();
22 |
23 | $table->unique(['group', 'key']);
24 | });
25 | }
26 |
27 | /**
28 | * Reverse the migrations.
29 | */
30 | public function down(): void
31 | {
32 | Schema::dropIfExists('settings');
33 | }
34 | };
35 |
--------------------------------------------------------------------------------
/app/Models/UserAddress.php:
--------------------------------------------------------------------------------
1 | 'boolean',
27 | ];
28 |
29 | public function country(): BelongsTo
30 | {
31 | return $this->belongsTo(Country::class);
32 | }
33 |
34 | public function state(): BelongsTo
35 | {
36 | return $this->belongsTo(State::class);
37 | }
38 |
39 | public function city(): BelongsTo
40 | {
41 | return $this->belongsTo(City::class);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/database/factories/UserAddressFactory.php:
--------------------------------------------------------------------------------
1 | first();
16 | $state = $country->has('states') ? $country->states()->inRandomOrder()->first() : null;
17 |
18 | return [
19 | 'country_id' => $country->id,
20 | 'state_id' => $state?->id,
21 | 'city_id' => $state?->has('cities') ? $state->cities()->inRandomOrder()->value('id') : null,
22 | 'street' => fake()->streetName,
23 | 'house' => fake()->buildingNumber,
24 | 'flat' => fake()->boolean ?: fake()->numberBetween(0, 100),
25 | 'postal_code' => intval(fake()->postcode),
26 | ];
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/database/migrations/2023_02_22_220212_create_good_property_table.php:
--------------------------------------------------------------------------------
1 | id();
18 | $table->foreignIdFor(Property::class)->constrained()->cascadeOnUpdate()->cascadeOnDelete();
19 | $table->foreignIdFor(Good::class)->constrained()->cascadeOnUpdate()->cascadeOnDelete();
20 | $table->string('value', 100);
21 | $table->timestamps();
22 | });
23 | }
24 |
25 | /**
26 | * Reverse the migrations.
27 | */
28 | public function down(): void
29 | {
30 | Schema::dropIfExists('good_property');
31 | }
32 | };
33 |
--------------------------------------------------------------------------------
/database/migrations/2023_02_22_220216_create_cart_items_table.php:
--------------------------------------------------------------------------------
1 | id();
18 | $table->foreignIdFor(User::class)->constrained()->cascadeOnUpdate()->cascadeOnDelete();
19 | $table->foreignIdFor(Good::class)->constrained()->cascadeOnUpdate()->cascadeOnDelete();
20 | $table->unsignedTinyInteger('quantity')->default(1);
21 | $table->timestamps();
22 | });
23 | }
24 |
25 | /**
26 | * Reverse the migrations.
27 | */
28 | public function down(): void
29 | {
30 | Schema::dropIfExists('cart_items');
31 | }
32 | };
33 |
--------------------------------------------------------------------------------
/app/Providers/EventServiceProvider.php:
--------------------------------------------------------------------------------
1 | >
16 | */
17 | protected $listen = [
18 | Registered::class => [SendEmailVerificationNotification::class],
19 | ];
20 |
21 | /**
22 | * Register any events for your application.
23 | */
24 | public function boot(): void
25 | {
26 | //
27 | }
28 |
29 | /**
30 | * Determine if events and listeners should be automatically discovered.
31 | */
32 | public function shouldDiscoverEvents(): bool
33 | {
34 | return false;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/database/migrations/0001_01_01_000001_create_cache_table.php:
--------------------------------------------------------------------------------
1 | string('key')->primary();
16 | $table->mediumText('value');
17 | $table->integer('expiration');
18 | });
19 |
20 | Schema::create('cache_locks', function (Blueprint $table) {
21 | $table->string('key')->primary();
22 | $table->string('owner');
23 | $table->integer('expiration');
24 | });
25 | }
26 |
27 | /**
28 | * Reverse the migrations.
29 | */
30 | public function down(): void
31 | {
32 | Schema::dropIfExists('cache');
33 | Schema::dropIfExists('cache_locks');
34 | }
35 | };
36 |
--------------------------------------------------------------------------------
/resources/js/Components/NavLink.vue:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/database/migrations/2019_05_03_000003_create_subscription_items_table.php:
--------------------------------------------------------------------------------
1 | id();
16 | $table->foreignId('subscription_id');
17 | $table->string('stripe_id')->unique();
18 | $table->string('stripe_product');
19 | $table->string('stripe_price');
20 | $table->integer('quantity')->nullable();
21 | $table->timestamps();
22 |
23 | $table->unique(['subscription_id', 'stripe_price']);
24 | });
25 | }
26 |
27 | /**
28 | * Reverse the migrations.
29 | */
30 | public function down(): void
31 | {
32 | Schema::dropIfExists('subscription_items');
33 | }
34 | };
35 |
--------------------------------------------------------------------------------
/resources/js/ssr.ts:
--------------------------------------------------------------------------------
1 | import { createSSRApp, h, DefineComponent } from 'vue'
2 | import { renderToString } from '@vue/server-renderer'
3 | import { createInertiaApp } from '@inertiajs/vue3'
4 | import createServer from '@inertiajs/vue3/server'
5 | import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers'
6 | import { ZiggyVue } from 'ziggy-js'
7 |
8 | const appName = import.meta.env.VITE_APP_NAME || 'brandford.'
9 |
10 | createServer((page) =>
11 | createInertiaApp({
12 | page,
13 | render: renderToString,
14 | title: (title) => `${title} - ${appName}`,
15 | resolve: (name) => resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob('./Pages/**/*.vue')),
16 | setup({ App, props, plugin }) {
17 | return createSSRApp({ render: () => h(App, props) })
18 | .use(plugin)
19 | .use(ZiggyVue, {
20 | ...page.props.ziggy,
21 | location: new URL(page.props.ziggy.location)
22 | })
23 | }
24 | })
25 | )
26 |
--------------------------------------------------------------------------------
/app/Http/Resources/ReviewResource.php:
--------------------------------------------------------------------------------
1 | $this->id,
16 | 'is_buyer' => $this->is_buyer,
17 | 'content' => $this->content,
18 | 'advantages' => $this->advantages,
19 | 'disadvantages' => $this->disadvantages,
20 | 'rating' => $this->rating,
21 | 'video_src' => $this->video_src,
22 | 'created_at' => $this->created_at->format('F d, Y'),
23 | 'updated_at' => $this->updated_at,
24 |
25 | 'user_id' => $this->user_id,
26 | 'good_id' => $this->good_id,
27 |
28 | 'username' => $this->user->full_name,
29 |
30 | 'good' => new GoodResource($this->whenLoaded('good')),
31 | ];
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/database/factories/PromoCodeFactory.php:
--------------------------------------------------------------------------------
1 | boolean ? 'fixed' : 'percentage';
16 |
17 | return [
18 | 'key' => Str::random(8),
19 | 'type' => $type,
20 | 'value' => fake()->numberBetween(1, $type === 'percentage' ? 99 : 1000),
21 | 'description' => fake()->sentence(10),
22 | 'used_times' => fake()->numberBetween(0, 1000),
23 | 'starts_at' => fake()->dateTimeThisYear(),
24 | 'expires_at' => fake()->dateTimeThisYear(),
25 | 'greater_than' => fake()->boolean ? fake()->numberBetween(0, 1000) : null,
26 | 'is_active' => fake()->boolean(77),
27 | 'is_public' => fake()->boolean(99),
28 | ];
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/app/Providers/HorizonServiceProvider.php:
--------------------------------------------------------------------------------
1 | email, [
32 | //
33 | ]);
34 | });
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/database/migrations/2023_02_22_220205_create_user_socials_table.php:
--------------------------------------------------------------------------------
1 | id();
18 | $table->foreignIdFor(User::class)->constrained()->cascadeOnUpdate()->cascadeOnDelete();
19 | $table->enum('provider', UserProvider::getValues());
20 | $table->string('provider_id');
21 | $table->string('provider_token');
22 | $table->timestamps();
23 |
24 | $table->unique(['provider', 'provider_id']);
25 | });
26 | }
27 |
28 | /**
29 | * Reverse the migrations.
30 | */
31 | public function down(): void
32 | {
33 | Schema::dropIfExists('user_socials');
34 | }
35 | };
36 |
--------------------------------------------------------------------------------
/database/migrations/2023_02_22_220219_create_order_items_table.php:
--------------------------------------------------------------------------------
1 | id();
18 | $table->foreignIdFor(Order::class)->constrained()->cascadeOnUpdate()->cascadeOnDelete();
19 | $table->foreignIdFor(Good::class)->constrained()->cascadeOnUpdate()->cascadeOnDelete();
20 | $table->unsignedTinyInteger('quantity')->default(1);
21 | $table->decimal('unit_price')->unsigned()->nullable();
22 | $table->timestamps();
23 | });
24 | }
25 |
26 | /**
27 | * Reverse the migrations.
28 | */
29 | public function down(): void
30 | {
31 | Schema::dropIfExists('order_items');
32 | }
33 | };
34 |
--------------------------------------------------------------------------------
/database/migrations/2023_02_22_220221_create_order_payments_table.php:
--------------------------------------------------------------------------------
1 | id();
18 | $table->foreignIdFor(Order::class)->constrained()->cascadeOnUpdate()->cascadeOnDelete();
19 | $table->decimal('amount')->unsigned();
20 | $table->string('status')->nullable();
21 | $table->enum('type', OrderPayment::$types);
22 | $table->uuid('session_id')->nullable();
23 | $table->timestamps();
24 | });
25 | }
26 |
27 | /**
28 | * Reverse the migrations.
29 | */
30 | public function down(): void
31 | {
32 | Schema::dropIfExists('order_payments');
33 | }
34 | };
35 |
--------------------------------------------------------------------------------
/database/seeders/UserSeeder.php:
--------------------------------------------------------------------------------
1 | has(OrderRecipientFactory::new()->count(rand(1, 3)))
18 | ->has(UserAddressFactory::new()->count(rand(1, 3)), 'addresses')
19 | ->create([
20 | 'first_name' => 'Gerald',
21 | 'last_name' => 'Ford',
22 | 'birth_date' => '2001-02-22',
23 | 'gender' => 'male',
24 | 'email' => User::ADMIN_EMAIL,
25 | 'password' => Hash::make('brandford22'),
26 | ]);
27 |
28 | $user->addAvatarMedia(config('services.multiavatar.url') . $user->getFilamentName() . '.svg?apikey=' . config('services.multiavatar.key'));
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/database/factories/OrderRecipientFactory.php:
--------------------------------------------------------------------------------
1 |
19 | */
20 | public function definition(): array
21 | {
22 | return [
23 | 'user_id' => User::inRandomOrder()->value('id'),
24 | 'title' => fake()->unique()->name,
25 | 'first_name' => fake()->firstName(),
26 | 'last_name' => fake()->lastName(),
27 | 'phone' => '+380' . $this->networkCodes[rand(0, count($this->networkCodes) - 1)] . fake()->unique()->numberBetween(1000000, 9999999),
28 | 'description' => fake()->text(50),
29 | 'is_default' => fake()->boolean,
30 | ];
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/Http/Requests/Profile/ProfileUpdateRequest.php:
--------------------------------------------------------------------------------
1 | merge([
14 | 'phone' => str($this->get('phone'))->replace(' ', '')->value(),
15 | ]);
16 | }
17 |
18 | /**
19 | * Get the validation rules that apply to the request.
20 | */
21 | public function rules(): array
22 | {
23 | return [
24 | 'first_name' => ['string', 'max:255'],
25 | 'last_name' => ['string', 'max:255'],
26 | 'email' => ['email', 'max:255', Rule::unique(User::class)->ignore($this->user()->id)],
27 | 'phone' => ['required', 'string', 'regex:/^\+\d{7,12}$/', Rule::unique(User::class)->ignore($this->user()->id)],
28 | ];
29 | }
30 |
31 | public function authorize(): bool
32 | {
33 | return boolval($this->user());
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/database/migrations/2023_02_22_220201_create_countries_table.php:
--------------------------------------------------------------------------------
1 | id();
16 | $table->string('name');
17 | $table->string('capital')->nullable();
18 | $table->char('iso2', 2)->unique();
19 | $table->char('iso3', 3)->unique();
20 | $table->char('phone_code');
21 | $table->char('currency', 3);
22 | $table->string('region')->nullable();
23 | $table->string('subregion')->nullable();
24 | $table->boolean('is_active')->default(true);
25 | $table->timestamps();
26 | });
27 | }
28 |
29 | /**
30 | * Reverse the migrations.
31 | */
32 | public function down(): void
33 | {
34 | Schema::dropIfExists('countries');
35 | }
36 | };
37 |
--------------------------------------------------------------------------------
/database/factories/OrderFactory.php:
--------------------------------------------------------------------------------
1 | fake()->unique()->uuid,
21 | 'user_id' => User::query()->inRandomOrder()->value('id'),
22 | 'user_address_id' => UserAddress::query()->inRandomOrder()->value('id'),
23 | 'delivery_method' => OrderDelivery::getRandomValue(),
24 | 'payment_method' => OrderPayment::getRandomValue(),
25 | 'goods_cost' => fake()->numberBetween(100, 10000),
26 | 'delivery_cost' => fake()->numberBetween(0, 100),
27 | 'total_cost' => fn (array $attributes) => $attributes['goods_cost'] + $attributes['delivery_cost'],
28 | 'status' => OrderStatus::getRandomValue(),
29 | ];
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/tests/Feature/Auth/AuthenticationTest.php:
--------------------------------------------------------------------------------
1 | get('/login');
8 |
9 | $response->assertStatus(200);
10 | });
11 |
12 | test('users can authenticate using the login screen', function () {
13 | $user = User::factory()->create();
14 |
15 | $response = $this->post('/login', [
16 | 'email' => $user->email,
17 | 'password' => 'password',
18 | ]);
19 |
20 | $this->assertAuthenticated();
21 | $response->assertRedirect(RouteServiceProvider::HOME);
22 | });
23 |
24 | test('users can not authenticate with invalid password', function () {
25 | $user = User::factory()->create();
26 |
27 | $this->post('/login', [
28 | 'email' => $user->email,
29 | 'password' => 'wrong-password',
30 | ]);
31 |
32 | $this->assertGuest();
33 | });
34 |
35 | test('users can logout', function () {
36 | $user = User::factory()->create();
37 |
38 | $response = $this->actingAs($user)->post('/logout');
39 |
40 | $this->assertGuest();
41 | $response->assertRedirect('/');
42 | });
43 |
--------------------------------------------------------------------------------
/app/Http/Requests/Profile/AddressRequest.php:
--------------------------------------------------------------------------------
1 | merge([
13 | 'is_main' => !$this->user()->addresses()->count(),
14 | ]);
15 | }
16 |
17 | public function rules(): array
18 | {
19 | return [
20 | 'country_id' => ['required', 'integer', Rule::exists('countries', 'id')],
21 | 'state_id' => ['nullable', 'integer', Rule::exists('states', 'id')],
22 | 'city_id' => ['nullable', 'integer', Rule::exists('cities', 'id')],
23 | 'street' => ['required', 'string', 'max:255'],
24 | 'house' => ['required', 'string', 'max:10'],
25 | 'flat' => ['nullable', 'string', 'max:10'],
26 | 'postal_code' => ['nullable', 'string', 'max:10'],
27 | 'is_main' => ['required', 'boolean'],
28 | ];
29 | }
30 |
31 | public function authorize(): bool
32 | {
33 | return boolval($this->user());
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/database/migrations/2019_05_03_000002_create_subscriptions_table.php:
--------------------------------------------------------------------------------
1 | id();
16 | $table->foreignId('user_id');
17 | $table->string('type');
18 | $table->string('stripe_id')->unique();
19 | $table->string('stripe_status');
20 | $table->string('stripe_price')->nullable();
21 | $table->integer('quantity')->nullable();
22 | $table->timestamp('trial_ends_at')->nullable();
23 | $table->timestamp('ends_at')->nullable();
24 | $table->timestamps();
25 |
26 | $table->index(['user_id', 'stripe_status']);
27 | });
28 | }
29 |
30 | /**
31 | * Reverse the migrations.
32 | */
33 | public function down(): void
34 | {
35 | Schema::dropIfExists('subscriptions');
36 | }
37 | };
38 |
--------------------------------------------------------------------------------
/database/migrations/2023_02_22_220222_create_media_table.php:
--------------------------------------------------------------------------------
1 | id();
13 |
14 | $table->morphs('model');
15 | $table->uuid()->nullable()->unique();
16 | $table->string('collection_name');
17 | $table->string('name');
18 | $table->string('file_name');
19 | $table->string('mime_type')->nullable();
20 | $table->string('disk');
21 | $table->string('conversions_disk')->nullable();
22 | $table->unsignedBigInteger('size');
23 | $table->json('manipulations');
24 | $table->json('custom_properties');
25 | $table->json('generated_conversions');
26 | $table->json('responsive_images');
27 | $table->unsignedInteger('order_column')->nullable()->index();
28 |
29 | $table->nullableTimestamps();
30 | });
31 | }
32 | };
33 |
--------------------------------------------------------------------------------
/database/migrations/2023_02_22_220208_create_categories_table.php:
--------------------------------------------------------------------------------
1 | id();
16 | $table->string('title', 100);
17 | $table->string('slug', 100)->unique();
18 | $table->string('description')->nullable();
19 | $table->boolean('is_active')->default(true);
20 | $table->boolean('is_navigational')->default(true);
21 | $table->foreignId('parent_id')->nullable()->constrained('categories')->cascadeOnUpdate()->cascadeOnDelete();
22 | $table->string('manual_url')->nullable();
23 | $table->timestamps();
24 | $table->softDeletes();
25 | });
26 | }
27 |
28 | /**
29 | * Reverse the migrations.
30 | */
31 | public function down(): void
32 | {
33 | Schema::dropIfExists('categories');
34 | }
35 | };
36 |
--------------------------------------------------------------------------------
/resources/js/Components/SecondaryButton.vue:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
34 |
35 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/app/Mail/User/PasswordMail.php:
--------------------------------------------------------------------------------
1 | password = $password;
26 | }
27 |
28 | /**
29 | * Get the message envelope.
30 | */
31 | public function envelope(): Envelope
32 | {
33 | return new Envelope(subject: 'Password Mail');
34 | }
35 |
36 | /**
37 | * Get the message content definition.
38 | */
39 | public function content(): Content
40 | {
41 | return new Content(markdown: 'mail.user.password');
42 | }
43 |
44 | /**
45 | * Get the attachments for the message.
46 | */
47 | public function attachments(): array
48 | {
49 | return [];
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/tests/Feature/Auth/PasswordUpdateTest.php:
--------------------------------------------------------------------------------
1 | create();
8 |
9 | $response = $this
10 | ->actingAs($user)
11 | ->from('/profile')
12 | ->put('/password', [
13 | 'current_password' => 'password',
14 | 'password' => 'new-password',
15 | 'password_confirmation' => 'new-password',
16 | ]);
17 |
18 | $response
19 | ->assertSessionHasNoErrors()
20 | ->assertRedirect('/profile');
21 |
22 | $this->assertTrue(Hash::check('new-password', $user->refresh()->password));
23 | });
24 |
25 | test('correct password must be provided to update password', function () {
26 | $user = User::factory()->create();
27 |
28 | $response = $this
29 | ->actingAs($user)
30 | ->from('/profile')
31 | ->put('/password', [
32 | 'current_password' => 'wrong-password',
33 | 'password' => 'new-password',
34 | 'password_confirmation' => 'new-password',
35 | ]);
36 |
37 | $response
38 | ->assertSessionHasErrors('current_password')
39 | ->assertRedirect('/profile');
40 | });
41 |
--------------------------------------------------------------------------------
/database/migrations/2023_02_22_220220_create_order_recipients_table.php:
--------------------------------------------------------------------------------
1 | id();
17 | $table->foreignIdFor(User::class)->constrained()->cascadeOnUpdate()->cascadeOnDelete();
18 | $table->string('title');
19 | $table->string('first_name', 50);
20 | $table->string('last_name', 50);
21 | $table->string('second_name', 50)->nullable();
22 | $table->string('phone', 13);
23 | $table->string('description')->nullable();
24 | $table->boolean('is_default')->default(false);
25 | $table->timestamps();
26 |
27 | $table->unique(['user_id', 'title']);
28 | });
29 | }
30 |
31 | /**
32 | * Reverse the migrations.
33 | */
34 | public function down(): void
35 | {
36 | Schema::dropIfExists('order_recipients');
37 | }
38 | };
39 |
--------------------------------------------------------------------------------
/resources/js/Components/ResponsiveNavLink.vue:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/ConfirmablePasswordController.php:
--------------------------------------------------------------------------------
1 | validate([
30 | 'email' => $request->user()->email,
31 | 'password' => $request->password])) {
32 | throw ValidationException::withMessages([
33 | 'password' => __('auth.password'),
34 | ]);
35 | }
36 |
37 | $request->session()->put('auth.password_confirmed_at', time());
38 |
39 | return redirect()->intended(RouteServiceProvider::HOME);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/config/filament.php:
--------------------------------------------------------------------------------
1 | [
18 |
19 | // 'echo' => [
20 | // 'broadcaster' => 'pusher',
21 | // 'key' => env('VITE_PUSHER_APP_KEY'),
22 | // 'cluster' => env('VITE_PUSHER_APP_CLUSTER'),
23 | // 'forceTLS' => true,
24 | // ],
25 |
26 | ],
27 |
28 | /*
29 | |--------------------------------------------------------------------------
30 | | Default Filesystem Disk
31 | |--------------------------------------------------------------------------
32 | |
33 | | This is the storage disk Filament will use to put media. You may use any
34 | | of the disks defined in the `config/filesystems.php`.
35 | |
36 | */
37 |
38 | 'default_filesystem_disk' => env('FILAMENT_FILESYSTEM_DISK', 'public'),
39 |
40 | ];
41 |
--------------------------------------------------------------------------------
/database/migrations/2023_02_22_220215_create_reviews_table.php:
--------------------------------------------------------------------------------
1 | id();
18 | $table->foreignIdFor(User::class)->constrained()->cascadeOnUpdate()->cascadeOnDelete();
19 | $table->foreignIdFor(Good::class)->constrained()->cascadeOnUpdate()->cascadeOnDelete();
20 | $table->boolean('is_buyer')->default(true);
21 | $table->text('content');
22 | $table->tinyText('advantages');
23 | $table->tinyText('disadvantages');
24 | $table->unsignedTinyInteger('rating');
25 | $table->string('video_src')->nullable();
26 | $table->ipAddress()->nullable();
27 | $table->timestamps();
28 | });
29 | }
30 |
31 | /**
32 | * Reverse the migrations.
33 | */
34 | public function down(): void
35 | {
36 | Schema::dropIfExists('reviews');
37 | }
38 | };
39 |
--------------------------------------------------------------------------------
/resources/views/app.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | {{ config('app.name', 'brandford.') }}
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | @routes
19 | @vite(['resources/js/app.ts', "resources/js/Pages/{$page['component']}.vue"])
20 | @inertiaHead
21 |
22 |
23 | @inertia
24 |
25 |
26 |
--------------------------------------------------------------------------------
/database/migrations/2023_02_22_220217_create_promo_codes_table.php:
--------------------------------------------------------------------------------
1 | id();
17 | $table->string('key', 50)->unique();
18 | $table->enum('type', PromoCode::getValues());
19 | $table->unsignedSmallInteger('value');
20 | $table->string('description');
21 | $table->integer('used_times')->default(0);
22 | $table->dateTime('starts_at')->nullable();
23 | $table->dateTime('expires_at')->nullable();
24 | $table->decimal('greater_than')->unsigned()->nullable();
25 | $table->boolean('is_active')->default(true);
26 | $table->boolean('is_public')->default(false);
27 | $table->timestamps();
28 | });
29 | }
30 |
31 | /**
32 | * Reverse the migrations.
33 | */
34 | public function down(): void
35 | {
36 | Schema::dropIfExists('promo_codes');
37 | }
38 | };
39 |
--------------------------------------------------------------------------------
/resources/js/Pages/Profile/Edit.vue:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/app/Http/Resources/UserAddressResource.php:
--------------------------------------------------------------------------------
1 | $this->id,
16 | 'is_main' => $this->is_main,
17 | 'street' => $this->street,
18 | 'house' => $this->house,
19 | 'flat' => $this->flat,
20 | 'postal_code' => $this->postal_code,
21 | 'created_at' => $this->created_at,
22 | 'updated_at' => $this->updated_at,
23 |
24 | 'country' => [
25 | 'id' => $this->country->id,
26 | 'name' => $this->country->name,
27 | 'iso2' => $this->country->iso2,
28 | ],
29 | 'state' => [
30 | 'id' => $this->state->id,
31 | 'uuid' => $this->state->uuid,
32 | 'name' => $this->state->name,
33 | ],
34 | 'city' => [
35 | 'id' => $this->city->id,
36 | 'uuid' => $this->city->uuid,
37 | 'name' => $this->city->name,
38 | ],
39 | ];
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/app/Models/Country.php:
--------------------------------------------------------------------------------
1 | 'boolean',
30 | ];
31 |
32 | protected $appends = ['flag'];
33 |
34 | public function states(): HasMany
35 | {
36 | return $this->hasMany(State::class);
37 | }
38 |
39 | public function cities(): HasManyThrough
40 | {
41 | return $this->hasManyThrough(City::class, State::class);
42 | }
43 |
44 | public function getRouteKeyName(): string
45 | {
46 | return 'iso2';
47 | }
48 |
49 | protected function flag(): Attribute
50 | {
51 | return Attribute::get(fn () => $this->getFirstMediaUrl('flag'));
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/resources/js/bootstrap.ts:
--------------------------------------------------------------------------------
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/js/types/index.d.ts:
--------------------------------------------------------------------------------
1 | import { Config } from 'ziggy-js'
2 |
3 | export interface User {
4 | id: number
5 | first_name: string
6 | last_name: string
7 | email: string
8 | email_verified_at: string
9 | avatar: string
10 | phone: string
11 | birth_date: string
12 | gender: string
13 | }
14 |
15 | export type Address = {
16 | id: number
17 | user_id: number
18 | is_main: boolean
19 | country_id: number
20 | country: Country
21 | state_id: number | null
22 | state: State
23 | city_id: number | null
24 | city: City
25 | street: string
26 | house: string
27 | flat: string | null
28 | postal_code: string | null
29 | }
30 |
31 | export type Country = {
32 | id: number
33 | name: string
34 | iso2: string
35 | }
36 |
37 | export type State = {
38 | id: number
39 | country_id: number
40 | name: string
41 | }
42 |
43 | export type City = {
44 | id: number
45 | state_id: number
46 | name: string
47 | }
48 |
49 | export type Cart = {
50 | count: number
51 | total: number
52 | items: any
53 | goods: any
54 | }
55 |
56 | export type PageProps = Record> = T & {
57 | user: User
58 | categories: any[]
59 | breadcrumbs: any[]
60 | cart: Cart
61 | notification: any
62 | ziggy: Config & { location: string }
63 | }
64 |
--------------------------------------------------------------------------------
/app/Jobs/StoreUserJob.php:
--------------------------------------------------------------------------------
1 | data = $data;
29 | }
30 |
31 | /**
32 | * Execute the job.
33 | *
34 | * @throws LimiterTimeoutException
35 | */
36 | public function handle(): void
37 | {
38 | Redis::throttle('mailtrap')->allow(2)->every(1)->then(function () {
39 | Mail::to($this->data['email'])->send(new PasswordMail($this->data['password']));
40 |
41 | $user = User::where('email', $this->data['email'])->firstOrFail();
42 | event(new Registered($user));
43 | }, function () {
44 | $this->release(2);
45 | });
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/database/factories/UserFactory.php:
--------------------------------------------------------------------------------
1 | boolean ? 'male' : 'female';
20 |
21 | return [
22 | 'first_name' => fake()->firstName($gender),
23 | 'last_name' => fake()->lastName(),
24 | 'birth_date' => fake()->dateTimeBetween('-30 years', '-14 years'),
25 | 'gender' => $gender,
26 | 'phone' => '+380' . $this->networkCodes[rand(0, count($this->networkCodes) - 1)] . fake()->unique()->numberBetween(1000000, 9999999),
27 | 'email' => fake()->unique()->safeEmail(),
28 | 'email_verified_at' => now(),
29 | 'password' => bcrypt(static::$password ??= fake()->password()),
30 | 'remember_token' => Str::random(10),
31 | ];
32 | }
33 |
34 | public function unverified(): static
35 | {
36 | return $this->state(
37 | fn (array $attributes) => [
38 | 'email_verified_at' => null,
39 | ],
40 | );
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/database/factories/GoodFactory.php:
--------------------------------------------------------------------------------
1 | fake()->unique()->numberBetween(1000000000),
20 | 'title' => fake()->unique()->text(rand(5, 20)),
21 | 'slug' => fn (array $attributes) => Str::slug($attributes['title']),
22 | 'category_id' => Category::whereDoesntHave('subcategories')->inRandomOrder()->value('id'),
23 | 'brand_id' => Brand::inRandomOrder()->value('id'),
24 | 'description' => fake()->paragraph(rand(5, 10)),
25 | 'short_description' => fake()->sentence(rand(25, 75)),
26 | 'warning_description' => fake()->sentence(rand(10, 50)),
27 | 'old_price' => fake()->boolean ? fake()->numberBetween(100, 100000) : null,
28 | 'price' => fn (array $attributes) => $attributes['old_price'] ? $attributes['old_price'] - ($attributes['old_price'] * rand(1, 80)) / 100 : rand(100, 100000),
29 | 'quantity' => fake()->numberBetween(0, 100),
30 | 'status' => GoodStatus::getRandomValue(),
31 | ];
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/resources/js/app.ts:
--------------------------------------------------------------------------------
1 | import './bootstrap'
2 | import '../css/app.css'
3 |
4 | import { createApp, DefineComponent, h } from 'vue'
5 | import { createInertiaApp } from '@inertiajs/vue3'
6 | import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers'
7 | import { ZiggyVue } from 'ziggy-js';
8 | import { library, dom } from '@fortawesome/fontawesome-svg-core'
9 | import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
10 | import { fas } from '@fortawesome/free-solid-svg-icons'
11 | import { far } from '@fortawesome/free-regular-svg-icons'
12 | import { fab } from '@fortawesome/free-brands-svg-icons'
13 | import Vue3Toasity, { type ToastContainerOptions } from 'vue3-toastify'
14 | import 'vue3-toastify/dist/index.css'
15 |
16 | library.add(fas, far, fab)
17 | dom.watch()
18 |
19 | const appName = import.meta.env.VITE_APP_NAME || 'brandford.'
20 |
21 | createInertiaApp({
22 | title: (title) => `${title} - ${appName}`,
23 | resolve: (name) => resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob('./Pages/**/*.vue')),
24 | setup({ el, App, props, plugin }) {
25 | createApp({ render: () => h(App, props) })
26 | .component('font-awesome-icon', FontAwesomeIcon)
27 | .use(plugin)
28 | .use(ZiggyVue)
29 | .use(Vue3Toasity, { autoClose: 3000 } as ToastContainerOptions)
30 | .mount(el)
31 | },
32 | progress: {
33 | color: '#6366f1'
34 | }
35 | })
36 |
--------------------------------------------------------------------------------
/app/Providers/RouteServiceProvider.php:
--------------------------------------------------------------------------------
1 | configureRateLimiting();
28 |
29 | $this->routes(function () {
30 | Route::middleware('api')
31 | ->prefix('api')
32 | ->group(base_path('routes/api.php'));
33 |
34 | Route::middleware('web')->group(base_path('routes/web.php'));
35 | });
36 | }
37 |
38 | /**
39 | * Configure the rate limiters for the application.
40 | */
41 | protected function configureRateLimiting(): void
42 | {
43 | RateLimiter::for('api', function (Request $request) {
44 | return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
45 | });
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/database/migrations/2023_02_22_220204_create_user_addresses_table.php:
--------------------------------------------------------------------------------
1 | id();
20 | $table->foreignIdFor(User::class)->constrained()->cascadeOnUpdate()->cascadeOnDelete();
21 | $table->boolean('is_main')->default(false);
22 | $table->foreignIdFor(Country::class)->constrained()->cascadeOnUpdate()->cascadeOnDelete();
23 | $table->foreignIdFor(State::class)->nullable()->constrained()->cascadeOnUpdate()->cascadeOnDelete();
24 | $table->foreignIdFor(City::class)->nullable()->constrained()->cascadeOnUpdate()->cascadeOnDelete();
25 | $table->string('street');
26 | $table->string('house', 10);
27 | $table->string('flat', 10)->nullable();
28 | $table->string('postal_code', 10)->nullable();
29 | $table->timestamps();
30 | });
31 | }
32 |
33 | /**
34 | * Reverse the migrations.
35 | */
36 | public function down(): void
37 | {
38 | Schema::dropIfExists('user_addresses');
39 | }
40 | };
41 |
--------------------------------------------------------------------------------
/app/Filament/Resources/StateResource/Pages/ViewState.php:
--------------------------------------------------------------------------------
1 | schema([
19 | Components\Section::make()->schema([
20 | Components\TextEntry::make('id')->label('ID'),
21 | Components\TextEntry::make('name'),
22 | Components\TextEntry::make('country.name')
23 | ->url(fn (State $record) => route('filament.admin.resources.countries.view', $record->country->iso2), true)
24 | ->badge()
25 | ->columnSpanFull(),
26 | Components\IconEntry::make('is_active')->boolean(),
27 | Components\TextEntry::make('cities_count')->default($this->record->cities()->count())->label('Cities')->badge(),
28 | Components\TextEntry::make('created_at')->label('Created Date')->dateTime(),
29 | Components\TextEntry::make('updated_at')->label('Last Modified Date')->dateTime(),
30 | ])->columns(),
31 | ]);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/Http/Middleware/HandleInertiaRequests.php:
--------------------------------------------------------------------------------
1 |
34 | */
35 | public function share(Request $request): array
36 | {
37 | return [
38 | ...parent::share($request),
39 | 'user' => $request->user(),
40 | 'ziggy' => fn () => [
41 | ...(new Ziggy)->toArray(),
42 | 'location' => $request->url(),
43 | ],
44 | 'categories' => $request->routeIs('livewire.message') ?: CategoryResource::collection(Category::whereParentId(null)->get()),
45 | 'cart' => new CartResource(Cart::getGoodsAndCartItems()),
46 | 'notification' => $request->session()->get('notification'),
47 | ];
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/app/Http/Resources/OrderResource.php:
--------------------------------------------------------------------------------
1 | $this->id,
19 | 'uuid' => $this->uuid,
20 | 'delivery_method' => OrderDelivery::fromValue($this->delivery_method)->description,
21 | 'payment_method' => OrderPayment::fromValue($this->payment_method)->description,
22 | 'goods_cost' => $this->goods_cost,
23 | 'delivery_cost' => $this->delivery_cost,
24 | 'total_cost' => $this->total_cost,
25 | 'status' => OrderStatus::fromValue($this->status)->description,
26 | 'status_history' => $this->status_history,
27 | 'created_at' => $this->created_at->format('d.m.y h:i:s'),
28 | 'updated_at' => $this->updated_at->format('d.m.y h:i:s'),
29 |
30 | 'user_id' => $this->user_id,
31 | 'user_address_id' => $this->user_address_id,
32 | 'promo_code_id' => $this->promo_code_id,
33 |
34 | 'userAddress' => new UserAddressResource($this->whenLoaded('userAddress')),
35 | 'orderItems' => OrderItemResource::collection($this->whenLoaded('orderItems')),
36 | ];
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/app/Filament/Resources/CityResource/Pages/ViewCity.php:
--------------------------------------------------------------------------------
1 | schema([
19 | Components\Section::make()->schema([
20 | Components\TextEntry::make('id')->label('ID'),
21 | Components\TextEntry::make('name'),
22 | Components\TextEntry::make('country.name')
23 | ->url(fn (City $record) => route('filament.admin.resources.countries.view', $record->country->iso2), true)
24 | ->badge(),
25 | Components\TextEntry::make('state.name')
26 | ->url(fn (City $record) => route('filament.admin.resources.states.view', $record->state_id), true)
27 | ->badge(),
28 | Components\IconEntry::make('is_active')->boolean()->columnSpanFull(),
29 | Components\TextEntry::make('created_at')->label('Created Date')->dateTime(),
30 | Components\TextEntry::make('updated_at')->label('Last Modified Date')->dateTime(),
31 | ])->columns(),
32 | ]);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/app/Models/Order.php:
--------------------------------------------------------------------------------
1 | OrderDelivery::class,
32 | 'payment_method' => \App\Enums\OrderPayment::class,
33 | 'goods_cost' => 'float',
34 | 'delivery_cost' => 'float',
35 | 'total_cost' => 'float',
36 | 'status' => OrderStatus::class,
37 | ];
38 |
39 | public function user(): BelongsTo
40 | {
41 | return $this->belongsTo(User::class);
42 | }
43 |
44 | public function userAddress(): BelongsTo
45 | {
46 | return $this->belongsTo(UserAddress::class);
47 | }
48 |
49 | public function promoCode(): BelongsTo
50 | {
51 | return $this->belongsTo(PromoCode::class);
52 | }
53 |
54 | public function orderItems(): HasMany
55 | {
56 | return $this->hasMany(OrderItem::class);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/AuthenticatedSessionController.php:
--------------------------------------------------------------------------------
1 | Route::has('password.request'),
25 | 'status' => session('status'),
26 | ]);
27 | }
28 |
29 | /**
30 | * Handle an incoming authentication request.
31 | */
32 | public function store(LoginRequest $request): RedirectResponse
33 | {
34 | $request->authenticate();
35 |
36 | $request->session()->regenerate();
37 |
38 | Cart::saveCookieCartItems();
39 |
40 | return redirect()->intended(RouteServiceProvider::HOME);
41 | }
42 |
43 | /**
44 | * Destroy an authenticated session.
45 | */
46 | public function destroy(Request $request): RedirectResponse
47 | {
48 | Auth::guard('web')->logout();
49 |
50 | $request->session()->invalidate();
51 |
52 | $request->session()->regenerateToken();
53 |
54 | return to_route('index.dashboard');
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/PasswordResetLinkController.php:
--------------------------------------------------------------------------------
1 | session('status'),
22 | ]);
23 | }
24 |
25 | /**
26 | * Handle an incoming password reset link request.
27 | *
28 | * @throws ValidationException
29 | */
30 | public function store(Request $request): RedirectResponse
31 | {
32 | $request->validate([
33 | 'email' => 'required|email',
34 | ]);
35 |
36 | // We will send the password reset link to this user. Once we have attempted
37 | // to send the link, we will examine the response then see the message we
38 | // need to show to the user. Finally, we'll send out a proper response.
39 | $status = Password::sendResetLink($request->only('email'));
40 |
41 | if ($status == Password::RESET_LINK_SENT) {
42 | return back()->with('status', __($status));
43 | }
44 |
45 | throw ValidationException::withMessages([
46 | 'email' => [trans($status)],
47 | ]);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/tests/Feature/Auth/EmailVerificationTest.php:
--------------------------------------------------------------------------------
1 | create([
11 | 'email_verified_at' => null,
12 | ]);
13 |
14 | $response = $this->actingAs($user)->get('/verify-email');
15 |
16 | $response->assertStatus(200);
17 | });
18 |
19 | test('email can be verified', function () {
20 | $user = User::factory()->create([
21 | 'email_verified_at' => null,
22 | ]);
23 |
24 | Event::fake();
25 |
26 | $verificationUrl = URL::temporarySignedRoute(
27 | 'verification.verify',
28 | now()->addMinutes(60),
29 | ['id' => $user->id, 'hash' => sha1($user->email)]
30 | );
31 |
32 | $response = $this->actingAs($user)->get($verificationUrl);
33 |
34 | Event::assertDispatched(Verified::class);
35 | expect($user->fresh()->hasVerifiedEmail())->toBeTrue();
36 | $response->assertRedirect(RouteServiceProvider::HOME . '?verified=1');
37 | });
38 |
39 | test('email is not verified with invalid hash', function () {
40 | $user = User::factory()->create([
41 | 'email_verified_at' => null,
42 | ]);
43 |
44 | $verificationUrl = URL::temporarySignedRoute(
45 | 'verification.verify',
46 | now()->addMinutes(60),
47 | ['id' => $user->id, 'hash' => sha1('wrong-email')]
48 | );
49 |
50 | $this->actingAs($user)->get($verificationUrl);
51 |
52 | expect($user->fresh()->hasVerifiedEmail())->toBeFalse();
53 | });
54 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | CONTAINER_NAME=store
2 | APP_NAME="brandford."
3 | APP_ENV=local
4 | APP_KEY=
5 | APP_DEBUG=true
6 | APP_URL=http://laravel-store.test
7 |
8 | LOG_CHANNEL=stack
9 | LOG_DEPRECATIONS_CHANNEL=null
10 | LOG_LEVEL=debug
11 |
12 | DB_CONNECTION=sqlite
13 | #DB_HOST=mysql
14 | #DB_PORT=3306
15 | #DB_DATABASE=brandford
16 | #DB_USERNAME=root
17 | #DB_PASSWORD=root
18 |
19 | BROADCAST_DRIVER=log
20 | CACHE_DRIVER=database
21 | FILESYSTEM_DISK=public
22 | QUEUE_CONNECTION=database
23 | SESSION_DRIVER=database
24 | SESSION_LIFETIME=120
25 |
26 | MEMCACHED_HOST=127.0.0.1
27 |
28 | REDIS_HOST=redis
29 | REDIS_PASSWORD=null
30 | REDIS_PORT=6379
31 |
32 | MAIL_MAILER=smtp
33 | MAIL_HOST=sandbox.smtp.mailtrap.io
34 | MAIL_PORT=2525
35 | MAIL_USERNAME=
36 | MAIL_PASSWORD=
37 | MAIL_ENCRYPTION=tls
38 | MAIL_FROM_ADDRESS="no-reply@brandford.com"
39 | MAIL_FROM_NAME="${APP_NAME}"
40 |
41 | AWS_ACCESS_KEY_ID=
42 | AWS_SECRET_ACCESS_KEY=
43 | AWS_DEFAULT_REGION=us-east-1
44 | AWS_BUCKET=
45 | AWS_USE_PATH_STYLE_ENDPOINT=false
46 |
47 | PUSHER_APP_ID=
48 | PUSHER_APP_KEY=
49 | PUSHER_APP_SECRET=
50 | PUSHER_HOST=
51 | PUSHER_PORT=443
52 | PUSHER_SCHEME=https
53 | PUSHER_APP_CLUSTER=mt1
54 |
55 | VITE_APP_NAME="${APP_NAME}"
56 | VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
57 | VITE_PUSHER_HOST="${PUSHER_HOST}"
58 | VITE_PUSHER_PORT="${PUSHER_PORT}"
59 | VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
60 | VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
61 |
62 | MULTIAVATAR_API_KEY=
63 |
64 | CSC_API_URL=https://api.countrystatecity.in/v1/countries/
65 | CSC_API_KEY=
66 |
67 | STRIPE_KEY=
68 | STRIPE_SECRET=
69 | STRIPE_WEBHOOK_SECRET=
70 |
71 | GITHUB_CLIENT_ID=
72 | GITHUB_CLIENT_SECRET=
73 |
74 | GOOGLE_CLIENT_ID=
75 | GOOGLE_CLIENT_SECRET=
76 |
--------------------------------------------------------------------------------
/app/Filament/Resources/UserResource/RelationManagers/SocialsRelationManager.php:
--------------------------------------------------------------------------------
1 | schema([
21 | Forms\Components\Section::make()->schema([
22 | Forms\Components\TextInput::make('provider'),
23 | Forms\Components\TextInput::make('provider_id'),
24 | Forms\Components\TextInput::make('provider_token'),
25 | ]),
26 | ]);
27 | }
28 |
29 | /**
30 | * @throws \Exception
31 | */
32 | public function table(Table $table): Table
33 | {
34 | return $table
35 | ->columns([
36 | Tables\Columns\TextColumn::make('id')->label('ID')->sortable(),
37 | Tables\Columns\TextColumn::make('provider')->sortable()->searchable(),
38 | Tables\Columns\TextColumn::make('provider_id')->searchable(),
39 | Tables\Columns\TextColumn::make('provider_token')->searchable()->toggleable()->limit(50),
40 | ])
41 | ->filters([
42 | //
43 | ])
44 | ->actions([
45 | Tables\Actions\ViewAction::make(),
46 | ]);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/bootstrap/app.php:
--------------------------------------------------------------------------------
1 | withProviders([
17 | AdminPanelProvider::class,
18 | HorizonServiceProvider::class,
19 | RouteServiceProvider::class,
20 | TelescopeServiceProvider::class,
21 | ])
22 | ->withRouting(
23 | web: __DIR__ . '/../routes/web.php',
24 | api: __DIR__ . '/../routes/api.php',
25 | commands: __DIR__ . '/../routes/console.php',
26 | // channels: __DIR__.'/../routes/channels.php',
27 | health: '/up',
28 | )
29 | ->withMiddleware(function (Middleware $middleware) {
30 | $middleware->web(append: [
31 | HandleInertiaRequests::class,
32 | AddLinkHeadersForPreloadedAssets::class,
33 | ]);
34 |
35 | $middleware->redirectGuestsTo(fn () => route('login'));
36 | $middleware->redirectUsersTo('/');
37 |
38 | $middleware->api('throttle:60,1');
39 |
40 | $middleware->replaceInGroup('web', ValidateCsrfToken::class, VerifyCsrfToken::class);
41 | })
42 | ->withExceptions(function (Exceptions $exceptions) {
43 | //
44 | })->create();
45 |
--------------------------------------------------------------------------------
/database/migrations/2023_02_22_220218_create_orders_table.php:
--------------------------------------------------------------------------------
1 | id();
22 | $table->uuid()->unique();
23 | $table->foreignIdFor(User::class)->constrained()->cascadeOnUpdate()->cascadeOnDelete();
24 | $table->foreignIdFor(UserAddress::class)->constrained()->cascadeOnUpdate()->cascadeOnDelete();
25 | $table->foreignIdFor(PromoCode::class)->nullable()->constrained()->cascadeOnUpdate()->cascadeOnDelete();
26 | $table->enum('delivery_method', OrderDelivery::getValues());
27 | $table->enum('payment_method', OrderPayment::getValues());
28 | $table->decimal('goods_cost')->unsigned();
29 | $table->decimal('delivery_cost')->unsigned()->nullable()->default(0);
30 | $table->decimal('total_cost')->unsigned();
31 | $table->enum('status', OrderStatus::getValues())->default(OrderStatus::UNPAID);
32 | $table->json('status_history')->nullable();
33 | $table->timestamps();
34 | });
35 | }
36 |
37 | /**
38 | * Reverse the migrations.
39 | */
40 | public function down(): void
41 | {
42 | Schema::dropIfExists('orders');
43 | }
44 | };
45 |
--------------------------------------------------------------------------------
/resources/js/Components/Rating.vue:
--------------------------------------------------------------------------------
1 |
28 |
29 |
30 |
31 |
32 | -
39 |
40 |
41 | {{ rateText[star - 1] }}
42 |
43 |
44 |
45 |
46 |
47 |
48 |
57 |
--------------------------------------------------------------------------------
/resources/js/Pages/Auth/ConfirmPassword.vue:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | This is a secure area of the application. Please confirm your password before continuing.
26 |
27 |
28 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/config/services.php:
--------------------------------------------------------------------------------
1 | [
17 | 'domain' => env('MAILGUN_DOMAIN'),
18 | 'secret' => env('MAILGUN_SECRET'),
19 | 'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'),
20 | 'scheme' => 'https',
21 | ],
22 |
23 | 'postmark' => [
24 | 'token' => env('POSTMARK_TOKEN'),
25 | ],
26 |
27 | 'ses' => [
28 | 'key' => env('AWS_ACCESS_KEY_ID'),
29 | 'secret' => env('AWS_SECRET_ACCESS_KEY'),
30 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
31 | ],
32 |
33 | 'multiavatar' => [
34 | 'url' => 'https://api.multiavatar.com/',
35 | 'key' => env('MULTIAVATAR_API_KEY'),
36 | ],
37 |
38 | 'csc' => [
39 | 'url' => env('CSC_API_URL'),
40 | 'key' => env('CSC_API_KEY'),
41 | ],
42 |
43 | 'github' => [
44 | 'client_id' => env('GITHUB_CLIENT_ID'),
45 | 'client_secret' => env('GITHUB_CLIENT_SECRET'),
46 | 'redirect' => '/social/github/callback',
47 | ],
48 |
49 | 'google' => [
50 | 'client_id' => env('GOOGLE_CLIENT_ID'),
51 | 'client_secret' => env('GOOGLE_CLIENT_SECRET'),
52 | 'redirect' => '/social/google/callback',
53 | ],
54 | ];
55 |
--------------------------------------------------------------------------------
/tests/Pest.php:
--------------------------------------------------------------------------------
1 | in('Feature');
18 |
19 | /*
20 | |--------------------------------------------------------------------------
21 | | Expectations
22 | |--------------------------------------------------------------------------
23 | |
24 | | When you're writing tests, you often need to check that values meet certain conditions. The
25 | | "expect()" function gives you access to a set of "expectations" methods that you can use
26 | | to assert different things. Of course, you may extend the Expectation API at any time.
27 | |
28 | */
29 |
30 | expect()->extend('toBeOne', function () {
31 | return $this->toBe(1);
32 | });
33 |
34 | /*
35 | |--------------------------------------------------------------------------
36 | | Functions
37 | |--------------------------------------------------------------------------
38 | |
39 | | While Pest is very powerful out-of-the-box, you may have some testing code specific to your
40 | | project that you don't want to repeat in every file. Here you can also expose helpers as
41 | | global functions to help you to reduce the number of lines of code in your test files.
42 | |
43 | */
44 |
45 | function something()
46 | {
47 | // ..
48 | }
49 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/RegisteredUserController.php:
--------------------------------------------------------------------------------
1 | validate([
37 | 'first_name' => ['required', 'string', 'max:255'],
38 | 'last_name' => ['required', 'string', 'max:255'],
39 | 'birth_date' => ['required', 'date'],
40 | 'gender' => ['required', 'string'],
41 | 'email' => ['required', 'string', 'email', 'max:255', Rule::unique('users')],
42 | 'password' => ['required', 'confirmed', Rules\Password::defaults()],
43 | ]);
44 |
45 | $data['password'] = Hash::make($data['password']);
46 |
47 | $user = User::create($data);
48 |
49 | event(new Registered($user));
50 |
51 | Auth::login($user);
52 |
53 | return redirect(RouteServiceProvider::HOME);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/app/Http/Controllers/ProfileController.php:
--------------------------------------------------------------------------------
1 | $request->user() instanceof MustVerifyEmail,
23 | 'status' => session('status'),
24 | ]);
25 | }
26 |
27 | /**
28 | * Update the user's profile information.
29 | */
30 | public function update(ProfileUpdateRequest $request): RedirectResponse
31 | {
32 | $request->user()->fill($request->validated());
33 |
34 | if ($request->user()->isDirty('email')) {
35 | $request->user()->email_verified_at = null;
36 | }
37 |
38 | $request->user()->save();
39 |
40 | return Redirect::route('profile.edit');
41 | }
42 |
43 | /**
44 | * Delete the user's account.
45 | */
46 | public function destroy(Request $request): RedirectResponse
47 | {
48 | $request->validate([
49 | 'password' => ['required', 'current_password'],
50 | ]);
51 |
52 | $user = $request->user();
53 |
54 | Auth::logout();
55 |
56 | $user->delete();
57 |
58 | $request->session()->invalidate();
59 | $request->session()->regenerateToken();
60 |
61 | return Redirect::to('/');
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/app/Filament/Resources/UserResource/Pages/CreateUser.php:
--------------------------------------------------------------------------------
1 | previousUrl ?? $this->getResource()::getUrl('index');
19 | }
20 |
21 | public Collection $permissions;
22 |
23 | protected function mutateFormDataBeforeCreate(array $data): array
24 | {
25 | $nonPermissionsFilter = [
26 | 'first_name',
27 | 'last_name',
28 | 'birth_date',
29 | 'gender',
30 | 'status',
31 | 'email',
32 | 'email_verified_at',
33 | 'phone',
34 | 'password',
35 | ];
36 |
37 | $this->permissions = collect($data)->filter(function ($permission, $key) use ($nonPermissionsFilter) {
38 | return !in_array($key, $nonPermissionsFilter) && Str::contains($key, '_');
39 | })->keys();
40 |
41 | return Arr::only($data, $nonPermissionsFilter);
42 | }
43 |
44 | protected function afterCreate(): void
45 | {
46 | $permissionModels = collect();
47 | $this->permissions->each(function ($permission) use ($permissionModels) {
48 | $permissionModels->push(Permission::firstOrCreate(
49 | ['name' => $permission],
50 | ));
51 | });
52 |
53 | $this->record->syncPermissions($permissionModels);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/database/migrations/2023_02_22_220209_create_goods_table.php:
--------------------------------------------------------------------------------
1 | id();
19 | $table->bigInteger('vendor_code')->unique();
20 | $table->string('title');
21 | $table->string('slug')->unique();
22 | $table->foreignIdFor(Category::class)->constrained()->cascadeOnUpdate()->cascadeOnDelete();
23 | $table->foreignIdFor(Brand::class)->nullable()->constrained()->cascadeOnUpdate()->nullOnDelete();
24 | $table->text('description')->nullable();
25 | $table->text('short_description')->nullable();
26 | $table->text('warning_description')->nullable();
27 | $table->decimal('old_price')->unsigned()->nullable();
28 | $table->decimal('price')->unsigned();
29 | $table->unsignedInteger('quantity')->default(0);
30 | $table->enum('status', GoodStatus::getValues());
31 | $table->json('options')->nullable();
32 | $table->timestamps();
33 |
34 | $table->softDeletes();
35 | if (config('database.default') !== 'sqlite') {
36 | $table->fullText(['title', 'description']);
37 | }
38 | });
39 | }
40 |
41 | /**
42 | * Reverse the migrations.
43 | */
44 | public function down(): void
45 | {
46 | Schema::dropIfExists('goods');
47 | }
48 | };
49 |
--------------------------------------------------------------------------------
/tests/Feature/Auth/PasswordResetTest.php:
--------------------------------------------------------------------------------
1 | get('/forgot-password');
9 |
10 | $response->assertStatus(200);
11 | });
12 |
13 | test('reset password link can be requested', function () {
14 | Notification::fake();
15 |
16 | $user = User::factory()->create();
17 |
18 | $this->post('/forgot-password', ['email' => $user->email]);
19 |
20 | Notification::assertSentTo($user, ResetPassword::class);
21 | });
22 |
23 | test('reset password screen can be rendered', function () {
24 | Notification::fake();
25 |
26 | $user = User::factory()->create();
27 |
28 | $this->post('/forgot-password', ['email' => $user->email]);
29 |
30 | Notification::assertSentTo($user, ResetPassword::class, function ($notification) {
31 | $response = $this->get('/reset-password/' . $notification->token);
32 |
33 | $response->assertStatus(200);
34 |
35 | return true;
36 | });
37 | });
38 |
39 | test('password can be reset with valid token', function () {
40 | Notification::fake();
41 |
42 | $user = User::factory()->create();
43 |
44 | $this->post('/forgot-password', ['email' => $user->email]);
45 |
46 | Notification::assertSentTo($user, ResetPassword::class, function ($notification) use ($user) {
47 | $response = $this->post('/reset-password', [
48 | 'token' => $notification->token,
49 | 'email' => $user->email,
50 | 'password' => 'password',
51 | 'password_confirmation' => 'password',
52 | ]);
53 |
54 | $response->assertSessionHasNoErrors();
55 |
56 | return true;
57 | });
58 | });
59 |
--------------------------------------------------------------------------------
/app/Filament/Resources/CountryResource/Pages/ViewCountry.php:
--------------------------------------------------------------------------------
1 | schema([
18 | Components\Section::make()->schema([
19 | Components\TextEntry::make('id')->label('ID'),
20 | Components\SpatieMediaLibraryImageEntry::make('flag')->collection('flag')->circular()->size(48),
21 | Components\TextEntry::make('name'),
22 | Components\TextEntry::make('capital'),
23 | Components\TextEntry::make('iso2'),
24 | Components\TextEntry::make('iso3'),
25 | Components\TextEntry::make('region'),
26 | Components\TextEntry::make('subregion'),
27 | Components\TextEntry::make('currency'),
28 | Components\IconEntry::make('is_active')->boolean(),
29 | Components\TextEntry::make('states_count')->default($this->record->states()->count())->label('States')->badge(),
30 | Components\TextEntry::make('cities_count')->default($this->record->cities()->count())->label('Cities')->badge(),
31 | Components\TextEntry::make('created_at')->label('Created Date')->dateTime(),
32 | Components\TextEntry::make('updated_at')->label('Last Modified Date')->dateTime(),
33 | ])->columns(),
34 | ]);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/Http/Resources/GoodResource.php:
--------------------------------------------------------------------------------
1 | $this->id,
16 | 'vendor_code' => $this->vendor_code,
17 | 'title' => $this->title,
18 | 'slug' => $this->slug,
19 | 'description' => $this->description,
20 | 'short_description' => $this->short_description,
21 | 'warning_description' => $this->warning_description,
22 | 'old_price' => $this->old_price,
23 | 'price' => $this->price,
24 | 'quantity' => $this->quantity,
25 | 'status' => $this->status,
26 | 'created_at' => $this->created_at,
27 | 'updated_at' => $this->updated_at,
28 |
29 | 'preview' => $this->preview,
30 | 'slides' => $this->getMedia('goods')
31 | ->pluck('file_name', 'id')
32 | ->map(fn (string $item, $key) => url("/storage/$key/$item"))
33 | ->all(),
34 |
35 | 'category_id' => $this->category_id,
36 | 'brand_id' => $this->brand_id,
37 |
38 | 'rating' => $this->reviews->avg('rating'),
39 | 'reviews_count' => $this->reviews->count(),
40 |
41 | 'brand' => new BrandResource($this->whenLoaded('brand')),
42 | 'category' => new CategoryResource($this->whenLoaded('category')),
43 | 'properties' => PropertyResource::collection($this->whenLoaded('properties')),
44 | 'reviews' => ReviewResource::collection($this->whenLoaded('reviews')),
45 | ];
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "type": "module",
4 | "scripts": {
5 | "dev": "vite",
6 | "build": "vue-tsc && vite build && vite build --ssr",
7 | "prettier": "prettier --write ."
8 | },
9 | "devDependencies": {
10 | "@inertiajs/vue3": "^1.0.0",
11 | "@tailwindcss/aspect-ratio": "^0.4.2",
12 | "@tailwindcss/forms": "^0.5.3",
13 | "@tailwindcss/typography": "^0.5.10",
14 | "@types/lodash": "^4.14.202",
15 | "@types/qs": "^6.9.11",
16 | "@vitejs/plugin-vue": "^5.0.0",
17 | "@vue/server-renderer": "^3.4.0",
18 | "autoprefixer": "^10.4.12",
19 | "axios": "^1.6.4",
20 | "laravel-vite-plugin": "^1.0.0",
21 | "lodash": "^4.17.21",
22 | "postcss": "^8.4.31",
23 | "prettier": "^3.2.4",
24 | "prettier-plugin-tailwindcss": "^0.5.11",
25 | "tailwind-scrollbar": "^3.0.5",
26 | "tailwindcss": "^3.2.1",
27 | "tippy.js": "^6.3.7",
28 | "typescript": "^5.0.2",
29 | "vite": "^5.0.0",
30 | "vue": "^3.4.0",
31 | "vue-tsc": "^1.8.27"
32 | },
33 | "dependencies": {
34 | "@fortawesome/fontawesome-svg-core": "^6.5.1",
35 | "@fortawesome/free-brands-svg-icons": "^6.5.1",
36 | "@fortawesome/free-regular-svg-icons": "^6.5.1",
37 | "@fortawesome/free-solid-svg-icons": "^6.5.1",
38 | "@fortawesome/vue-fontawesome": "^3.0.5",
39 | "@headlessui/vue": "^1.7.17",
40 | "@kyvg/vue3-notification": "^3.1.3",
41 | "@splidejs/vue-splide": "^0.6.12",
42 | "@stripe/stripe-js": "^2.4.0",
43 | "@vueform/slider": "^2.1.10",
44 | "@vueuse/core": "^10.7.2",
45 | "flowbite": "^2.2.1",
46 | "qs": "^6.11.2",
47 | "vue-tel-input": "^8.3.1",
48 | "vue3-toastify": "^0.2.1",
49 | "ziggy-js": "^2.0.5"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------