├── .editorconfig
├── .env.example
├── .gitattributes
├── .gitignore
├── .styleci.yml
├── README.md
├── app
├── Console
│ └── Commands
│ │ └── DeleteUnpaidOrders.php
├── Enums
│ ├── AddressType.php
│ ├── CustomerStatus.php
│ ├── OrderStatus.php
│ └── PaymentStatus.php
├── Helpers
│ └── Cart.php
├── Http
│ ├── Controllers
│ │ ├── Api
│ │ │ ├── AuthController.php
│ │ │ ├── CategoryController.php
│ │ │ ├── CustomerController.php
│ │ │ ├── DashboardController.php
│ │ │ ├── OrderController.php
│ │ │ ├── ProductController.php
│ │ │ └── UserController.php
│ │ ├── Auth
│ │ │ ├── AuthenticatedSessionController.php
│ │ │ ├── ConfirmablePasswordController.php
│ │ │ ├── EmailVerificationNotificationController.php
│ │ │ ├── EmailVerificationPromptController.php
│ │ │ ├── NewPasswordController.php
│ │ │ ├── PasswordResetLinkController.php
│ │ │ ├── RegisteredUserController.php
│ │ │ └── VerifyEmailController.php
│ │ ├── CartController.php
│ │ ├── CheckoutController.php
│ │ ├── Controller.php
│ │ ├── OrderController.php
│ │ ├── ProductController.php
│ │ ├── ProfileController.php
│ │ └── ReportController.php
│ ├── Middleware
│ │ ├── Admin.php
│ │ └── GuestOrVerified.php
│ ├── Requests
│ │ ├── Auth
│ │ │ └── LoginRequest.php
│ │ ├── CreateUserRequest.php
│ │ ├── CustomerRequest.php
│ │ ├── PasswordUpdateRequest.php
│ │ ├── ProductRequest.php
│ │ ├── ProfileRequest.php
│ │ ├── StoreCategoryRequest.php
│ │ ├── UpdateCategoryRequest.php
│ │ └── UpdateUserRequest.php
│ └── Resources
│ │ ├── CategoryResource.php
│ │ ├── CategoryTreeResource.php
│ │ ├── CountryResource.php
│ │ ├── CustomerListResource.php
│ │ ├── CustomerResource.php
│ │ ├── Dashboard
│ │ └── OrderResource.php
│ │ ├── OrderListResource.php
│ │ ├── OrderResource.php
│ │ ├── ProductListResource.php
│ │ ├── ProductResource.php
│ │ └── UserResource.php
├── Mail
│ ├── NewOrderEmail.php
│ └── OrderUpdateEmail.php
├── Models
│ ├── Api
│ │ ├── Product.php
│ │ └── User.php
│ ├── CartItem.php
│ ├── Category.php
│ ├── Country.php
│ ├── Customer.php
│ ├── CustomerAddress.php
│ ├── Order.php
│ ├── OrderDetail.php
│ ├── OrderItem.php
│ ├── Payment.php
│ ├── Product.php
│ ├── ProductCategory.php
│ ├── ProductImage.php
│ └── User.php
├── Providers
│ ├── AppServiceProvider.php
│ └── TelescopeServiceProvider.php
├── Traits
│ └── ReportTrait.php
└── View
│ └── Components
│ ├── AppLayout.php
│ └── GuestLayout.php
├── artisan
├── backend
├── .env.example
├── .env.production
├── .gitignore
├── .vscode
│ └── extensions.json
├── README.md
├── index.html
├── package-lock.json
├── package.json
├── postcss.config.js
├── public
│ └── favicon.ico
├── src
│ ├── App.vue
│ ├── assets
│ │ ├── logo.png
│ │ └── noimage.png
│ ├── axios.js
│ ├── components
│ │ ├── AppLayout.vue
│ │ ├── GuestLayout.vue
│ │ ├── ImagePreview.vue
│ │ ├── Navbar.vue
│ │ ├── Sidebar.vue
│ │ └── core
│ │ │ ├── Charts
│ │ │ ├── Bar.vue
│ │ │ ├── Doughnut.vue
│ │ │ └── Line.vue
│ │ │ ├── CustomInput.vue
│ │ │ ├── Spinner.vue
│ │ │ ├── Table
│ │ │ └── TableHeaderCell.vue
│ │ │ └── Toast.vue
│ ├── constants.js
│ ├── filters
│ │ └── currency.js
│ ├── index.css
│ ├── main.js
│ ├── router
│ │ └── index.js
│ ├── store
│ │ ├── actions.js
│ │ ├── index.js
│ │ ├── mutations.js
│ │ └── state.js
│ └── views
│ │ ├── Categories
│ │ ├── Categories.vue
│ │ ├── CategoriesTable.vue
│ │ └── CategoryModal.vue
│ │ ├── Customers
│ │ ├── CustomerView.vue
│ │ ├── Customers.vue
│ │ └── CustomersTable.vue
│ │ ├── Dashboard.vue
│ │ ├── Login.vue
│ │ ├── NotFound.vue
│ │ ├── Orders
│ │ ├── OrderStatus.vue
│ │ ├── OrderView.vue
│ │ ├── Orders.vue
│ │ └── OrdersTable.vue
│ │ ├── Products
│ │ ├── ProductForm.vue
│ │ ├── Products.vue
│ │ └── ProductsTable.vue
│ │ ├── Reports
│ │ ├── CustomersReport.vue
│ │ ├── OrdersReport.vue
│ │ └── Report.vue
│ │ ├── RequestPassword.vue
│ │ ├── ResetPassword.vue
│ │ └── Users
│ │ ├── UserModal.vue
│ │ ├── Users.vue
│ │ └── UsersTable.vue
├── tailwind.config.js
└── vite.config.js
├── bootstrap
├── app.php
├── cache
│ └── .gitignore
└── providers.php
├── composer.json
├── composer.lock
├── config
├── app.php
├── auth.php
├── cache.php
├── database.php
├── filesystems.php
├── logging.php
├── mail.php
├── queue.php
├── sanctum.php
├── services.php
├── session.php
└── telescope.php
├── database
├── .gitignore
├── factories
│ ├── ProductFactory.php
│ └── UserFactory.php
├── migrations
│ ├── 2014_10_12_000000_create_users_table.php
│ ├── 2014_10_12_100000_create_password_resets_table.php
│ ├── 2019_08_19_000000_create_failed_jobs_table.php
│ ├── 2019_12_14_000001_create_personal_access_tokens_table.php
│ ├── 2022_07_09_004121_create_products_table.php
│ ├── 2022_07_09_004135_create_orders_table.php
│ ├── 2022_07_09_004342_create_countries_table.php
│ ├── 2022_07_09_004403_create_cart_items_table.php
│ ├── 2022_07_09_004417_create_order_details_table.php
│ ├── 2022_07_09_004430_create_order_items_table.php
│ ├── 2022_07_09_004446_create_payments_table.php
│ ├── 2022_07_09_004505_create_customers_table.php
│ ├── 2022_07_09_004515_create_customer_addresses_table.php
│ ├── 2022_07_11_043258_add_is_admin_column_to_users_table.php
│ ├── 2022_09_11_142434_rename_customer_id_column.php
│ ├── 2022_09_17_025414_change_countries_states_column_into_json.php
│ ├── 2022_10_01_142356_add_session_id_to_payments_table.php
│ ├── 2022_10_09_171628_add_published_column_to_products.php
│ ├── 2022_11_28_194915_update_order_items_order_id.php
│ ├── 2022_11_28_194929_update_payments_order_id.php
│ ├── 2023_02_26_194708_add_expires_at_column_to_personal_access_tokens.php
│ ├── 2023_08_29_144700_add_quantity_column_to_products_table.php
│ ├── 2023_09_01_145113_create_product_images_table.php
│ ├── 2023_09_22_145051_create_categories_table.php
│ └── 2023_10_16_151019_create_product_categories_table.php
└── seeders
│ ├── AdminUserSeeder.php
│ ├── CountrySeeder.php
│ ├── DatabaseSeeder.php
│ └── ProductSeeder.php
├── lang
└── en
│ ├── auth.php
│ ├── pagination.php
│ ├── passwords.php
│ └── validation.php
├── package-lock.json
├── package.json
├── phpunit.xml
├── postcss.config.js
├── public
├── .htaccess
├── build
│ ├── assets
│ │ ├── app-ca70469a.css
│ │ └── app-e41bd908.js
│ └── manifest.json
├── favicon.ico
├── img
│ └── noimage.png
├── index.php
├── robots.txt
└── vendor
│ └── telescope
│ ├── app-dark.css
│ ├── app.css
│ ├── app.js
│ ├── favicon.ico
│ └── mix-manifest.json
├── resources
├── css
│ └── app.css
├── js
│ ├── app.js
│ ├── bootstrap.js
│ └── http.js
└── views
│ ├── auth
│ ├── confirm-password.blade.php
│ ├── forgot-password.blade.php
│ ├── login.blade.php
│ ├── register.blade.php
│ ├── reset-password.blade.php
│ └── verify-email.blade.php
│ ├── cart
│ └── index.blade.php
│ ├── checkout
│ ├── failure.blade.php
│ └── success.blade.php
│ ├── components
│ ├── application-logo.blade.php
│ ├── auth-card.blade.php
│ ├── auth-session-status.blade.php
│ ├── auth-validation-errors.blade.php
│ ├── button.blade.php
│ ├── category-list.blade.php
│ ├── dropdown-link.blade.php
│ ├── dropdown.blade.php
│ ├── input.blade.php
│ ├── label.blade.php
│ ├── nav-link.blade.php
│ └── responsive-nav-link.blade.php
│ ├── dashboard.blade.php
│ ├── layouts
│ ├── app.blade.php
│ ├── guest.blade.php
│ └── navigation.blade.php
│ ├── mail
│ ├── new-order.blade.php
│ └── update-order.blade.php
│ ├── order
│ ├── index.blade.php
│ └── view.blade.php
│ ├── product
│ ├── index.blade.php
│ └── view.blade.php
│ ├── profile
│ └── view.blade.php
│ ├── vendor
│ └── mail
│ │ ├── html
│ │ ├── button.blade.php
│ │ ├── footer.blade.php
│ │ ├── header.blade.php
│ │ ├── layout.blade.php
│ │ ├── message.blade.php
│ │ ├── panel.blade.php
│ │ ├── subcopy.blade.php
│ │ ├── table.blade.php
│ │ └── themes
│ │ │ └── default.css
│ │ └── text
│ │ ├── button.blade.php
│ │ ├── footer.blade.php
│ │ ├── header.blade.php
│ │ ├── layout.blade.php
│ │ ├── message.blade.php
│ │ ├── panel.blade.php
│ │ ├── subcopy.blade.php
│ │ └── table.blade.php
│ └── welcome.blade.php
├── routes
├── api.php
├── auth.php
├── console.php
└── web.php
├── storage
├── app
│ ├── .gitignore
│ └── public
│ │ └── .gitignore
├── debugbar
│ └── .gitignore
├── framework
│ ├── .gitignore
│ ├── cache
│ │ ├── .gitignore
│ │ └── data
│ │ │ └── .gitignore
│ ├── sessions
│ │ └── .gitignore
│ ├── testing
│ │ └── .gitignore
│ └── views
│ │ └── .gitignore
└── logs
│ └── .gitignore
├── tailwind.config.js
├── tests
├── CreatesApplication.php
├── Feature
│ ├── Auth
│ │ ├── AuthenticationTest.php
│ │ ├── EmailVerificationTest.php
│ │ ├── PasswordConfirmationTest.php
│ │ ├── PasswordResetTest.php
│ │ └── RegistrationTest.php
│ └── ExampleTest.php
├── TestCase.php
└── Unit
│ └── ExampleTest.php
└── vite.config.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | insert_final_newline = true
7 | indent_style = space
8 | indent_size = 4
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
14 | [*.{yml,yaml,js,css,html,vue}]
15 | indent_size = 2
16 |
17 | [docker-compose.yml]
18 | indent_size = 4
19 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | APP_NAME=Laravel
2 | APP_ENV=local
3 | APP_KEY=
4 | APP_DEBUG=true
5 | APP_URL=http://localhost
6 |
7 | LOG_CHANNEL=stack
8 | LOG_DEPRECATIONS_CHANNEL=null
9 | LOG_LEVEL=debug
10 |
11 | DB_CONNECTION=mysql
12 | DB_HOST=127.0.0.1
13 | DB_PORT=3306
14 | DB_DATABASE=laravel_vue_ecommerce
15 | DB_USERNAME=root
16 | DB_PASSWORD=
17 |
18 | BROADCAST_DRIVER=log
19 | CACHE_DRIVER=file
20 | FILESYSTEM_DISK=local
21 | QUEUE_CONNECTION=sync
22 | SESSION_DRIVER=file
23 | SESSION_LIFETIME=120
24 |
25 | MEMCACHED_HOST=127.0.0.1
26 |
27 | REDIS_HOST=127.0.0.1
28 | REDIS_PASSWORD=null
29 | REDIS_PORT=6379
30 |
31 | MAIL_MAILER=smtp
32 | MAIL_HOST=mailhog
33 | MAIL_PORT=1025
34 | MAIL_USERNAME=null
35 | MAIL_PASSWORD=null
36 | MAIL_ENCRYPTION=null
37 | MAIL_FROM_ADDRESS="hello@example.com"
38 | MAIL_FROM_NAME="${APP_NAME}"
39 |
40 | AWS_ACCESS_KEY_ID=
41 | AWS_SECRET_ACCESS_KEY=
42 | AWS_DEFAULT_REGION=us-east-1
43 | AWS_BUCKET=
44 | AWS_USE_PATH_STYLE_ENDPOINT=false
45 |
46 | PUSHER_APP_ID=
47 | PUSHER_APP_KEY=
48 | PUSHER_APP_SECRET=
49 | PUSHER_HOST=
50 | PUSHER_PORT=443
51 | PUSHER_SCHEME=https
52 | PUSHER_APP_CLUSTER=mt1
53 |
54 | VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
55 | VITE_PUSHER_HOST="${PUSHER_HOST}"
56 | VITE_PUSHER_PORT="${PUSHER_PORT}"
57 | VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
58 | VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
59 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /public/hot
3 | /public/storage
4 | /storage/*.key
5 | /vendor
6 | .env
7 | .env.backup
8 | .phpunit.result.cache
9 | Homestead.json
10 | Homestead.yaml
11 | npm-debug.log
12 | yarn-error.log
13 | /.idea
14 | /.vscode
15 |
--------------------------------------------------------------------------------
/.styleci.yml:
--------------------------------------------------------------------------------
1 | php:
2 | preset: laravel
3 | disabled:
4 | - no_unused_imports
5 | finder:
6 | not-name:
7 | - index.php
8 | js:
9 | finder:
10 | not-name:
11 | - vite.config.js
12 | css: true
13 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Laravel E-commerce Website
2 | E-commerce application built with Laravel, Vue.js, Tailwind.css and Alpine.js.
3 |
4 | > If you want to see every single step how this E-commerce application is build and learn how to build your own Full Stack applications, check my website [thecodeholic.com](https://thecodeholic.com)
5 |
6 | ## Demo
7 | Admin Panel: https://admin.lcommerce.net
8 | ```
9 | Email: admin@example.com
10 | Password: admin123
11 | ```
12 |
13 | Website: https://lcommerce.net
14 |
15 | ```
16 | Email: user1@example.com
17 | Password: useruser1
18 |
19 |
20 | Email: user2@example.com
21 | Password: useruser2
22 | ```
23 |
24 | ## Installation
25 | Make sure you have environment setup properly. You will need MySQL, PHP8.1, Node.js and composer.
26 |
27 | ### Install Laravel Website + API
28 | 1. Download the project (or clone using GIT)
29 | 2. Copy `.env.example` into `.env` and configure database credentials
30 | 3. Navigate to the project's root directory using terminal
31 | 4. Run `composer install`
32 | 5. Set the encryption key by executing `php artisan key:generate --ansi`
33 | 6. Run migrations `php artisan migrate --seed`
34 | 7. Start local server by executing `php artisan serve`
35 | 8. Open new terminal and navigate to the project root directory
36 | 9. Run `npm install`
37 | 10. Run `npm run dev` to start vite server for Laravel frontend
38 |
39 | ### Install Vue.js Admin Panel
40 | 1. Navigate to `backend` folder
41 | 2. Run `npm install`
42 | 3. Copy `backend/.env.example` into `backend/.env`
43 | 4. Make sure `VITE_API_BASE_URL` key in `backend/.env` is set to your Laravel API host (Default: http://localhost:8000)
44 | 5. Run `npm run dev`
45 | 6. Open Vue.js Admin Panel in browser and login with
46 | ```
47 | admin@example.com
48 | admin123
49 | ```
50 |
--------------------------------------------------------------------------------
/app/Console/Commands/DeleteUnpaidOrders.php:
--------------------------------------------------------------------------------
1 | argument('hours');
32 | $count = Order::deleteUnpaidOrders($hours);
33 | $this->info("$count unpaid orders were deleted");
34 | return Command::SUCCESS;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/Enums/AddressType.php:
--------------------------------------------------------------------------------
1 |
15 | * @package App\Enums
16 | */
17 | enum AddressType: string
18 | {
19 | case Shipping = 'shipping';
20 | case Billing = 'billing';
21 | }
22 |
--------------------------------------------------------------------------------
/app/Enums/CustomerStatus.php:
--------------------------------------------------------------------------------
1 |
15 | * @package App\Enums
16 | */
17 | enum CustomerStatus: string
18 | {
19 | case Active = 'active';
20 | case Disabled = 'disabled';
21 | }
22 |
--------------------------------------------------------------------------------
/app/Enums/OrderStatus.php:
--------------------------------------------------------------------------------
1 |
15 | * @package App\Enums
16 | */
17 | enum OrderStatus: string
18 | {
19 | case Unpaid = 'unpaid';
20 | case Paid = 'paid';
21 | case Cancelled = 'cancelled';
22 | case Shipped = 'shipped';
23 | case Completed = 'completed';
24 |
25 | public static function getStatuses()
26 | {
27 | return [
28 | self::Paid, self::Unpaid, self::Cancelled, self::Shipped, self::Completed
29 | ];
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/app/Enums/PaymentStatus.php:
--------------------------------------------------------------------------------
1 |
15 | * @package App\Enums
16 | */
17 | enum PaymentStatus: string
18 | {
19 | case Pending = 'pending';
20 | case Paid = 'paid';
21 | case Failed = 'failed';
22 | }
23 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Api/AuthController.php:
--------------------------------------------------------------------------------
1 | validate([
15 | 'email'=> ['required', 'email'],
16 | 'password' => 'required',
17 | 'remember' => 'boolean'
18 | ]);
19 | $remember = $credentials['remember'] ?? false;
20 | unset($credentials['remember']);
21 | if (!Auth::attempt($credentials, $remember)) {
22 | return response([
23 | 'message' => 'Email or password is incorrect'
24 | ], 422);
25 | }
26 |
27 | /** @var \App\Models\User $user */
28 | $user = Auth::user();
29 | if (!$user->is_admin) {
30 | Auth::logout();
31 | return response([
32 | 'message' => 'You don\'t have permission to authenticate as admin'
33 | ], 403);
34 | }
35 | if (!$user->email_verified_at) {
36 | Auth::logout();
37 | return response([
38 | 'message' => 'Your email address is not verified'
39 | ], 403);
40 | }
41 | $token = $user->createToken('main')->plainTextToken;
42 | return response([
43 | 'user' => new UserResource($user),
44 | 'token' => $token
45 | ]);
46 |
47 | }
48 |
49 | public function logout()
50 | {
51 | /** @var \App\Models\User $user */
52 | $user = Auth::user();
53 | $user->currentAccessToken()->delete();
54 |
55 | return response('', 204);
56 | }
57 |
58 | public function getUser(Request $request)
59 | {
60 | return new UserResource($request->user());
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Api/CategoryController.php:
--------------------------------------------------------------------------------
1 | orderBy($sortField, $sortDirection)
24 | ->latest()
25 | ->get();
26 |
27 | return CategoryResource::collection($categories);
28 | }
29 |
30 | public function getAsTree()
31 | {
32 | return Category::getActiveAsTree(CategoryTreeResource::class);
33 | }
34 |
35 | /**
36 | * Store a newly created resource in storage.
37 | */
38 | public function store(StoreCategoryRequest $request)
39 | {
40 | $data = $request->validated();
41 | $data['created_by'] = $request->user()->id;
42 | $data['updated_by'] = $request->user()->id;
43 | $category = Category::create($data);
44 |
45 | return new CategoryResource($category);
46 | }
47 |
48 | /**
49 | * Update the specified resource in storage.
50 | */
51 | public function update(UpdateCategoryRequest $request, Category $category)
52 | {
53 | $data = $request->validated();
54 | $data['updated_by'] = $request->user()->id;
55 | $category->update($data);
56 |
57 | return new CategoryResource($category);
58 | }
59 |
60 | /**
61 | * Remove the specified resource from storage.
62 | */
63 | public function destroy(Category $category)
64 | {
65 | $category->delete();
66 |
67 | return response()->noContent();
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Api/OrderController.php:
--------------------------------------------------------------------------------
1 | withCount('items')
33 | ->with('user.customer')
34 | ->where('id', 'like', "%{$search}%")
35 | ->orderBy($sortField, $sortDirection)
36 | ->paginate($perPage);
37 |
38 | return OrderListResource::collection($query);
39 | }
40 |
41 | public function view(Order $order)
42 | {
43 | $order->load('items.product');
44 |
45 | return new OrderResource($order);
46 | }
47 |
48 | public function getStatuses()
49 | {
50 | return OrderStatus::getStatuses();
51 | }
52 |
53 | public function changeStatus(Order $order, $status)
54 | {
55 | DB::beginTransaction();
56 | try {
57 | $order->status = $status;
58 | $order->save();
59 |
60 | if ($status === OrderStatus::Cancelled->value) {
61 | foreach ($order->items as $item) {
62 | $product = $item->product;
63 | if ($product && $product->quantity !== null) {
64 | $product->quantity += $item->quantity;
65 | $product->save();
66 | }
67 | }
68 | }
69 | Mail::to($order->user)->send(new OrderUpdateEmail($order));
70 | } catch (\Exception $e) {
71 | DB::rollBack();
72 | throw $e;
73 | }
74 |
75 | DB::commit();
76 |
77 | return response('', 200);
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/AuthenticatedSessionController.php:
--------------------------------------------------------------------------------
1 | authenticate();
35 |
36 | $request->session()->regenerate();
37 |
38 | Cart::moveCartItemsIntoDb();
39 |
40 | return redirect()->intended(RouteServiceProvider::HOME);
41 | }
42 |
43 | /**
44 | * Destroy an authenticated session.
45 | *
46 | * @param \Illuminate\Http\Request $request
47 | * @return \Illuminate\Http\RedirectResponse
48 | */
49 | public function destroy(Request $request)
50 | {
51 | Auth::guard('web')->logout();
52 |
53 | $request->session()->invalidate();
54 |
55 | $request->session()->regenerateToken();
56 |
57 | return redirect('/');
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/ConfirmablePasswordController.php:
--------------------------------------------------------------------------------
1 | validate([
32 | 'email' => $request->user()->email,
33 | 'password' => $request->password,
34 | ])) {
35 | throw ValidationException::withMessages([
36 | 'password' => __('auth.password'),
37 | ]);
38 | }
39 |
40 | $request->session()->put('auth.password_confirmed_at', time());
41 |
42 | return redirect()->intended(RouteServiceProvider::HOME);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/EmailVerificationNotificationController.php:
--------------------------------------------------------------------------------
1 | user()->hasVerifiedEmail()) {
20 | return redirect()->intended(RouteServiceProvider::HOME);
21 | }
22 |
23 | $request->user()->sendEmailVerificationNotification();
24 |
25 | return back()->with('status', 'verification-link-sent');
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/EmailVerificationPromptController.php:
--------------------------------------------------------------------------------
1 | user()->hasVerifiedEmail()
20 | ? redirect()->intended(RouteServiceProvider::HOME)
21 | : view('auth.verify-email');
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/NewPasswordController.php:
--------------------------------------------------------------------------------
1 | $request]);
24 | }
25 |
26 | /**
27 | * Handle an incoming new password request.
28 | *
29 | * @param \Illuminate\Http\Request $request
30 | * @return \Illuminate\Http\RedirectResponse
31 | *
32 | * @throws \Illuminate\Validation\ValidationException
33 | */
34 | public function store(Request $request)
35 | {
36 | $request->validate([
37 | 'token' => ['required'],
38 | 'email' => ['required', 'email'],
39 | 'password' => ['required', 'confirmed', Rules\Password::defaults()],
40 | ]);
41 |
42 | // Here we will attempt to reset the user's password. If it is successful we
43 | // will update the password on an actual user model and persist it to the
44 | // database. Otherwise we will parse the error and return the response.
45 | $status = Password::reset(
46 | $request->only('email', 'password', 'password_confirmation', 'token'),
47 | function ($user) use ($request) {
48 | $user->forceFill([
49 | 'password' => Hash::make($request->password),
50 | 'remember_token' => Str::random(60),
51 | ])->save();
52 |
53 | event(new PasswordReset($user));
54 | }
55 | );
56 |
57 | // If the password was successfully reset, we will redirect the user back to
58 | // the application's home authenticated view. If there is an error we can
59 | // redirect them back to where they came from with their error message.
60 | return $status == Password::PASSWORD_RESET
61 | ? redirect()->route('login')->with('status', __($status))
62 | : back()->withInput($request->only('email'))
63 | ->withErrors(['email' => __($status)]);
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/PasswordResetLinkController.php:
--------------------------------------------------------------------------------
1 | validate([
32 | 'email' => ['required', 'email'],
33 | ]);
34 |
35 | // We will send the password reset link to this user. Once we have attempted
36 | // to send the link, we will examine the response then see the message we
37 | // need to show to the user. Finally, we'll send out a proper response.
38 | $status = Password::sendResetLink(
39 | $request->only('email')
40 | );
41 |
42 | return $status == Password::RESET_LINK_SENT
43 | ? back()->with('status', __($status))
44 | : back()->withInput($request->only('email'))
45 | ->withErrors(['email' => __($status)]);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/RegisteredUserController.php:
--------------------------------------------------------------------------------
1 | validate([
40 | 'name' => ['required', 'string', 'max:255'],
41 | 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
42 | 'password' => ['required', 'confirmed', Rules\Password::defaults()],
43 | ]);
44 |
45 | DB::beginTransaction();
46 | try {
47 | $user = User::create([
48 | 'name' => $request->name,
49 | 'email' => $request->email,
50 | 'password' => Hash::make($request->password),
51 | ]);
52 |
53 | event(new Registered($user));
54 |
55 | $customer = new Customer();
56 | $names = explode(" ", $user->name);
57 | $customer->user_id = $user->id;
58 | $customer->first_name = $names[0];
59 | $customer->last_name = $names[1] ?? '';
60 | $customer->save();
61 |
62 | Auth::login($user);
63 | } catch (\Exception $e) {
64 | DB::rollBack();
65 | return redirect()->back()->withInput()->with('error', 'Unable to register right now.');
66 | }
67 |
68 | DB::commit();
69 |
70 | Cart::moveCartItemsIntoDb();
71 |
72 | return redirect(RouteServiceProvider::HOME);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/VerifyEmailController.php:
--------------------------------------------------------------------------------
1 | user()->hasVerifiedEmail()) {
22 | return redirect()->intended(RouteServiceProvider::HOME.'?verified=1');
23 | }
24 |
25 | if ($request->user()->markEmailAsVerified()) {
26 | $customer = $request->user()->customer;
27 | $customer->status = CustomerStatus::Active->value;
28 | $customer->save();
29 | event(new Verified($request->user()));
30 | }
31 |
32 | return redirect()->intended(RouteServiceProvider::HOME.'?verified=1');
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Controller.php:
--------------------------------------------------------------------------------
1 | user();
14 |
15 | $orders = Order::withCount('items')
16 | ->where(['created_by' => $user->id])
17 | ->orderBy('created_at', 'desc')
18 | ->paginate(10);
19 |
20 | return view('order.index', compact('orders'));
21 | }
22 |
23 | public function view(Order $order)
24 | {
25 | /** @var \App\Models\User $user */
26 | $user = \request()->user();
27 | if ($order->created_by !== $user->id) {
28 | return response("You don't have permission to view this order", 403);
29 | }
30 |
31 | return view('order.view', compact('order'));
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/Http/Controllers/ProductController.php:
--------------------------------------------------------------------------------
1 | renderProducts($query);
17 | }
18 |
19 | public function byCategory(Category $category)
20 | {
21 | $categories = Category::getAllChildrenByParent($category);
22 |
23 | $query = Product::query()
24 | ->select('products.*')
25 | ->join('product_categories AS pc', 'pc.product_id', 'products.id')
26 | ->whereIn('pc.category_id', array_map(fn($c) => $c->id, $categories));
27 |
28 | return $this->renderProducts($query);
29 | }
30 |
31 | public function view(Product $product)
32 | {
33 | return view('product.view', ['product' => $product]);
34 | }
35 |
36 | private function renderProducts(Builder $query)
37 | {
38 | $search = \request()->get('search');
39 | $sort = \request()->get('sort', '-updated_at');
40 |
41 | if ($sort) {
42 | $sortDirection = 'asc';
43 | if ($sort[0] === '-') {
44 | $sortDirection = 'desc';
45 | }
46 | $sortField = preg_replace('/^-?/', '', $sort);
47 |
48 | $query->orderBy($sortField, $sortDirection);
49 | }
50 | $products = $query
51 | ->where('published', '=', 1)
52 | ->where(function ($query) use ($search) {
53 | /** @var $query \Illuminate\Database\Eloquent\Builder */
54 | $query->where('products.title', 'like', "%$search%")
55 | ->orWhere('products.description', 'like', "%$search%");
56 | })
57 |
58 | ->paginate(5);
59 |
60 | return view('product.index', [
61 | 'products' => $products
62 | ]);
63 |
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/app/Http/Controllers/ReportController.php:
--------------------------------------------------------------------------------
1 | prepareDataForBarChart($query, 'Orders By Day');
23 | }
24 |
25 | public function customers()
26 | {
27 | $query = Customer::query();
28 |
29 | return $this->prepareDataForBarChart($query, 'Customers By Day');
30 | }
31 |
32 | private function prepareDataForBarChart($query, $label)
33 | {
34 | $fromDate = $this->getFromDate() ?: Carbon::now()->subDay(30);
35 | $query
36 | ->select([DB::raw('CAST(created_at as DATE) AS day'), DB::raw('COUNT(created_at) AS count')])
37 | ->groupBy(DB::raw('CAST(created_at as DATE)'));
38 | if ($fromDate) {
39 | $query->where('created_at', '>', $fromDate);
40 | }
41 | $records = $query->get()->keyBy('day');
42 |
43 | // Process for chartjs
44 | $days = [];
45 | $labels = [];
46 | $now = Carbon::now();
47 | while ($fromDate < $now) {
48 | $key = $fromDate->format('Y-m-d');
49 | $labels[] = $key;
50 | $fromDate = $fromDate->addDay(1);
51 | $days[] = isset($records[$key]) ? $records[$key]['count'] : 0;
52 | }
53 |
54 | return [
55 | 'labels' => $labels,
56 | 'datasets' => [[
57 | 'label' => $label,
58 | 'backgroundColor' => '#f87979',
59 | 'data' => $days
60 | ]]
61 | ];
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/app/Http/Middleware/Admin.php:
--------------------------------------------------------------------------------
1 | is_admin == 1) {
21 | return $next($request);
22 | }
23 | return response([
24 | 'message' => 'You don\'t have permission to perform this action'
25 | ], 403);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/Http/Middleware/GuestOrVerified.php:
--------------------------------------------------------------------------------
1 | user()) {
20 | return $next($request);
21 | }
22 | return parent::handle($request, $next, $redirectToRoute);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/Http/Requests/CreateUserRequest.php:
--------------------------------------------------------------------------------
1 |
24 | */
25 | public function rules()
26 | {
27 | return [
28 | 'name' => ['required', 'max:55'],
29 | 'email' => ['required', 'email'],
30 | 'password' => ['required', Password::min(8)->numbers()->letters()->symbols()]
31 | ];
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/Http/Requests/CustomerRequest.php:
--------------------------------------------------------------------------------
1 |
25 | */
26 | public function rules()
27 | {
28 | return [
29 | 'first_name' => ['required'],
30 | 'last_name' => ['required'],
31 | 'phone' => ['required', 'min:7'],
32 | 'email' => ['required', 'email'],
33 | 'status' => ['required', 'boolean'],
34 |
35 | 'shippingAddress.address1' => ['required'],
36 | 'shippingAddress.address2' => ['required'],
37 | 'shippingAddress.city' => ['required'],
38 | 'shippingAddress.state' => ['required'],
39 | 'shippingAddress.zipcode' => ['required'],
40 | 'shippingAddress.country_code' => ['required', 'exists:countries,code'],
41 |
42 | 'billingAddress.address1' => ['required'],
43 | 'billingAddress.address2' => ['required'],
44 | 'billingAddress.city' => ['required'],
45 | 'billingAddress.state' => ['required'],
46 | 'billingAddress.zipcode' => ['required'],
47 | 'billingAddress.country_code' => ['required', 'exists:countries,code'],
48 |
49 | ];
50 | }
51 |
52 | public function attributes()
53 | {
54 | return [
55 | 'billingAddress.address1' => 'address 1',
56 | 'billingAddress.address2' => 'address 2',
57 | 'billingAddress.city' => 'city',
58 | 'billingAddress.state' => 'state',
59 | 'billingAddress.zipcode' => 'zip code',
60 | 'billingAddress.country_code' => 'country',
61 | 'shippingAddress.address1' => 'address 1',
62 | 'shippingAddress.address2' => 'address 2',
63 | 'shippingAddress.city' => 'city',
64 | 'shippingAddress.state' => 'state',
65 | 'shippingAddress.zipcode' => 'zip code',
66 | 'shippingAddress.country_code' => 'country',
67 | ];
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/app/Http/Requests/PasswordUpdateRequest.php:
--------------------------------------------------------------------------------
1 |
24 | */
25 | public function rules()
26 | {
27 | return [
28 | 'old_password' => 'current_password',
29 | 'new_password' => ['required', 'confirmed', Password::min(8)->letters()->numbers()->symbols()]
30 | ];
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/Http/Requests/ProductRequest.php:
--------------------------------------------------------------------------------
1 |
23 | */
24 | public function rules()
25 | {
26 | return [
27 | 'title' => ['required', 'max:2000'],
28 | 'images.*' => ['nullable', 'image'],
29 | 'deleted_images.*' => ['nullable', 'int'],
30 | 'image_positions.*' => ['nullable', 'int'],
31 | 'categories.*' => ['nullable', 'int', 'exists:categories,id'],
32 | 'price' => ['required', 'numeric', 'min:0.01'],
33 | 'quantity' => ['nullable', 'numeric', 'min:0'],
34 | 'description' => ['nullable', 'string'],
35 | 'published' => ['required', 'boolean']
36 | ];
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/app/Http/Requests/ProfileRequest.php:
--------------------------------------------------------------------------------
1 |
23 | */
24 | public function rules()
25 | {
26 | return [
27 | 'first_name' => ['required'],
28 | 'last_name' => ['required'],
29 | 'phone' => ['required', 'min:7'],
30 | 'email' => ['required', 'email'],
31 |
32 | 'shipping.address1' => ['required'],
33 | 'shipping.address2' => ['required'],
34 | 'shipping.city' => ['required'],
35 | 'shipping.state' => ['required'],
36 | 'shipping.zipcode' => ['required'],
37 | 'shipping.country_code' => ['required', 'exists:countries,code'],
38 |
39 | 'billing.address1' => ['required'],
40 | 'billing.address2' => ['required'],
41 | 'billing.city' => ['required'],
42 | 'billing.state' => ['required'],
43 | 'billing.zipcode' => ['required'],
44 | 'billing.country_code' => ['required', 'exists:countries,code'],
45 |
46 | ];
47 | }
48 |
49 | public function attributes()
50 | {
51 | return [
52 | 'billing.address1' => 'address 1',
53 | 'billing.address2' => 'address 2',
54 | 'billing.city' => 'city',
55 | 'billing.state' => 'state',
56 | 'billing.zipcode' => 'zip code',
57 | 'billing.country_code' => 'country',
58 | 'shipping.address1' => 'address 1',
59 | 'shipping.address2' => 'address 2',
60 | 'shipping.city' => 'city',
61 | 'shipping.state' => 'state',
62 | 'shipping.zipcode' => 'zip code',
63 | 'shipping.country_code' => 'country',
64 | ];
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/app/Http/Requests/StoreCategoryRequest.php:
--------------------------------------------------------------------------------
1 |
21 | */
22 | public function rules(): array
23 | {
24 | return [
25 | 'name' => ['required', 'string'],
26 | 'parent_id' => ['nullable', 'exists:categories,id'],
27 | 'active' => ['required', 'boolean']
28 | ];
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/app/Http/Requests/UpdateCategoryRequest.php:
--------------------------------------------------------------------------------
1 |
22 | */
23 | public function rules(): array
24 | {
25 | return [
26 | 'name' => ['required', 'string'],
27 | 'parent_id' => [
28 | 'nullable', 'exists:categories,id',
29 | function(string $attribute, $value, \Closure $fail) {
30 | $id = $this->get('id');
31 | $category = Category::where('id', $id)->first();
32 |
33 | $children = Category::getAllChildrenByParent($category);
34 | $ids = array_map(fn($c) => $c->id, $children);
35 |
36 | if (in_array($value, $ids)) {
37 | return $fail('You cannot choose category as parent which is already a child of the category.');
38 | }
39 | }
40 | ],
41 | 'active' => ['required', 'boolean']
42 | ];
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/app/Http/Requests/UpdateUserRequest.php:
--------------------------------------------------------------------------------
1 |
24 | */
25 | public function rules()
26 | {
27 | return [
28 | 'name' => ['max:55'],
29 | 'email' => ['email'],
30 | 'password' => ['nullable', Password::min(8)->numbers()->letters()->symbols()]
31 | ];
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/Http/Resources/CategoryResource.php:
--------------------------------------------------------------------------------
1 |
14 | */
15 | public function toArray(Request $request): array
16 | {
17 | return [
18 | 'id' => $this->id,
19 | 'name' => $this->name,
20 | 'slug' => $this->slug,
21 | 'active' => $this->active,
22 | 'parent_id' => $this->parent_id,
23 | 'parent' => $this->parent ? new CategoryResource($this->parent) : null,
24 | 'created_at' => $this->created_at->format('Y-m-d H:i:s'),
25 | 'updated_at' => $this->updated_at->format('Y-m-d H:i:s'),
26 | ];
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/Http/Resources/CategoryTreeResource.php:
--------------------------------------------------------------------------------
1 |
14 | */
15 | public function toArray(Request $request): array
16 | {
17 | $data = [
18 | 'id' => $this->id,
19 | 'label' => $this->name,
20 | ];
21 |
22 | if ($this->children ?? false) {
23 | $data['children'] = $this->children;
24 | }
25 |
26 | return $data;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/Http/Resources/CountryResource.php:
--------------------------------------------------------------------------------
1 | $this->code,
23 | 'name' => $this->name,
24 | 'states' => json_decode($this->states, true),
25 | ];
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/Http/Resources/CustomerListResource.php:
--------------------------------------------------------------------------------
1 | $this->user_id,
23 | 'first_name' => $this->first_name,
24 | 'last_name' => $this->last_name,
25 | 'email' => $this->user->email,
26 | 'phone' => $this->phone,
27 | 'status' => $this->status,
28 | 'created_at' => (new \DateTime($this->created_at))->format('Y-m-d H:i:s'),
29 | 'updated_at' => (new \DateTime($this->updated_at))->format('Y-m-d H:i:s'),
30 | ];
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/Http/Resources/CustomerResource.php:
--------------------------------------------------------------------------------
1 | shippingAddress;
23 | $billing = $this->billingAddress;
24 | return [
25 | 'id' => $this->user_id,
26 | 'first_name' => $this->first_name,
27 | 'last_name' => $this->last_name,
28 | 'email' => $this->user->email,
29 | 'phone' => $this->phone,
30 | 'status' => $this->status === CustomerStatus::Active->value,
31 | 'created_at' => (new \DateTime($this->created_at))->format('Y-m-d H:i:s'),
32 | 'updated_at' => (new \DateTime($this->updated_at))->format('Y-m-d H:i:s'),
33 |
34 | 'shippingAddress' => [
35 | 'id' => $shipping?->id,
36 | 'address1' => $shipping?->address1,
37 | 'address2' => $shipping?->address2,
38 | 'city' => $shipping?->city,
39 | 'state' => $shipping?->state,
40 | 'zipcode' => $shipping?->zipcode,
41 | 'country_code' => $shipping?->country->code,
42 | ],
43 | 'billingAddress' => [
44 | 'id' => $billing?->id,
45 | 'address1' => $billing?->address1,
46 | 'address2' => $billing?->address2,
47 | 'city' => $billing?->city,
48 | 'state' => $billing?->state,
49 | 'zipcode' => $billing?->zipcode,
50 | 'country_code' => $billing?->country->code,
51 | ]
52 | ];
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/app/Http/Resources/Dashboard/OrderResource.php:
--------------------------------------------------------------------------------
1 | $this->id,
21 | 'total_price' => $this->total_price,
22 | 'created_at' => $this->created_at->diffForHumans(),
23 | 'items' => $this->items,
24 | 'user_id' => $this->user_id,
25 | 'first_name' => $this->first_name,
26 | 'last_name' => $this->last_name,
27 | ];
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/Http/Resources/OrderListResource.php:
--------------------------------------------------------------------------------
1 | $this->id,
21 | 'status' => $this->status,
22 | 'total_price' => $this->total_price,
23 | 'number_of_items' => $this->items_count,
24 | 'customer' => [
25 | 'id' => $this->user->id,
26 | 'first_name' => $this->user->customer->first_name,
27 | 'last_name' => $this->user->customer->last_name,
28 | ],
29 | 'created_at' => (new \DateTime($this->created_at))->format('Y-m-d H:i:s'),
30 | 'updated_at' => (new \DateTime($this->updated_at))->format('Y-m-d H:i:s'),
31 | ];
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/Http/Resources/ProductListResource.php:
--------------------------------------------------------------------------------
1 | $this->id,
21 | 'title' => $this->title,
22 | 'image_url' => $this->image,
23 | 'price' => $this->price,
24 | 'quantity' => $this->quantity,
25 | 'updated_at' => ( new \DateTime($this->updated_at) )->format('Y-m-d H:i:s'),
26 | ];
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/Http/Resources/ProductResource.php:
--------------------------------------------------------------------------------
1 | $this->id,
23 | 'title' => $this->title,
24 | 'slug' => $this->slug,
25 | 'description' => $this->description,
26 | 'image_url' => $this->image,
27 | 'images' => $this->images,
28 | 'price' => $this->price,
29 | 'quantity' => $this->quantity,
30 | 'published' => (bool)$this->published,
31 | 'categories' => $this->categories->map(fn($c) => $c->id),
32 | 'created_at' => (new \DateTime($this->created_at))->format('Y-m-d H:i:s'),
33 | 'updated_at' => (new \DateTime($this->updated_at))->format('Y-m-d H:i:s'),
34 | ];
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/Http/Resources/UserResource.php:
--------------------------------------------------------------------------------
1 | $this->id,
22 | 'name' => $this->name,
23 | 'email' => $this->email,
24 | 'created_at' => (new DateTime($this->created_at))->format('Y-m-d H:i:s'),
25 | ];
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/Mail/NewOrderEmail.php:
--------------------------------------------------------------------------------
1 | subject('New Order')
34 | ->view('mail.new-order');
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/Mail/OrderUpdateEmail.php:
--------------------------------------------------------------------------------
1 | subject('Order Status was updated')
34 | ->view('mail.update-order');
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/Models/Api/Product.php:
--------------------------------------------------------------------------------
1 | generateSlugsFrom('name')
23 | ->saveSlugsTo('slug');
24 | }
25 |
26 | public function parent()
27 | {
28 | return $this->belongsTo(Category::class);
29 | }
30 |
31 | public function products()
32 | {
33 | return $this->belongsToMany(Product::class); // product_category
34 | }
35 |
36 | public static function getActiveAsTree($resourceClassName = null)
37 | {
38 | $categories = Category::where('active', true)->orderBy('parent_id')->get();
39 | return self::buildCategoryTree($categories, null, $resourceClassName);
40 | }
41 |
42 | public static function getAllChildrenByParent(Category $category)
43 | {
44 | $categories = Category::where('active', true)->orderBy('parent_id')->get();
45 | $result[] = $category;
46 | self::getCategoriesArray($categories, $category->id, $result);
47 |
48 | return $result;
49 | }
50 |
51 | private static function buildCategoryTree($categories, $parentId = null, $resourceClassName = null)
52 | {
53 | $categoryTree = [];
54 |
55 | foreach ($categories as $category) {
56 | if ($category->parent_id === $parentId) {
57 | $children = self::buildCategoryTree($categories, $category->id, $resourceClassName);
58 | if ($children) {
59 | $category->setAttribute('children', $children);
60 | }
61 | $categoryTree[] = $resourceClassName ? new $resourceClassName($category) : $category;
62 | }
63 | }
64 |
65 | return $categoryTree;
66 | }
67 |
68 | private static function getCategoriesArray($categories, $parentId, &$result)
69 | {
70 | foreach ($categories as $category) {
71 | if ($category->parent_id === $parentId) {
72 | $result[] = $category;
73 | self::getCategoriesArray($categories, $category->id, $result);
74 | }
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/app/Models/Country.php:
--------------------------------------------------------------------------------
1 | belongsTo(User::class);
21 | }
22 |
23 | private function _getAddresses(): HasOne
24 | {
25 | return $this->hasOne(CustomerAddress::class, 'customer_id', 'user_id');
26 | }
27 |
28 | public function shippingAddress(): HasOne
29 | {
30 | return $this->_getAddresses()->where('type', '=', AddressType::Shipping->value);
31 | }
32 |
33 | public function billingAddress(): HasOne
34 | {
35 | return $this->_getAddresses()->where('type', '=', AddressType::Billing->value);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/app/Models/CustomerAddress.php:
--------------------------------------------------------------------------------
1 | belongsTo(Customer::class);
19 | }
20 |
21 | public function country(): BelongsTo
22 | {
23 | return $this->belongsTo(Country::class, 'country_code', 'code');
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/Models/Order.php:
--------------------------------------------------------------------------------
1 | status === OrderStatus::Paid->value;
22 | }
23 |
24 | public function payment(): HasOne
25 | {
26 | return $this->hasOne(Payment::class);
27 | }
28 |
29 | public function user()
30 | {
31 | return $this->belongsTo(User::class, 'created_by');
32 | }
33 |
34 | public function items(): HasMany
35 | {
36 | return $this->hasMany(OrderItem::class);
37 | }
38 |
39 | public static function deleteUnpaidOrders($hours)
40 | {
41 | return Order::query()->where('status', OrderStatus::Unpaid->value)
42 | ->where('created_at', '<', Carbon::now()->subHours($hours))
43 | ->delete();
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/app/Models/OrderDetail.php:
--------------------------------------------------------------------------------
1 | belongsTo(Order::class);
18 | }
19 |
20 | public function product(): BelongsTo
21 | {
22 | return $this->belongsTo(Product::class);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/Models/Payment.php:
--------------------------------------------------------------------------------
1 | hasOne(Order::class, 'id', 'order_id');
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/app/Models/Product.php:
--------------------------------------------------------------------------------
1 | generateSlugsFrom('title')
26 | ->saveSlugsTo('slug');
27 | }
28 |
29 | public function getRouteKeyName()
30 | {
31 | return 'slug';
32 | }
33 |
34 | public function images()
35 | {
36 | return $this->hasMany(ProductImage::class)->orderBy('position');
37 | }
38 |
39 | public function getImageAttribute()
40 | {
41 | return $this->images->count() > 0 ? $this->images->get(0)->url : null;
42 | }
43 |
44 | public function categories()
45 | {
46 | return $this->belongsToMany(Category::class, 'product_categories');
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/app/Models/ProductCategory.php:
--------------------------------------------------------------------------------
1 | belongsTo(Product::class);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/Models/User.php:
--------------------------------------------------------------------------------
1 |
19 | */
20 | protected $fillable = [
21 | 'name',
22 | 'email',
23 | 'password',
24 | 'email_verified_at',
25 | 'is_admin'
26 | ];
27 |
28 | /**
29 | * The attributes that should be hidden for serialization.
30 | *
31 | * @var array
32 | */
33 | protected $hidden = [
34 | 'password',
35 | 'remember_token',
36 | ];
37 |
38 | /**
39 | * The attributes that should be cast.
40 | *
41 | * @var array
42 | */
43 | protected $casts = [
44 | 'email_verified_at' => 'datetime',
45 | ];
46 |
47 | public function customer()
48 | {
49 | return $this->hasOne(Customer::class);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/app/Providers/AppServiceProvider.php:
--------------------------------------------------------------------------------
1 | app->environment('local')) {
17 | $this->app->register(\Laravel\Telescope\TelescopeServiceProvider::class);
18 | $this->app->register(TelescopeServiceProvider::class);
19 | }
20 | }
21 |
22 | /**
23 | * Bootstrap any application services.
24 | *
25 | * @return void
26 | */
27 | public function boot()
28 | {
29 | //
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/app/Providers/TelescopeServiceProvider.php:
--------------------------------------------------------------------------------
1 | hideSensitiveRequestDetails();
20 |
21 | Telescope::filter(function (IncomingEntry $entry) {
22 | if ($this->app->environment('local')) {
23 | return true;
24 | }
25 |
26 | return $entry->isReportableException() ||
27 | $entry->isFailedRequest() ||
28 | $entry->isFailedJob() ||
29 | $entry->isScheduledTask() ||
30 | $entry->hasMonitoredTag();
31 | });
32 | }
33 |
34 | /**
35 | * Prevent sensitive request details from being logged by Telescope.
36 | */
37 | protected function hideSensitiveRequestDetails(): void
38 | {
39 | if ($this->app->environment('local')) {
40 | return;
41 | }
42 |
43 | Telescope::hideRequestParameters(['_token']);
44 |
45 | Telescope::hideRequestHeaders([
46 | 'cookie',
47 | 'x-csrf-token',
48 | 'x-xsrf-token',
49 | ]);
50 | }
51 |
52 | /**
53 | * Register the Telescope gate.
54 | *
55 | * This gate determines who can access Telescope in non-local environments.
56 | */
57 | protected function gate(): void
58 | {
59 | Gate::define('viewTelescope', function ($user) {
60 | return in_array($user->email, [
61 | //
62 | ]);
63 | });
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/app/Traits/ReportTrait.php:
--------------------------------------------------------------------------------
1 | get('d');
18 | $array = [
19 | '1d' => Carbon::now()->subDays(1),
20 | '1k' => Carbon::now()->subDays(7),
21 | '2k' => Carbon::now()->subDays(14),
22 | '1m' => Carbon::now()->subDays(30),
23 | '3m' => Carbon::now()->subDays(60),
24 | '6m' => Carbon::now()->subDays(180),
25 | ];
26 |
27 | return $array[$paramDate] ?? null;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/View/Components/AppLayout.php:
--------------------------------------------------------------------------------
1 | make(Illuminate\Contracts\Console\Kernel::class);
34 |
35 | $status = $kernel->handle(
36 | $input = new Symfony\Component\Console\Input\ArgvInput,
37 | new Symfony\Component\Console\Output\ConsoleOutput
38 | );
39 |
40 | /*
41 | |--------------------------------------------------------------------------
42 | | Shutdown The Application
43 | |--------------------------------------------------------------------------
44 | |
45 | | Once Artisan has finished running, we will fire off the shutdown events
46 | | so that any final work may be done by the application before we shut
47 | | down the process. This is the last thing to happen to the request.
48 | |
49 | */
50 |
51 | $kernel->terminate($input, $status);
52 |
53 | exit($status);
54 |
--------------------------------------------------------------------------------
/backend/.env.example:
--------------------------------------------------------------------------------
1 | VITE_API_BASE_URL = http://localhost:8000
2 |
--------------------------------------------------------------------------------
/backend/.env.production:
--------------------------------------------------------------------------------
1 | VITE_API_BASE_URL = https://lcommerce.net
2 |
--------------------------------------------------------------------------------
/backend/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 | .env
26 |
--------------------------------------------------------------------------------
/backend/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": ["Vue.volar"]
3 | }
4 |
--------------------------------------------------------------------------------
/backend/README.md:
--------------------------------------------------------------------------------
1 | # Vue 3 + Vite
2 |
3 | This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `
12 |