├── LICENSE.txt
├── README.md
├── backend
├── .editorconfig
├── .env.example
├── .gitattributes
├── .gitignore
├── README.md
├── app
│ ├── Http
│ │ ├── Controllers
│ │ │ ├── Api
│ │ │ │ ├── AuthController.php
│ │ │ │ └── UserController.php
│ │ │ └── Controller.php
│ │ ├── Requests
│ │ │ ├── LoginRequest.php
│ │ │ ├── SignupRequest.php
│ │ │ ├── UpdateUserPasswordRequest.php
│ │ │ └── UpdateUserRequest.php
│ │ └── Resources
│ │ │ └── UserResource.php
│ ├── Models
│ │ └── User.php
│ └── Providers
│ │ └── AppServiceProvider.php
├── artisan
├── 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
├── database
│ ├── .gitignore
│ ├── factories
│ │ └── UserFactory.php
│ ├── migrations
│ │ ├── 0001_01_01_000000_create_users_table.php
│ │ ├── 0001_01_01_000001_create_cache_table.php
│ │ ├── 0001_01_01_000002_create_jobs_table.php
│ │ └── 2024_08_29_215256_create_personal_access_tokens_table.php
│ └── seeders
│ │ └── DatabaseSeeder.php
├── package.json
├── phpcs.xml
├── phpunit.xml
├── public
│ ├── .htaccess
│ ├── favicon.ico
│ ├── index.php
│ └── robots.txt
├── resources
│ ├── css
│ │ └── app.css
│ ├── js
│ │ ├── app.js
│ │ └── bootstrap.js
│ └── views
│ │ └── welcome.blade.php
├── routes
│ ├── api.php
│ ├── console.php
│ └── web.php
├── storage
│ ├── app
│ │ ├── .gitignore
│ │ └── public
│ │ │ └── .gitignore
│ ├── framework
│ │ ├── .gitignore
│ │ ├── cache
│ │ │ ├── .gitignore
│ │ │ └── data
│ │ │ │ └── .gitignore
│ │ ├── sessions
│ │ │ └── .gitignore
│ │ ├── testing
│ │ │ └── .gitignore
│ │ └── views
│ │ │ └── .gitignore
│ └── logs
│ │ └── .gitignore
├── tests
│ ├── Feature
│ │ └── ExampleTest.php
│ ├── Pest.php
│ ├── TestCase.php
│ └── Unit
│ │ └── ExampleTest.php
└── vite.config.js
└── frontend
├── .env.example
├── .gitignore
├── README.md
├── eslint.config.js
├── index.html
├── package-lock.json
├── package.json
├── postcss.config.js
├── public
└── vite.svg
├── src
├── assets
│ ├── laravel-logo.svg
│ └── react-logo.svg
├── components
│ ├── AuthPageContainer.jsx
│ ├── MainMenu.jsx
│ ├── NavBar.jsx
│ ├── PageContainer.jsx
│ ├── UserMenu.jsx
│ └── ui
│ │ ├── AppLogo.jsx
│ │ ├── Button.jsx
│ │ ├── ConfirmationModal.jsx
│ │ ├── Form.jsx
│ │ ├── Input.jsx
│ │ └── Notification.jsx
├── hooks
│ ├── useChangePassword.js
│ ├── useLogin.js
│ ├── useProfile.js
│ └── useSignUp.js
├── index.css
├── layouts
│ ├── DefaultLayout.jsx
│ └── GuestLayout.jsx
├── main.jsx
├── pages
│ ├── AboutPage.jsx
│ ├── ChangePasswordPage.jsx
│ ├── ContactPage.jsx
│ ├── HomePage.jsx
│ ├── NotFoundPage.jsx
│ ├── ProfilePage.jsx
│ └── auth
│ │ ├── LoginPage.jsx
│ │ └── SignUpPage.jsx
├── routes
│ └── router.jsx
└── services
│ ├── api
│ └── axios-client.js
│ └── contexts
│ └── ContextProvider.jsx
├── tailwind.config.js
└── vite.config.js
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 Cristian Scheid
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Laravel & React Base
2 |
3 | ## Description
4 |
5 | This project serves as a minimal template for rapid development, using Laravel as a REST API for the backend and React for the frontend, all styled with Tailwind CSS. It features user authentication and profile management, providing a solid foundation for building more complex applications.
6 |
7 | ### Features
8 |
9 | - User signup and login.
10 | - Profile viewing, updating, and deletion.
11 | - Password change functionality.
12 | - Protected routes using Laravel Sanctum for authentication.
13 | - Mobile-first design approach with responsive layouts.
14 |
15 | A branch `raw` is also available, providing the same project without any styling. This is useful for those who prefer plain CSS or frameworks like Bootstrap instead of Tailwind CSS. The installation process remains the same; simply use `git clone -b raw https://github.com/cristianscheid/laravel-react-base.git` to clone the repository.
16 |
17 | ## Built With
18 |
19 | ![PHP][php-badge]
20 | ![Laravel][laravel-badge]
21 | ![MySQL][mysql-badge]
22 |
23 | ![JavaScript][javascript-badge]
24 | ![React][react-badge]
25 | ![Tailwind CSS][tailwindcss-badge]
26 |
27 | ## Visuals
28 |
29 | https://github.com/user-attachments/assets/6042e15b-dc1f-483a-b3a1-299daeeb0460
30 |
31 | ## Installation
32 |
33 | To get started with this project, follow the steps below:
34 |
35 | 1. **Clone the repository**
36 |
37 | ```
38 | git clone https://github.com/cristianscheid/laravel-react-base.git
39 | cd laravel-react-base
40 | ```
41 |
42 | 2. **Set up backend environment**
43 |
44 | - Navigate to the backend directory (`laravel-react-base/backend/`).
45 | - Install dependencies:
46 |
47 | ```
48 | composer install
49 | ```
50 |
51 | - Set up environment variables:
52 |
53 | ```
54 | cp .env.example .env
55 | ```
56 |
57 | > Open the `.env` file and configure your database and other settings (you can keep the default settings to use SQLite for simplicity).
58 |
59 | - Generate application key:
60 |
61 | ```
62 | php artisan key:generate --ansi
63 | ```
64 |
65 | - Run database migrations:
66 |
67 | ```
68 | php artisan migrate
69 | ```
70 |
71 | 3. **Set up frontend environment**
72 |
73 | - Navigate to the frontend directory (`laravel-react-base/frontend/`).
74 | - Install dependencies:
75 |
76 | ```
77 | npm install
78 | ```
79 |
80 | - Set up environment variables:
81 |
82 | ```
83 | cp .env.example .env
84 | ```
85 |
86 | > Open the `.env` file and set the API base URL to point to your Laravel application (default: `http://localhost:8000`).
87 |
88 | 4. **Start the servers**
89 |
90 | - From backend directory (`laravel-react-base/backend/`):
91 |
92 | ```
93 | php artisan serve
94 | ```
95 |
96 | - From frontend directory (`laravel-react-base/frontend/`):
97 |
98 | ```
99 | npm run dev
100 | ```
101 |
102 | ## Usage
103 |
104 | Once the application is running, you can access it at `http://localhost:5173`.
105 |
106 | ## License
107 |
108 | Distributed under the MIT License. See LICENSE.txt for more information.
109 |
110 |
111 |
112 | [php-badge]: https://img.shields.io/badge/PHP-8.3-gray?style=for-the-badge&logo=php&logoColor=white
113 | [laravel-badge]: https://img.shields.io/badge/Laravel-11.21-gray?style=for-the-badge&logo=laravel&logoColor=white
114 | [mysql-badge]: https://img.shields.io/badge/MySQL-8.0-gray?style=for-the-badge&logo=mysql&logoColor=white
115 | [javascript-badge]: https://img.shields.io/badge/JavaScript-ES6-gray?style=for-the-badge&logo=javascript&logoColor=white
116 | [react-badge]: https://img.shields.io/badge/React-18.3-gray?style=for-the-badge&logo=react&logoColor=white
117 | [tailwindcss-badge]: https://img.shields.io/badge/Tailwind%20CSS-3.4-gray?style=for-the-badge&logo=tailwindcss&logoColor=white
118 |
--------------------------------------------------------------------------------
/backend/.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 |
--------------------------------------------------------------------------------
/backend/.env.example:
--------------------------------------------------------------------------------
1 | APP_NAME=Laravel
2 | APP_ENV=local
3 | APP_KEY=
4 | APP_DEBUG=true
5 | APP_TIMEZONE=UTC
6 | APP_URL=http://localhost
7 |
8 | APP_LOCALE=en
9 | APP_FALLBACK_LOCALE=en
10 | APP_FAKER_LOCALE=en_US
11 |
12 | APP_MAINTENANCE_DRIVER=file
13 | # APP_MAINTENANCE_STORE=database
14 |
15 | BCRYPT_ROUNDS=12
16 |
17 | LOG_CHANNEL=stack
18 | LOG_STACK=single
19 | LOG_DEPRECATIONS_CHANNEL=null
20 | LOG_LEVEL=debug
21 |
22 | DB_CONNECTION=sqlite
23 | # DB_HOST=127.0.0.1
24 | # DB_PORT=3306
25 | # DB_DATABASE=laravel
26 | # DB_USERNAME=root
27 | # DB_PASSWORD=
28 |
29 | SESSION_DRIVER=database
30 | SESSION_LIFETIME=120
31 | SESSION_ENCRYPT=false
32 | SESSION_PATH=/
33 | SESSION_DOMAIN=null
34 |
35 | BROADCAST_CONNECTION=log
36 | FILESYSTEM_DISK=local
37 | QUEUE_CONNECTION=database
38 |
39 | CACHE_STORE=database
40 | CACHE_PREFIX=
41 |
42 | MEMCACHED_HOST=127.0.0.1
43 |
44 | REDIS_CLIENT=phpredis
45 | REDIS_HOST=127.0.0.1
46 | REDIS_PASSWORD=null
47 | REDIS_PORT=6379
48 |
49 | MAIL_MAILER=log
50 | MAIL_HOST=127.0.0.1
51 | MAIL_PORT=2525
52 | MAIL_USERNAME=null
53 | MAIL_PASSWORD=null
54 | MAIL_ENCRYPTION=null
55 | MAIL_FROM_ADDRESS="hello@example.com"
56 | MAIL_FROM_NAME="${APP_NAME}"
57 |
58 | AWS_ACCESS_KEY_ID=
59 | AWS_SECRET_ACCESS_KEY=
60 | AWS_DEFAULT_REGION=us-east-1
61 | AWS_BUCKET=
62 | AWS_USE_PATH_STYLE_ENDPOINT=false
63 |
64 | VITE_APP_NAME="${APP_NAME}"
65 |
--------------------------------------------------------------------------------
/backend/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=lf
2 |
3 | *.blade.php diff=html
4 | *.css diff=css
5 | *.html diff=html
6 | *.md diff=markdown
7 | *.php diff=php
8 |
9 | /.github export-ignore
10 | CHANGELOG.md export-ignore
11 | .styleci.yml export-ignore
12 |
--------------------------------------------------------------------------------
/backend/.gitignore:
--------------------------------------------------------------------------------
1 | /.phpunit.cache
2 | /node_modules
3 | /public/build
4 | /public/hot
5 | /public/storage
6 | /storage/*.key
7 | /vendor
8 | .env
9 | .env.backup
10 | .env.production
11 | .phpactor.json
12 | .phpunit.result.cache
13 | Homestead.json
14 | Homestead.yaml
15 | auth.json
16 | npm-debug.log
17 | yarn-error.log
18 | /.fleet
19 | /.idea
20 | /.vscode
21 |
--------------------------------------------------------------------------------
/backend/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | ## About Laravel
11 |
12 | Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as:
13 |
14 | - [Simple, fast routing engine](https://laravel.com/docs/routing).
15 | - [Powerful dependency injection container](https://laravel.com/docs/container).
16 | - Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage.
17 | - Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent).
18 | - Database agnostic [schema migrations](https://laravel.com/docs/migrations).
19 | - [Robust background job processing](https://laravel.com/docs/queues).
20 | - [Real-time event broadcasting](https://laravel.com/docs/broadcasting).
21 |
22 | Laravel is accessible, powerful, and provides tools required for large, robust applications.
23 |
24 | ## Learning Laravel
25 |
26 | Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework.
27 |
28 | You may also try the [Laravel Bootcamp](https://bootcamp.laravel.com), where you will be guided through building a modern Laravel application from scratch.
29 |
30 | If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains thousands of video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library.
31 |
32 | ## Laravel Sponsors
33 |
34 | We would like to extend our thanks to the following sponsors for funding Laravel development. If you are interested in becoming a sponsor, please visit the [Laravel Partners program](https://partners.laravel.com).
35 |
36 | ### Premium Partners
37 |
38 | - **[Vehikl](https://vehikl.com/)**
39 | - **[Tighten Co.](https://tighten.co)**
40 | - **[WebReinvent](https://webreinvent.com/)**
41 | - **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)**
42 | - **[64 Robots](https://64robots.com)**
43 | - **[Curotec](https://www.curotec.com/services/technologies/laravel/)**
44 | - **[Cyber-Duck](https://cyber-duck.co.uk)**
45 | - **[DevSquad](https://devsquad.com/hire-laravel-developers)**
46 | - **[Jump24](https://jump24.co.uk)**
47 | - **[Redberry](https://redberry.international/laravel/)**
48 | - **[Active Logic](https://activelogic.com)**
49 | - **[byte5](https://byte5.de)**
50 | - **[OP.GG](https://op.gg)**
51 |
52 | ## Contributing
53 |
54 | Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).
55 |
56 | ## Code of Conduct
57 |
58 | In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct).
59 |
60 | ## Security Vulnerabilities
61 |
62 | If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed.
63 |
64 | ## License
65 |
66 | The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).
67 |
--------------------------------------------------------------------------------
/backend/app/Http/Controllers/Api/AuthController.php:
--------------------------------------------------------------------------------
1 | validated();
17 |
18 | /** @var \App\Models\User $user */
19 | $user = User::create([
20 | 'name' => $data['name'],
21 | 'email' => $data['email'],
22 | 'password' => bcrypt($data['password']),
23 | ]);
24 |
25 | $token = $user->createToken('main')->plainTextToken;
26 |
27 | return response()->json([
28 | 'user' => $user,
29 | 'token' => $token,
30 | ]);
31 | }
32 |
33 | public function login(LoginRequest $request): JsonResponse
34 | {
35 | $data = $request->validated();
36 |
37 | if (! Auth::attempt($data)) {
38 | return response()->json([
39 | 'message' => 'Provided email address or password is incorrect.',
40 | 'errors' => [
41 | 'email_or_password' => [
42 | 'Provided email address or password is incorrect.',
43 | ],
44 | ],
45 | ], 422);
46 | }
47 |
48 | /** @var \App\Models\User $user */
49 | $user = Auth::user();
50 | $token = $user->createToken('main')->plainTextToken;
51 |
52 | return response()->json([
53 | 'user' => $user,
54 | 'token' => $token,
55 | ]);
56 | }
57 |
58 | public function logout(): JsonResponse
59 | {
60 | /** @var \App\Models\User $user */
61 | $user = Auth::user();
62 | $user->currentAccessToken()->delete();
63 |
64 | return response()->json(null, 204);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/backend/app/Http/Controllers/Api/UserController.php:
--------------------------------------------------------------------------------
1 | json($user);
20 | }
21 |
22 | public function update(UpdateUserRequest $request): JsonResponse
23 | {
24 | $data = $request->validated();
25 |
26 | /** @var \App\Models\User $user */
27 | $user = Auth::user();
28 | $user->update($data);
29 |
30 | return response()->json($user);
31 | }
32 |
33 | public function destroy(): JsonResponse
34 | {
35 | /** @var \App\Models\User $user */
36 | $user = Auth::user();
37 | $user->tokens()->delete();
38 | $user->delete();
39 |
40 | return response()->json(null, 204);
41 | }
42 |
43 | public function changePassword(
44 | UpdateUserPasswordRequest $request
45 | ): JsonResponse {
46 | $data = $request->validated();
47 |
48 | $currentPassword = $data['current_password'];
49 | $newPassword = $data['new_password'];
50 |
51 | /** @var \App\Models\User $user */
52 | $user = Auth::user();
53 |
54 | if (! Hash::check($currentPassword, $user->password)) {
55 | return response()->json([
56 | 'message' => 'The current password is incorrect.',
57 | 'errors' => [
58 | 'current_password' => [
59 | 'The current password is incorrect.',
60 | ],
61 | ],
62 | ], 422);
63 | }
64 |
65 | $user->password = bcrypt($newPassword);
66 | $user->save();
67 |
68 | return response()->json($user);
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/backend/app/Http/Controllers/Controller.php:
--------------------------------------------------------------------------------
1 | |string>
21 | */
22 | public function rules(): array
23 | {
24 | return [
25 | 'email' => 'required|email|exists:users,email',
26 | 'password' => 'required',
27 | ];
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/backend/app/Http/Requests/SignupRequest.php:
--------------------------------------------------------------------------------
1 | |string>
22 | */
23 | public function rules(): array
24 | {
25 | return [
26 | 'name' => 'required|string|max:55',
27 | 'email' => 'required|email|unique:users,email',
28 | 'password' => [
29 | 'required',
30 | 'confirmed',
31 | Password::min(8)
32 | ->letters()
33 | ->symbols(),
34 | ],
35 | ];
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/backend/app/Http/Requests/UpdateUserPasswordRequest.php:
--------------------------------------------------------------------------------
1 | |string>
22 | */
23 | public function rules(): array
24 | {
25 | return [
26 | 'current_password' => 'required',
27 | 'new_password' => [
28 | 'required',
29 | 'confirmed',
30 | Password::min(8)
31 | ->letters()
32 | ->symbols(),
33 | ],
34 | ];
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/backend/app/Http/Requests/UpdateUserRequest.php:
--------------------------------------------------------------------------------
1 | |string>
22 | */
23 | public function rules(): array
24 | {
25 | return [
26 | 'name' => 'required|string|max:55',
27 | 'email' => 'required|email|unique:users,email,'.Auth::id(),
28 | ];
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/backend/app/Http/Resources/UserResource.php:
--------------------------------------------------------------------------------
1 |
16 | */
17 | public function toArray(Request $request): array
18 | {
19 | return [
20 | 'id' => $this->id,
21 | 'name' => $this->name,
22 | 'email' => $this->email,
23 | 'created_at' => $this->created_at->format('Y-m-d H:i:s'),
24 | ];
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/backend/app/Models/User.php:
--------------------------------------------------------------------------------
1 |
21 | */
22 | protected $fillable = [
23 | 'name',
24 | 'email',
25 | 'password',
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 | * Get the attributes that should be cast.
40 | *
41 | * @return array
42 | */
43 | protected function casts(): array
44 | {
45 | return [
46 | 'email_verified_at' => 'datetime',
47 | 'password' => 'hashed',
48 | ];
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/backend/app/Providers/AppServiceProvider.php:
--------------------------------------------------------------------------------
1 | handleCommand(new ArgvInput);
14 |
15 | exit($status);
16 |
--------------------------------------------------------------------------------
/backend/bootstrap/app.php:
--------------------------------------------------------------------------------
1 | withRouting(
9 | web: __DIR__.'/../routes/web.php',
10 | api: __DIR__.'/../routes/api.php',
11 | commands: __DIR__.'/../routes/console.php',
12 | health: '/up',
13 | )
14 | ->withMiddleware(function (Middleware $middleware) {
15 | //
16 | })
17 | ->withExceptions(function (Exceptions $exceptions) {
18 | //
19 | })->create();
20 |
--------------------------------------------------------------------------------
/backend/bootstrap/cache/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/backend/bootstrap/providers.php:
--------------------------------------------------------------------------------
1 | env('APP_NAME', 'Laravel'),
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Application Environment
21 | |--------------------------------------------------------------------------
22 | |
23 | | This value determines the "environment" your application is currently
24 | | running in. This may determine how you prefer to configure various
25 | | services the application utilizes. Set this in your ".env" file.
26 | |
27 | */
28 |
29 | 'env' => env('APP_ENV', 'production'),
30 |
31 | /*
32 | |--------------------------------------------------------------------------
33 | | Application Debug Mode
34 | |--------------------------------------------------------------------------
35 | |
36 | | When your application is in debug mode, detailed error messages with
37 | | stack traces will be shown on every error that occurs within your
38 | | application. If disabled, a simple generic error page is shown.
39 | |
40 | */
41 |
42 | 'debug' => (bool) env('APP_DEBUG', false),
43 |
44 | /*
45 | |--------------------------------------------------------------------------
46 | | Application URL
47 | |--------------------------------------------------------------------------
48 | |
49 | | This URL is used by the console to properly generate URLs when using
50 | | the Artisan command line tool. You should set this to the root of
51 | | the application so that it's available within Artisan commands.
52 | |
53 | */
54 |
55 | 'url' => env('APP_URL', 'http://localhost'),
56 |
57 | /*
58 | |--------------------------------------------------------------------------
59 | | Application Timezone
60 | |--------------------------------------------------------------------------
61 | |
62 | | Here you may specify the default timezone for your application, which
63 | | will be used by the PHP date and date-time functions. The timezone
64 | | is set to "UTC" by default as it is suitable for most use cases.
65 | |
66 | */
67 |
68 | 'timezone' => env('APP_TIMEZONE', 'UTC'),
69 |
70 | /*
71 | |--------------------------------------------------------------------------
72 | | Application Locale Configuration
73 | |--------------------------------------------------------------------------
74 | |
75 | | The application locale determines the default locale that will be used
76 | | by Laravel's translation / localization methods. This option can be
77 | | set to any locale for which you plan to have translation strings.
78 | |
79 | */
80 |
81 | 'locale' => env('APP_LOCALE', 'en'),
82 |
83 | 'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'),
84 |
85 | 'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'),
86 |
87 | /*
88 | |--------------------------------------------------------------------------
89 | | Encryption Key
90 | |--------------------------------------------------------------------------
91 | |
92 | | This key is utilized by Laravel's encryption services and should be set
93 | | to a random, 32 character string to ensure that all encrypted values
94 | | are secure. You should do this prior to deploying the application.
95 | |
96 | */
97 |
98 | 'cipher' => 'AES-256-CBC',
99 |
100 | 'key' => env('APP_KEY'),
101 |
102 | 'previous_keys' => [
103 | ...array_filter(
104 | explode(',', env('APP_PREVIOUS_KEYS', ''))
105 | ),
106 | ],
107 |
108 | /*
109 | |--------------------------------------------------------------------------
110 | | Maintenance Mode Driver
111 | |--------------------------------------------------------------------------
112 | |
113 | | These configuration options determine the driver used to determine and
114 | | manage Laravel's "maintenance mode" status. The "cache" driver will
115 | | allow maintenance mode to be controlled across multiple machines.
116 | |
117 | | Supported drivers: "file", "cache"
118 | |
119 | */
120 |
121 | 'maintenance' => [
122 | 'driver' => env('APP_MAINTENANCE_DRIVER', 'file'),
123 | 'store' => env('APP_MAINTENANCE_STORE', 'database'),
124 | ],
125 |
126 | ];
127 |
--------------------------------------------------------------------------------
/backend/config/auth.php:
--------------------------------------------------------------------------------
1 | [
17 | 'guard' => env('AUTH_GUARD', 'web'),
18 | 'passwords' => env('AUTH_PASSWORD_BROKER', 'users'),
19 | ],
20 |
21 | /*
22 | |--------------------------------------------------------------------------
23 | | Authentication Guards
24 | |--------------------------------------------------------------------------
25 | |
26 | | Next, you may define every authentication guard for your application.
27 | | Of course, a great default configuration has been defined for you
28 | | which utilizes session storage plus the Eloquent user provider.
29 | |
30 | | All authentication guards have a user provider, which defines how the
31 | | users are actually retrieved out of your database or other storage
32 | | system used by the application. Typically, Eloquent is utilized.
33 | |
34 | | Supported: "session"
35 | |
36 | */
37 |
38 | 'guards' => [
39 | 'web' => [
40 | 'driver' => 'session',
41 | 'provider' => 'users',
42 | ],
43 | ],
44 |
45 | /*
46 | |--------------------------------------------------------------------------
47 | | User Providers
48 | |--------------------------------------------------------------------------
49 | |
50 | | All authentication guards have a user provider, which defines how the
51 | | users are actually retrieved out of your database or other storage
52 | | system used by the application. Typically, Eloquent is utilized.
53 | |
54 | | If you have multiple user tables or models you may configure multiple
55 | | providers to represent the model / table. These providers may then
56 | | be assigned to any extra authentication guards you have defined.
57 | |
58 | | Supported: "database", "eloquent"
59 | |
60 | */
61 |
62 | 'providers' => [
63 | 'users' => [
64 | 'driver' => 'eloquent',
65 | 'model' => env('AUTH_MODEL', App\Models\User::class),
66 | ],
67 |
68 | // 'users' => [
69 | // 'driver' => 'database',
70 | // 'table' => 'users',
71 | // ],
72 | ],
73 |
74 | /*
75 | |--------------------------------------------------------------------------
76 | | Resetting Passwords
77 | |--------------------------------------------------------------------------
78 | |
79 | | These configuration options specify the behavior of Laravel's password
80 | | reset functionality, including the table utilized for token storage
81 | | and the user provider that is invoked to actually retrieve users.
82 | |
83 | | The expiry time is the number of minutes that each reset token will be
84 | | considered valid. This security feature keeps tokens short-lived so
85 | | they have less time to be guessed. You may change this as needed.
86 | |
87 | | The throttle setting is the number of seconds a user must wait before
88 | | generating more password reset tokens. This prevents the user from
89 | | quickly generating a very large amount of password reset tokens.
90 | |
91 | */
92 |
93 | 'passwords' => [
94 | 'users' => [
95 | 'provider' => 'users',
96 | 'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'),
97 | 'expire' => 60,
98 | 'throttle' => 60,
99 | ],
100 | ],
101 |
102 | /*
103 | |--------------------------------------------------------------------------
104 | | Password Confirmation Timeout
105 | |--------------------------------------------------------------------------
106 | |
107 | | Here you may define the amount of seconds before a password confirmation
108 | | window expires and users are asked to re-enter their password via the
109 | | confirmation screen. By default, the timeout lasts for three hours.
110 | |
111 | */
112 |
113 | 'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800),
114 |
115 | ];
116 |
--------------------------------------------------------------------------------
/backend/config/cache.php:
--------------------------------------------------------------------------------
1 | env('CACHE_STORE', 'database'),
19 |
20 | /*
21 | |--------------------------------------------------------------------------
22 | | Cache Stores
23 | |--------------------------------------------------------------------------
24 | |
25 | | Here you may define all of the cache "stores" for your application as
26 | | well as their drivers. You may even define multiple stores for the
27 | | same cache driver to group types of items stored in your caches.
28 | |
29 | | Supported drivers: "array", "database", "file", "memcached",
30 | | "redis", "dynamodb", "octane", "null"
31 | |
32 | */
33 |
34 | 'stores' => [
35 |
36 | 'array' => [
37 | 'driver' => 'array',
38 | 'serialize' => false,
39 | ],
40 |
41 | 'database' => [
42 | 'driver' => 'database',
43 | 'connection' => env('DB_CACHE_CONNECTION'),
44 | 'table' => env('DB_CACHE_TABLE', 'cache'),
45 | 'lock_connection' => env('DB_CACHE_LOCK_CONNECTION'),
46 | 'lock_table' => env('DB_CACHE_LOCK_TABLE'),
47 | ],
48 |
49 | 'file' => [
50 | 'driver' => 'file',
51 | 'path' => storage_path('framework/cache/data'),
52 | 'lock_path' => storage_path('framework/cache/data'),
53 | ],
54 |
55 | 'memcached' => [
56 | 'driver' => 'memcached',
57 | 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'),
58 | 'sasl' => [
59 | env('MEMCACHED_USERNAME'),
60 | env('MEMCACHED_PASSWORD'),
61 | ],
62 | 'options' => [
63 | // Memcached::OPT_CONNECT_TIMEOUT => 2000,
64 | ],
65 | 'servers' => [
66 | [
67 | 'host' => env('MEMCACHED_HOST', '127.0.0.1'),
68 | 'port' => env('MEMCACHED_PORT', 11211),
69 | 'weight' => 100,
70 | ],
71 | ],
72 | ],
73 |
74 | 'redis' => [
75 | 'driver' => 'redis',
76 | 'connection' => env('REDIS_CACHE_CONNECTION', 'cache'),
77 | 'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'),
78 | ],
79 |
80 | 'dynamodb' => [
81 | 'driver' => 'dynamodb',
82 | 'key' => env('AWS_ACCESS_KEY_ID'),
83 | 'secret' => env('AWS_SECRET_ACCESS_KEY'),
84 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
85 | 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),
86 | 'endpoint' => env('DYNAMODB_ENDPOINT'),
87 | ],
88 |
89 | 'octane' => [
90 | 'driver' => 'octane',
91 | ],
92 |
93 | ],
94 |
95 | /*
96 | |--------------------------------------------------------------------------
97 | | Cache Key Prefix
98 | |--------------------------------------------------------------------------
99 | |
100 | | When utilizing the APC, database, memcached, Redis, and DynamoDB cache
101 | | stores, there might be other applications using the same cache. For
102 | | that reason, you may prefix every cache key to avoid collisions.
103 | |
104 | */
105 |
106 | 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'),
107 |
108 | ];
109 |
--------------------------------------------------------------------------------
/backend/config/database.php:
--------------------------------------------------------------------------------
1 | env('DB_CONNECTION', 'sqlite'),
20 |
21 | /*
22 | |--------------------------------------------------------------------------
23 | | Database Connections
24 | |--------------------------------------------------------------------------
25 | |
26 | | Below are all of the database connections defined for your application.
27 | | An example configuration is provided for each database system which
28 | | is supported by Laravel. You're free to add / remove connections.
29 | |
30 | */
31 |
32 | 'connections' => [
33 |
34 | 'sqlite' => [
35 | 'driver' => 'sqlite',
36 | 'url' => env('DB_URL'),
37 | 'database' => env('DB_DATABASE', database_path('database.sqlite')),
38 | 'prefix' => '',
39 | 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
40 | 'busy_timeout' => null,
41 | 'journal_mode' => null,
42 | 'synchronous' => null,
43 | ],
44 |
45 | 'mysql' => [
46 | 'driver' => 'mysql',
47 | 'url' => env('DB_URL'),
48 | 'host' => env('DB_HOST', '127.0.0.1'),
49 | 'port' => env('DB_PORT', '3306'),
50 | 'database' => env('DB_DATABASE', 'laravel'),
51 | 'username' => env('DB_USERNAME', 'root'),
52 | 'password' => env('DB_PASSWORD', ''),
53 | 'unix_socket' => env('DB_SOCKET', ''),
54 | 'charset' => env('DB_CHARSET', 'utf8mb4'),
55 | 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
56 | 'prefix' => '',
57 | 'prefix_indexes' => true,
58 | 'strict' => true,
59 | 'engine' => null,
60 | 'options' => extension_loaded('pdo_mysql') ? array_filter([
61 | PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
62 | ]) : [],
63 | ],
64 |
65 | 'mariadb' => [
66 | 'driver' => 'mariadb',
67 | 'url' => env('DB_URL'),
68 | 'host' => env('DB_HOST', '127.0.0.1'),
69 | 'port' => env('DB_PORT', '3306'),
70 | 'database' => env('DB_DATABASE', 'laravel'),
71 | 'username' => env('DB_USERNAME', 'root'),
72 | 'password' => env('DB_PASSWORD', ''),
73 | 'unix_socket' => env('DB_SOCKET', ''),
74 | 'charset' => env('DB_CHARSET', 'utf8mb4'),
75 | 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
76 | 'prefix' => '',
77 | 'prefix_indexes' => true,
78 | 'strict' => true,
79 | 'engine' => null,
80 | 'options' => extension_loaded('pdo_mysql') ? array_filter([
81 | PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
82 | ]) : [],
83 | ],
84 |
85 | 'pgsql' => [
86 | 'driver' => 'pgsql',
87 | 'url' => env('DB_URL'),
88 | 'host' => env('DB_HOST', '127.0.0.1'),
89 | 'port' => env('DB_PORT', '5432'),
90 | 'database' => env('DB_DATABASE', 'laravel'),
91 | 'username' => env('DB_USERNAME', 'root'),
92 | 'password' => env('DB_PASSWORD', ''),
93 | 'charset' => env('DB_CHARSET', 'utf8'),
94 | 'prefix' => '',
95 | 'prefix_indexes' => true,
96 | 'search_path' => 'public',
97 | 'sslmode' => 'prefer',
98 | ],
99 |
100 | 'sqlsrv' => [
101 | 'driver' => 'sqlsrv',
102 | 'url' => env('DB_URL'),
103 | 'host' => env('DB_HOST', 'localhost'),
104 | 'port' => env('DB_PORT', '1433'),
105 | 'database' => env('DB_DATABASE', 'laravel'),
106 | 'username' => env('DB_USERNAME', 'root'),
107 | 'password' => env('DB_PASSWORD', ''),
108 | 'charset' => env('DB_CHARSET', 'utf8'),
109 | 'prefix' => '',
110 | 'prefix_indexes' => true,
111 | // 'encrypt' => env('DB_ENCRYPT', 'yes'),
112 | // 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'),
113 | ],
114 |
115 | ],
116 |
117 | /*
118 | |--------------------------------------------------------------------------
119 | | Migration Repository Table
120 | |--------------------------------------------------------------------------
121 | |
122 | | This table keeps track of all the migrations that have already run for
123 | | your application. Using this information, we can determine which of
124 | | the migrations on disk haven't actually been run on the database.
125 | |
126 | */
127 |
128 | 'migrations' => [
129 | 'table' => 'migrations',
130 | 'update_date_on_publish' => true,
131 | ],
132 |
133 | /*
134 | |--------------------------------------------------------------------------
135 | | Redis Databases
136 | |--------------------------------------------------------------------------
137 | |
138 | | Redis is an open source, fast, and advanced key-value store that also
139 | | provides a richer body of commands than a typical key-value system
140 | | such as Memcached. You may define your connection settings here.
141 | |
142 | */
143 |
144 | 'redis' => [
145 |
146 | 'client' => env('REDIS_CLIENT', 'phpredis'),
147 |
148 | 'options' => [
149 | 'cluster' => env('REDIS_CLUSTER', 'redis'),
150 | 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
151 | ],
152 |
153 | 'default' => [
154 | 'url' => env('REDIS_URL'),
155 | 'host' => env('REDIS_HOST', '127.0.0.1'),
156 | 'username' => env('REDIS_USERNAME'),
157 | 'password' => env('REDIS_PASSWORD'),
158 | 'port' => env('REDIS_PORT', '6379'),
159 | 'database' => env('REDIS_DB', '0'),
160 | ],
161 |
162 | 'cache' => [
163 | 'url' => env('REDIS_URL'),
164 | 'host' => env('REDIS_HOST', '127.0.0.1'),
165 | 'username' => env('REDIS_USERNAME'),
166 | 'password' => env('REDIS_PASSWORD'),
167 | 'port' => env('REDIS_PORT', '6379'),
168 | 'database' => env('REDIS_CACHE_DB', '1'),
169 | ],
170 |
171 | ],
172 |
173 | ];
174 |
--------------------------------------------------------------------------------
/backend/config/filesystems.php:
--------------------------------------------------------------------------------
1 | env('FILESYSTEM_DISK', 'local'),
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Filesystem Disks
21 | |--------------------------------------------------------------------------
22 | |
23 | | Below you may configure as many filesystem disks as necessary, and you
24 | | may even configure multiple disks for the same driver. Examples for
25 | | most supported storage drivers are configured here for reference.
26 | |
27 | | Supported drivers: "local", "ftp", "sftp", "s3"
28 | |
29 | */
30 |
31 | 'disks' => [
32 |
33 | 'local' => [
34 | 'driver' => 'local',
35 | 'root' => storage_path('app'),
36 | 'throw' => false,
37 | ],
38 |
39 | 'public' => [
40 | 'driver' => 'local',
41 | 'root' => storage_path('app/public'),
42 | 'url' => env('APP_URL').'/storage',
43 | 'visibility' => 'public',
44 | 'throw' => false,
45 | ],
46 |
47 | 's3' => [
48 | 'driver' => 's3',
49 | 'key' => env('AWS_ACCESS_KEY_ID'),
50 | 'secret' => env('AWS_SECRET_ACCESS_KEY'),
51 | 'region' => env('AWS_DEFAULT_REGION'),
52 | 'bucket' => env('AWS_BUCKET'),
53 | 'url' => env('AWS_URL'),
54 | 'endpoint' => env('AWS_ENDPOINT'),
55 | 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
56 | 'throw' => false,
57 | ],
58 |
59 | ],
60 |
61 | /*
62 | |--------------------------------------------------------------------------
63 | | Symbolic Links
64 | |--------------------------------------------------------------------------
65 | |
66 | | Here you may configure the symbolic links that will be created when the
67 | | `storage:link` Artisan command is executed. The array keys should be
68 | | the locations of the links and the values should be their targets.
69 | |
70 | */
71 |
72 | 'links' => [
73 | public_path('storage') => storage_path('app/public'),
74 | ],
75 |
76 | ];
77 |
--------------------------------------------------------------------------------
/backend/config/logging.php:
--------------------------------------------------------------------------------
1 | env('LOG_CHANNEL', 'stack'),
22 |
23 | /*
24 | |--------------------------------------------------------------------------
25 | | Deprecations Log Channel
26 | |--------------------------------------------------------------------------
27 | |
28 | | This option controls the log channel that should be used to log warnings
29 | | regarding deprecated PHP and library features. This allows you to get
30 | | your application ready for upcoming major versions of dependencies.
31 | |
32 | */
33 |
34 | 'deprecations' => [
35 | 'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
36 | 'trace' => env('LOG_DEPRECATIONS_TRACE', false),
37 | ],
38 |
39 | /*
40 | |--------------------------------------------------------------------------
41 | | Log Channels
42 | |--------------------------------------------------------------------------
43 | |
44 | | Here you may configure the log channels for your application. Laravel
45 | | utilizes the Monolog PHP logging library, which includes a variety
46 | | of powerful log handlers and formatters that you're free to use.
47 | |
48 | | Available drivers: "single", "daily", "slack", "syslog",
49 | | "errorlog", "monolog", "custom", "stack"
50 | |
51 | */
52 |
53 | 'channels' => [
54 |
55 | 'stack' => [
56 | 'driver' => 'stack',
57 | 'channels' => explode(',', env('LOG_STACK', 'single')),
58 | 'ignore_exceptions' => false,
59 | ],
60 |
61 | 'single' => [
62 | 'driver' => 'single',
63 | 'path' => storage_path('logs/laravel.log'),
64 | 'level' => env('LOG_LEVEL', 'debug'),
65 | 'replace_placeholders' => true,
66 | ],
67 |
68 | 'daily' => [
69 | 'driver' => 'daily',
70 | 'path' => storage_path('logs/laravel.log'),
71 | 'level' => env('LOG_LEVEL', 'debug'),
72 | 'days' => env('LOG_DAILY_DAYS', 14),
73 | 'replace_placeholders' => true,
74 | ],
75 |
76 | 'slack' => [
77 | 'driver' => 'slack',
78 | 'url' => env('LOG_SLACK_WEBHOOK_URL'),
79 | 'username' => env('LOG_SLACK_USERNAME', 'Laravel Log'),
80 | 'emoji' => env('LOG_SLACK_EMOJI', ':boom:'),
81 | 'level' => env('LOG_LEVEL', 'critical'),
82 | 'replace_placeholders' => true,
83 | ],
84 |
85 | 'papertrail' => [
86 | 'driver' => 'monolog',
87 | 'level' => env('LOG_LEVEL', 'debug'),
88 | 'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class),
89 | 'handler_with' => [
90 | 'host' => env('PAPERTRAIL_URL'),
91 | 'port' => env('PAPERTRAIL_PORT'),
92 | 'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'),
93 | ],
94 | 'processors' => [PsrLogMessageProcessor::class],
95 | ],
96 |
97 | 'stderr' => [
98 | 'driver' => 'monolog',
99 | 'level' => env('LOG_LEVEL', 'debug'),
100 | 'handler' => StreamHandler::class,
101 | 'formatter' => env('LOG_STDERR_FORMATTER'),
102 | 'with' => [
103 | 'stream' => 'php://stderr',
104 | ],
105 | 'processors' => [PsrLogMessageProcessor::class],
106 | ],
107 |
108 | 'syslog' => [
109 | 'driver' => 'syslog',
110 | 'level' => env('LOG_LEVEL', 'debug'),
111 | 'facility' => env('LOG_SYSLOG_FACILITY', LOG_USER),
112 | 'replace_placeholders' => true,
113 | ],
114 |
115 | 'errorlog' => [
116 | 'driver' => 'errorlog',
117 | 'level' => env('LOG_LEVEL', 'debug'),
118 | 'replace_placeholders' => true,
119 | ],
120 |
121 | 'null' => [
122 | 'driver' => 'monolog',
123 | 'handler' => NullHandler::class,
124 | ],
125 |
126 | 'emergency' => [
127 | 'path' => storage_path('logs/laravel.log'),
128 | ],
129 |
130 | ],
131 |
132 | ];
133 |
--------------------------------------------------------------------------------
/backend/config/mail.php:
--------------------------------------------------------------------------------
1 | env('MAIL_MAILER', 'log'),
18 |
19 | /*
20 | |--------------------------------------------------------------------------
21 | | Mailer Configurations
22 | |--------------------------------------------------------------------------
23 | |
24 | | Here you may configure all of the mailers used by your application plus
25 | | their respective settings. Several examples have been configured for
26 | | you and you are free to add your own as your application requires.
27 | |
28 | | Laravel supports a variety of mail "transport" drivers that can be used
29 | | when delivering an email. You may specify which one you're using for
30 | | your mailers below. You may also add additional mailers if needed.
31 | |
32 | | Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2",
33 | | "postmark", "resend", "log", "array",
34 | | "failover", "roundrobin"
35 | |
36 | */
37 |
38 | 'mailers' => [
39 |
40 | 'smtp' => [
41 | 'transport' => 'smtp',
42 | 'url' => env('MAIL_URL'),
43 | 'host' => env('MAIL_HOST', '127.0.0.1'),
44 | 'port' => env('MAIL_PORT', 2525),
45 | 'encryption' => env('MAIL_ENCRYPTION', 'tls'),
46 | 'username' => env('MAIL_USERNAME'),
47 | 'password' => env('MAIL_PASSWORD'),
48 | 'timeout' => null,
49 | 'local_domain' => env('MAIL_EHLO_DOMAIN', parse_url(env('APP_URL', 'http://localhost'), PHP_URL_HOST)),
50 | ],
51 |
52 | 'ses' => [
53 | 'transport' => 'ses',
54 | ],
55 |
56 | 'postmark' => [
57 | 'transport' => 'postmark',
58 | // 'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'),
59 | // 'client' => [
60 | // 'timeout' => 5,
61 | // ],
62 | ],
63 |
64 | 'resend' => [
65 | 'transport' => 'resend',
66 | ],
67 |
68 | 'sendmail' => [
69 | 'transport' => 'sendmail',
70 | 'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'),
71 | ],
72 |
73 | 'log' => [
74 | 'transport' => 'log',
75 | 'channel' => env('MAIL_LOG_CHANNEL'),
76 | ],
77 |
78 | 'array' => [
79 | 'transport' => 'array',
80 | ],
81 |
82 | 'failover' => [
83 | 'transport' => 'failover',
84 | 'mailers' => [
85 | 'smtp',
86 | 'log',
87 | ],
88 | ],
89 |
90 | 'roundrobin' => [
91 | 'transport' => 'roundrobin',
92 | 'mailers' => [
93 | 'ses',
94 | 'postmark',
95 | ],
96 | ],
97 |
98 | ],
99 |
100 | /*
101 | |--------------------------------------------------------------------------
102 | | Global "From" Address
103 | |--------------------------------------------------------------------------
104 | |
105 | | You may wish for all emails sent by your application to be sent from
106 | | the same address. Here you may specify a name and address that is
107 | | used globally for all emails that are sent by your application.
108 | |
109 | */
110 |
111 | 'from' => [
112 | 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
113 | 'name' => env('MAIL_FROM_NAME', 'Example'),
114 | ],
115 |
116 | ];
117 |
--------------------------------------------------------------------------------
/backend/config/queue.php:
--------------------------------------------------------------------------------
1 | env('QUEUE_CONNECTION', 'database'),
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Queue Connections
21 | |--------------------------------------------------------------------------
22 | |
23 | | Here you may configure the connection options for every queue backend
24 | | used by your application. An example configuration is provided for
25 | | each backend supported by Laravel. You're also free to add more.
26 | |
27 | | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null"
28 | |
29 | */
30 |
31 | 'connections' => [
32 |
33 | 'sync' => [
34 | 'driver' => 'sync',
35 | ],
36 |
37 | 'database' => [
38 | 'driver' => 'database',
39 | 'connection' => env('DB_QUEUE_CONNECTION'),
40 | 'table' => env('DB_QUEUE_TABLE', 'jobs'),
41 | 'queue' => env('DB_QUEUE', 'default'),
42 | 'retry_after' => (int) env('DB_QUEUE_RETRY_AFTER', 90),
43 | 'after_commit' => false,
44 | ],
45 |
46 | 'beanstalkd' => [
47 | 'driver' => 'beanstalkd',
48 | 'host' => env('BEANSTALKD_QUEUE_HOST', 'localhost'),
49 | 'queue' => env('BEANSTALKD_QUEUE', 'default'),
50 | 'retry_after' => (int) env('BEANSTALKD_QUEUE_RETRY_AFTER', 90),
51 | 'block_for' => 0,
52 | 'after_commit' => false,
53 | ],
54 |
55 | 'sqs' => [
56 | 'driver' => 'sqs',
57 | 'key' => env('AWS_ACCESS_KEY_ID'),
58 | 'secret' => env('AWS_SECRET_ACCESS_KEY'),
59 | 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
60 | 'queue' => env('SQS_QUEUE', 'default'),
61 | 'suffix' => env('SQS_SUFFIX'),
62 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
63 | 'after_commit' => false,
64 | ],
65 |
66 | 'redis' => [
67 | 'driver' => 'redis',
68 | 'connection' => env('REDIS_QUEUE_CONNECTION', 'default'),
69 | 'queue' => env('REDIS_QUEUE', 'default'),
70 | 'retry_after' => (int) env('REDIS_QUEUE_RETRY_AFTER', 90),
71 | 'block_for' => null,
72 | 'after_commit' => false,
73 | ],
74 |
75 | ],
76 |
77 | /*
78 | |--------------------------------------------------------------------------
79 | | Job Batching
80 | |--------------------------------------------------------------------------
81 | |
82 | | The following options configure the database and table that store job
83 | | batching information. These options can be updated to any database
84 | | connection and table which has been defined by your application.
85 | |
86 | */
87 |
88 | 'batching' => [
89 | 'database' => env('DB_CONNECTION', 'sqlite'),
90 | 'table' => 'job_batches',
91 | ],
92 |
93 | /*
94 | |--------------------------------------------------------------------------
95 | | Failed Queue Jobs
96 | |--------------------------------------------------------------------------
97 | |
98 | | These options configure the behavior of failed queue job logging so you
99 | | can control how and where failed jobs are stored. Laravel ships with
100 | | support for storing failed jobs in a simple file or in a database.
101 | |
102 | | Supported drivers: "database-uuids", "dynamodb", "file", "null"
103 | |
104 | */
105 |
106 | 'failed' => [
107 | 'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'),
108 | 'database' => env('DB_CONNECTION', 'sqlite'),
109 | 'table' => 'failed_jobs',
110 | ],
111 |
112 | ];
113 |
--------------------------------------------------------------------------------
/backend/config/sanctum.php:
--------------------------------------------------------------------------------
1 | explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
19 | '%s%s',
20 | 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
21 | Sanctum::currentApplicationUrlWithPort()
22 | ))),
23 |
24 | /*
25 | |--------------------------------------------------------------------------
26 | | Sanctum Guards
27 | |--------------------------------------------------------------------------
28 | |
29 | | This array contains the authentication guards that will be checked when
30 | | Sanctum is trying to authenticate a request. If none of these guards
31 | | are able to authenticate the request, Sanctum will use the bearer
32 | | token that's present on an incoming request for authentication.
33 | |
34 | */
35 |
36 | 'guard' => ['web'],
37 |
38 | /*
39 | |--------------------------------------------------------------------------
40 | | Expiration Minutes
41 | |--------------------------------------------------------------------------
42 | |
43 | | This value controls the number of minutes until an issued token will be
44 | | considered expired. This will override any values set in the token's
45 | | "expires_at" attribute, but first-party sessions are not affected.
46 | |
47 | */
48 |
49 | 'expiration' => null,
50 |
51 | /*
52 | |--------------------------------------------------------------------------
53 | | Token Prefix
54 | |--------------------------------------------------------------------------
55 | |
56 | | Sanctum can prefix new tokens in order to take advantage of numerous
57 | | security scanning initiatives maintained by open source platforms
58 | | that notify developers if they commit tokens into repositories.
59 | |
60 | | See: https://docs.github.com/en/code-security/secret-scanning/about-secret-scanning
61 | |
62 | */
63 |
64 | 'token_prefix' => env('SANCTUM_TOKEN_PREFIX', ''),
65 |
66 | /*
67 | |--------------------------------------------------------------------------
68 | | Sanctum Middleware
69 | |--------------------------------------------------------------------------
70 | |
71 | | When authenticating your first-party SPA with Sanctum you may need to
72 | | customize some of the middleware Sanctum uses while processing the
73 | | request. You may change the middleware listed below as required.
74 | |
75 | */
76 |
77 | 'middleware' => [
78 | 'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class,
79 | 'encrypt_cookies' => Illuminate\Cookie\Middleware\EncryptCookies::class,
80 | 'validate_csrf_token' => Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class,
81 | ],
82 |
83 | ];
84 |
--------------------------------------------------------------------------------
/backend/config/services.php:
--------------------------------------------------------------------------------
1 | [
18 | 'token' => env('POSTMARK_TOKEN'),
19 | ],
20 |
21 | 'ses' => [
22 | 'key' => env('AWS_ACCESS_KEY_ID'),
23 | 'secret' => env('AWS_SECRET_ACCESS_KEY'),
24 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
25 | ],
26 |
27 | 'resend' => [
28 | 'key' => env('RESEND_KEY'),
29 | ],
30 |
31 | 'slack' => [
32 | 'notifications' => [
33 | 'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'),
34 | 'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'),
35 | ],
36 | ],
37 |
38 | ];
39 |
--------------------------------------------------------------------------------
/backend/config/session.php:
--------------------------------------------------------------------------------
1 | env('SESSION_DRIVER', 'database'),
22 |
23 | /*
24 | |--------------------------------------------------------------------------
25 | | Session Lifetime
26 | |--------------------------------------------------------------------------
27 | |
28 | | Here you may specify the number of minutes that you wish the session
29 | | to be allowed to remain idle before it expires. If you want them
30 | | to expire immediately when the browser is closed then you may
31 | | indicate that via the expire_on_close configuration option.
32 | |
33 | */
34 |
35 | 'lifetime' => env('SESSION_LIFETIME', 120),
36 |
37 | 'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false),
38 |
39 | /*
40 | |--------------------------------------------------------------------------
41 | | Session Encryption
42 | |--------------------------------------------------------------------------
43 | |
44 | | This option allows you to easily specify that all of your session data
45 | | should be encrypted before it's stored. All encryption is performed
46 | | automatically by Laravel and you may use the session like normal.
47 | |
48 | */
49 |
50 | 'encrypt' => env('SESSION_ENCRYPT', false),
51 |
52 | /*
53 | |--------------------------------------------------------------------------
54 | | Session File Location
55 | |--------------------------------------------------------------------------
56 | |
57 | | When utilizing the "file" session driver, the session files are placed
58 | | on disk. The default storage location is defined here; however, you
59 | | are free to provide another location where they should be stored.
60 | |
61 | */
62 |
63 | 'files' => storage_path('framework/sessions'),
64 |
65 | /*
66 | |--------------------------------------------------------------------------
67 | | Session Database Connection
68 | |--------------------------------------------------------------------------
69 | |
70 | | When using the "database" or "redis" session drivers, you may specify a
71 | | connection that should be used to manage these sessions. This should
72 | | correspond to a connection in your database configuration options.
73 | |
74 | */
75 |
76 | 'connection' => env('SESSION_CONNECTION'),
77 |
78 | /*
79 | |--------------------------------------------------------------------------
80 | | Session Database Table
81 | |--------------------------------------------------------------------------
82 | |
83 | | When using the "database" session driver, you may specify the table to
84 | | be used to store sessions. Of course, a sensible default is defined
85 | | for you; however, you're welcome to change this to another table.
86 | |
87 | */
88 |
89 | 'table' => env('SESSION_TABLE', 'sessions'),
90 |
91 | /*
92 | |--------------------------------------------------------------------------
93 | | Session Cache Store
94 | |--------------------------------------------------------------------------
95 | |
96 | | When using one of the framework's cache driven session backends, you may
97 | | define the cache store which should be used to store the session data
98 | | between requests. This must match one of your defined cache stores.
99 | |
100 | | Affects: "apc", "dynamodb", "memcached", "redis"
101 | |
102 | */
103 |
104 | 'store' => env('SESSION_STORE'),
105 |
106 | /*
107 | |--------------------------------------------------------------------------
108 | | Session Sweeping Lottery
109 | |--------------------------------------------------------------------------
110 | |
111 | | Some session drivers must manually sweep their storage location to get
112 | | rid of old sessions from storage. Here are the chances that it will
113 | | happen on a given request. By default, the odds are 2 out of 100.
114 | |
115 | */
116 |
117 | 'lottery' => [2, 100],
118 |
119 | /*
120 | |--------------------------------------------------------------------------
121 | | Session Cookie Name
122 | |--------------------------------------------------------------------------
123 | |
124 | | Here you may change the name of the session cookie that is created by
125 | | the framework. Typically, you should not need to change this value
126 | | since doing so does not grant a meaningful security improvement.
127 | |
128 | */
129 |
130 | 'cookie' => env(
131 | 'SESSION_COOKIE',
132 | Str::slug(env('APP_NAME', 'laravel'), '_').'_session'
133 | ),
134 |
135 | /*
136 | |--------------------------------------------------------------------------
137 | | Session Cookie Path
138 | |--------------------------------------------------------------------------
139 | |
140 | | The session cookie path determines the path for which the cookie will
141 | | be regarded as available. Typically, this will be the root path of
142 | | your application, but you're free to change this when necessary.
143 | |
144 | */
145 |
146 | 'path' => env('SESSION_PATH', '/'),
147 |
148 | /*
149 | |--------------------------------------------------------------------------
150 | | Session Cookie Domain
151 | |--------------------------------------------------------------------------
152 | |
153 | | This value determines the domain and subdomains the session cookie is
154 | | available to. By default, the cookie will be available to the root
155 | | domain and all subdomains. Typically, this shouldn't be changed.
156 | |
157 | */
158 |
159 | 'domain' => env('SESSION_DOMAIN'),
160 |
161 | /*
162 | |--------------------------------------------------------------------------
163 | | HTTPS Only Cookies
164 | |--------------------------------------------------------------------------
165 | |
166 | | By setting this option to true, session cookies will only be sent back
167 | | to the server if the browser has a HTTPS connection. This will keep
168 | | the cookie from being sent to you when it can't be done securely.
169 | |
170 | */
171 |
172 | 'secure' => env('SESSION_SECURE_COOKIE'),
173 |
174 | /*
175 | |--------------------------------------------------------------------------
176 | | HTTP Access Only
177 | |--------------------------------------------------------------------------
178 | |
179 | | Setting this value to true will prevent JavaScript from accessing the
180 | | value of the cookie and the cookie will only be accessible through
181 | | the HTTP protocol. It's unlikely you should disable this option.
182 | |
183 | */
184 |
185 | 'http_only' => env('SESSION_HTTP_ONLY', true),
186 |
187 | /*
188 | |--------------------------------------------------------------------------
189 | | Same-Site Cookies
190 | |--------------------------------------------------------------------------
191 | |
192 | | This option determines how your cookies behave when cross-site requests
193 | | take place, and can be used to mitigate CSRF attacks. By default, we
194 | | will set this value to "lax" to permit secure cross-site requests.
195 | |
196 | | See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value
197 | |
198 | | Supported: "lax", "strict", "none", null
199 | |
200 | */
201 |
202 | 'same_site' => env('SESSION_SAME_SITE', 'lax'),
203 |
204 | /*
205 | |--------------------------------------------------------------------------
206 | | Partitioned Cookies
207 | |--------------------------------------------------------------------------
208 | |
209 | | Setting this value to true will tie the cookie to the top-level site for
210 | | a cross-site context. Partitioned cookies are accepted by the browser
211 | | when flagged "secure" and the Same-Site attribute is set to "none".
212 | |
213 | */
214 |
215 | 'partitioned' => env('SESSION_PARTITIONED_COOKIE', false),
216 |
217 | ];
218 |
--------------------------------------------------------------------------------
/backend/database/.gitignore:
--------------------------------------------------------------------------------
1 | *.sqlite*
2 |
--------------------------------------------------------------------------------
/backend/database/factories/UserFactory.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 | class UserFactory extends Factory
13 | {
14 | /**
15 | * The current password being used by the factory.
16 | */
17 | protected static ?string $password;
18 |
19 | /**
20 | * Define the model's default state.
21 | *
22 | * @return array
23 | */
24 | public function definition(): array
25 | {
26 | return [
27 | 'name' => fake()->name(),
28 | 'email' => fake()->unique()->safeEmail(),
29 | 'email_verified_at' => now(),
30 | 'password' => static::$password ??= Hash::make('password'),
31 | 'remember_token' => Str::random(10),
32 | ];
33 | }
34 |
35 | /**
36 | * Indicate that the model's email address should be unverified.
37 | */
38 | public function unverified(): static
39 | {
40 | return $this->state(fn (array $attributes) => [
41 | 'email_verified_at' => null,
42 | ]);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/backend/database/migrations/0001_01_01_000000_create_users_table.php:
--------------------------------------------------------------------------------
1 | id();
16 | $table->string('name');
17 | $table->string('email')->unique();
18 | $table->timestamp('email_verified_at')->nullable();
19 | $table->string('password');
20 | $table->rememberToken();
21 | $table->timestamps();
22 | });
23 |
24 | Schema::create('password_reset_tokens', function (Blueprint $table) {
25 | $table->string('email')->primary();
26 | $table->string('token');
27 | $table->timestamp('created_at')->nullable();
28 | });
29 |
30 | Schema::create('sessions', function (Blueprint $table) {
31 | $table->string('id')->primary();
32 | $table->foreignId('user_id')->nullable()->index();
33 | $table->string('ip_address', 45)->nullable();
34 | $table->text('user_agent')->nullable();
35 | $table->longText('payload');
36 | $table->integer('last_activity')->index();
37 | });
38 | }
39 |
40 | /**
41 | * Reverse the migrations.
42 | */
43 | public function down(): void
44 | {
45 | Schema::dropIfExists('users');
46 | Schema::dropIfExists('password_reset_tokens');
47 | Schema::dropIfExists('sessions');
48 | }
49 | };
50 |
--------------------------------------------------------------------------------
/backend/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 |
--------------------------------------------------------------------------------
/backend/database/migrations/0001_01_01_000002_create_jobs_table.php:
--------------------------------------------------------------------------------
1 | id();
16 | $table->string('queue')->index();
17 | $table->longText('payload');
18 | $table->unsignedTinyInteger('attempts');
19 | $table->unsignedInteger('reserved_at')->nullable();
20 | $table->unsignedInteger('available_at');
21 | $table->unsignedInteger('created_at');
22 | });
23 |
24 | Schema::create('job_batches', function (Blueprint $table) {
25 | $table->string('id')->primary();
26 | $table->string('name');
27 | $table->integer('total_jobs');
28 | $table->integer('pending_jobs');
29 | $table->integer('failed_jobs');
30 | $table->longText('failed_job_ids');
31 | $table->mediumText('options')->nullable();
32 | $table->integer('cancelled_at')->nullable();
33 | $table->integer('created_at');
34 | $table->integer('finished_at')->nullable();
35 | });
36 |
37 | Schema::create('failed_jobs', function (Blueprint $table) {
38 | $table->id();
39 | $table->string('uuid')->unique();
40 | $table->text('connection');
41 | $table->text('queue');
42 | $table->longText('payload');
43 | $table->longText('exception');
44 | $table->timestamp('failed_at')->useCurrent();
45 | });
46 | }
47 |
48 | /**
49 | * Reverse the migrations.
50 | */
51 | public function down(): void
52 | {
53 | Schema::dropIfExists('jobs');
54 | Schema::dropIfExists('job_batches');
55 | Schema::dropIfExists('failed_jobs');
56 | }
57 | };
58 |
--------------------------------------------------------------------------------
/backend/database/migrations/2024_08_29_215256_create_personal_access_tokens_table.php:
--------------------------------------------------------------------------------
1 | id();
16 | $table->morphs('tokenable');
17 | $table->string('name');
18 | $table->string('token', 64)->unique();
19 | $table->text('abilities')->nullable();
20 | $table->timestamp('last_used_at')->nullable();
21 | $table->timestamp('expires_at')->nullable();
22 | $table->timestamps();
23 | });
24 | }
25 |
26 | /**
27 | * Reverse the migrations.
28 | */
29 | public function down(): void
30 | {
31 | Schema::dropIfExists('personal_access_tokens');
32 | }
33 | };
34 |
--------------------------------------------------------------------------------
/backend/database/seeders/DatabaseSeeder.php:
--------------------------------------------------------------------------------
1 | create();
17 |
18 | User::factory()->create([
19 | 'name' => 'Test User',
20 | 'email' => 'test@example.com',
21 | ]);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/backend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "type": "module",
4 | "scripts": {
5 | "dev": "vite",
6 | "build": "vite build"
7 | },
8 | "devDependencies": {
9 | "axios": "^1.6.4",
10 | "laravel-vite-plugin": "^1.0",
11 | "vite": "^5.0"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/backend/phpcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | app
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/backend/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 |
33 |
34 |
--------------------------------------------------------------------------------
/backend/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 |
--------------------------------------------------------------------------------
/backend/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cristianscheid/laravel-react-base/9976bbe5c136b0d12b79bbaabd876d7290402c1a/backend/public/favicon.ico
--------------------------------------------------------------------------------
/backend/public/index.php:
--------------------------------------------------------------------------------
1 | handleRequest(Request::capture());
18 |
--------------------------------------------------------------------------------
/backend/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Disallow:
3 |
--------------------------------------------------------------------------------
/backend/resources/css/app.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cristianscheid/laravel-react-base/9976bbe5c136b0d12b79bbaabd876d7290402c1a/backend/resources/css/app.css
--------------------------------------------------------------------------------
/backend/resources/js/app.js:
--------------------------------------------------------------------------------
1 | import './bootstrap';
2 |
--------------------------------------------------------------------------------
/backend/resources/js/bootstrap.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | window.axios = axios;
3 |
4 | window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
5 |
--------------------------------------------------------------------------------
/backend/resources/views/welcome.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Laravel
8 |
9 |
10 |
11 |
12 |
13 |
14 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
56 |
57 |
58 |
59 |
64 |
65 |
76 |
81 |
84 |
85 |
86 |
87 |
88 |
91 |
92 |
93 |
Documentation
94 |
95 |
96 | Laravel has wonderful documentation covering every aspect of the framework. Whether you are a newcomer or have prior experience with Laravel, we recommend reading our documentation from beginning to end.
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
109 |
112 |
113 |
114 |
Laracasts
115 |
116 |
117 | Laracasts offers thousands of video tutorials on Laravel, PHP, and JavaScript development. Check them out, see for yourself, and massively level up your development skills in the process.
118 |
119 |
120 |
121 |
122 |
123 |
124 |
128 |
131 |
132 |
133 |
Laravel News
134 |
135 |
136 | Laravel News is a community driven portal and newsletter aggregating all of the latest and most important news in the Laravel ecosystem, including new package releases and tutorials.
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
150 |
151 |
152 |
153 |
154 |
155 |
Vibrant Ecosystem
156 |
157 |
158 | Laravel's robust library of first-party tools and libraries, such as Forge , Vapor , Nova , Envoyer , and Herd help you take your projects to the next level. Pair them with powerful open source libraries like Cashier , Dusk , Echo , Horizon , Sanctum , Telescope , and more.
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 | Laravel v{{ Illuminate\Foundation\Application::VERSION }} (PHP v{{ PHP_VERSION }})
167 |
168 |
169 |
170 |
171 |
172 |
173 |
--------------------------------------------------------------------------------
/backend/routes/api.php:
--------------------------------------------------------------------------------
1 | group(function () {
13 | Route::get('/user', [UserController::class, 'show']);
14 | Route::put('/user', [UserController::class, 'update']);
15 | Route::delete('/user', [UserController::class, 'destroy']);
16 | Route::put('/change-password', [UserController::class, 'changePassword']);
17 | Route::post('/logout', [AuthController::class, 'logout']);
18 | });
19 |
--------------------------------------------------------------------------------
/backend/routes/console.php:
--------------------------------------------------------------------------------
1 | comment(Inspiring::quote());
8 | })->purpose('Display an inspiring quote')->hourly();
9 |
--------------------------------------------------------------------------------
/backend/routes/web.php:
--------------------------------------------------------------------------------
1 | get('/');
5 |
6 | $response->assertStatus(200);
7 | });
8 |
--------------------------------------------------------------------------------
/backend/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 |
--------------------------------------------------------------------------------
/backend/tests/TestCase.php:
--------------------------------------------------------------------------------
1 | toBeTrue();
5 | });
6 |
--------------------------------------------------------------------------------
/backend/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite';
2 | import laravel from 'laravel-vite-plugin';
3 |
4 | export default defineConfig({
5 | plugins: [
6 | laravel({
7 | input: ['resources/css/app.css', 'resources/js/app.js'],
8 | refresh: true,
9 | }),
10 | ],
11 | });
12 |
--------------------------------------------------------------------------------
/frontend/.env.example:
--------------------------------------------------------------------------------
1 | VITE_API_BASE_URL=http://localhost:8000
--------------------------------------------------------------------------------
/frontend/.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 | # Environment variables
16 | .env
17 |
18 | # Editor directories and files
19 | .vscode/*
20 | !.vscode/extensions.json
21 | .idea
22 | .DS_Store
23 | *.suo
24 | *.ntvs*
25 | *.njsproj
26 | *.sln
27 | *.sw?
28 |
--------------------------------------------------------------------------------
/frontend/README.md:
--------------------------------------------------------------------------------
1 | # React + Vite
2 |
3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4 |
5 | Currently, two official plugins are available:
6 |
7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
9 |
--------------------------------------------------------------------------------
/frontend/eslint.config.js:
--------------------------------------------------------------------------------
1 | import js from '@eslint/js'
2 | import globals from 'globals'
3 | import react from 'eslint-plugin-react'
4 | import reactHooks from 'eslint-plugin-react-hooks'
5 | import reactRefresh from 'eslint-plugin-react-refresh'
6 |
7 | export default [
8 | { ignores: ['dist'] },
9 | {
10 | files: ['**/*.{js,jsx}'],
11 | languageOptions: {
12 | ecmaVersion: 2020,
13 | globals: globals.browser,
14 | parserOptions: {
15 | ecmaVersion: 'latest',
16 | ecmaFeatures: { jsx: true },
17 | sourceType: 'module',
18 | },
19 | },
20 | settings: { react: { version: '18.3' } },
21 | plugins: {
22 | react,
23 | 'react-hooks': reactHooks,
24 | 'react-refresh': reactRefresh,
25 | },
26 | rules: {
27 | ...js.configs.recommended.rules,
28 | ...react.configs.recommended.rules,
29 | ...react.configs['jsx-runtime'].rules,
30 | ...reactHooks.configs.recommended.rules,
31 | 'react/jsx-no-target-blank': 'off',
32 | 'react-refresh/only-export-components': [
33 | 'warn',
34 | { allowConstantExport: true },
35 | ],
36 | },
37 | },
38 | ]
39 |
--------------------------------------------------------------------------------
/frontend/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frontend",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "lint": "eslint .",
10 | "preview": "vite preview"
11 | },
12 | "dependencies": {
13 | "axios": "^1.7.5",
14 | "react": "^18.3.1",
15 | "react-dom": "^18.3.1",
16 | "react-router-dom": "^6.26.1"
17 | },
18 | "devDependencies": {
19 | "@eslint/js": "^9.9.0",
20 | "@types/react": "^18.3.3",
21 | "@types/react-dom": "^18.3.0",
22 | "@vitejs/plugin-react": "^4.3.1",
23 | "autoprefixer": "^10.4.20",
24 | "eslint": "^9.9.0",
25 | "eslint-plugin-react": "^7.35.0",
26 | "eslint-plugin-react-hooks": "^5.1.0-rc.0",
27 | "eslint-plugin-react-refresh": "^0.4.9",
28 | "globals": "^15.9.0",
29 | "postcss": "^8.4.45",
30 | "tailwindcss": "^3.4.10",
31 | "vite": "^5.4.1"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/frontend/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/frontend/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/src/assets/laravel-logo.svg:
--------------------------------------------------------------------------------
1 |
3 |
4 |
--------------------------------------------------------------------------------
/frontend/src/assets/react-logo.svg:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/frontend/src/components/AuthPageContainer.jsx:
--------------------------------------------------------------------------------
1 | import { Link } from "react-router-dom";
2 | import AppLogo from "./ui/AppLogo";
3 |
4 | export default function AuthPageContainer({
5 | title,
6 | children,
7 | footerText,
8 | linkText,
9 | linkTo,
10 | }) {
11 | return (
12 |
13 |
14 |
15 |
{title}
16 |
{children}
17 |
18 | {footerText}{" "}
19 |
23 | {linkText}
24 |
25 |
26 |
27 |
28 | );
29 | }
30 |
--------------------------------------------------------------------------------
/frontend/src/components/MainMenu.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef, useState } from "react";
2 | import { Link } from "react-router-dom";
3 |
4 | export default function MainMenu() {
5 | const [isMenuOpen, setIsMenuOpen] = useState(false);
6 | const menuRef = useRef(null);
7 |
8 | const menuItems = [
9 | { linkTo: "/", label: "Home" },
10 | { linkTo: "/about", label: "About" },
11 | { linkTo: "/contact", label: "Contact" },
12 | ];
13 |
14 | const toggleMenu = () => {
15 | setIsMenuOpen((prevState) => !prevState);
16 | };
17 |
18 | const handleClickOutside = (event) => {
19 | if (menuRef.current && !menuRef.current.contains(event.target)) {
20 | setIsMenuOpen(false);
21 | }
22 | };
23 |
24 | const handleLinkClick = () => {
25 | setIsMenuOpen(false);
26 | };
27 |
28 | useEffect(() => {
29 | document.addEventListener("mousedown", handleClickOutside);
30 | return () => {
31 | document.removeEventListener("mousedown", handleClickOutside);
32 | };
33 | }, []);
34 |
35 | return (
36 |
37 | {/* Menu toggle button */}
38 |
42 |
43 |
50 |
51 |
52 |
53 | {/* Menu container */}
54 |
59 | {/* Menu items */}
60 |
61 | {menuItems.map(({ linkTo, label }) => (
62 |
63 |
68 | {label}
69 |
70 |
71 | ))}
72 |
73 |
74 |
75 | );
76 | }
77 |
--------------------------------------------------------------------------------
/frontend/src/components/NavBar.jsx:
--------------------------------------------------------------------------------
1 | import { Link } from "react-router-dom";
2 |
3 | import MainMenu from "./MainMenu";
4 | import AppLogo from "./ui/AppLogo";
5 | import UserMenu from "./UserMenu";
6 |
7 | export default function NavBar({ user, onLogout }) {
8 | return (
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/frontend/src/components/PageContainer.jsx:
--------------------------------------------------------------------------------
1 | export default function PageContainer({ title, children, is404 = false }) {
2 | return (
3 |
8 |
9 |
10 | {title}
11 |
12 |
{children}
13 |
14 |
17 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/frontend/src/components/UserMenu.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef, useState } from "react";
2 | import { Link } from "react-router-dom";
3 |
4 | export default function UserMenu({ user, onLogout }) {
5 | const [isMenuOpen, setIsMenuOpen] = useState(false);
6 | const menuRef = useRef(null);
7 |
8 | const menuItems = [
9 | { linkTo: "/profile", label: "Profile" },
10 | { linkTo: "/change-password", label: "Change Password" },
11 | ];
12 |
13 | const toggleMenu = () => {
14 | setIsMenuOpen((prevState) => !prevState);
15 | };
16 |
17 | const handleClickOutside = (event) => {
18 | if (menuRef.current && !menuRef.current.contains(event.target)) {
19 | setIsMenuOpen(false);
20 | }
21 | };
22 |
23 | const handleLinkClick = () => {
24 | setIsMenuOpen(false);
25 | };
26 |
27 | useEffect(() => {
28 | document.addEventListener("mousedown", handleClickOutside);
29 | return () => {
30 | document.removeEventListener("mousedown", handleClickOutside);
31 | };
32 | }, []);
33 |
34 | return (
35 |
36 | {/* User avatar button */}
37 |
41 |
42 |
50 |
51 |
52 |
53 | {/* Dropdown menu container */}
54 |
59 | {/* User info */}
60 |
61 | {user.name}
62 |
63 | {user.email}
64 |
65 |
66 |
67 | {/* Menu items */}
68 |
69 | {menuItems.map(({ linkTo, label }) => (
70 |
71 |
76 | {label}
77 |
78 |
79 | ))}
80 |
81 | {/* Logout button */}
82 |
83 |
87 | Logout
88 |
89 |
90 |
91 |
92 |
93 | );
94 | }
95 |
--------------------------------------------------------------------------------
/frontend/src/components/ui/AppLogo.jsx:
--------------------------------------------------------------------------------
1 | import laravelLogo from "../../assets/laravel-logo.svg";
2 | import reactLogo from "../../assets/react-logo.svg";
3 |
4 | export default function AppLogo() {
5 | return (
6 |
7 |
8 |
9 |
10 | );
11 | }
12 |
--------------------------------------------------------------------------------
/frontend/src/components/ui/Button.jsx:
--------------------------------------------------------------------------------
1 | export default function Button({
2 | type = "button",
3 | onClick,
4 | label,
5 | variant = "primary",
6 | }) {
7 | const baseStyles =
8 | "my-4 w-full font-medium rounded-lg text-sm px-5 py-2.5 text-white focus:outline-none";
9 |
10 | const colorStyles =
11 | variant === "primary"
12 | ? "focus:ring-4 focus:ring-blue-800 bg-blue-600 hover:bg-blue-700"
13 | : "focus:ring-4 focus:ring-red-800 bg-red-600 hover:bg-red-700";
14 |
15 | return (
16 |
21 | {label}
22 |
23 | );
24 | }
25 |
--------------------------------------------------------------------------------
/frontend/src/components/ui/ConfirmationModal.jsx:
--------------------------------------------------------------------------------
1 | import Button from "../ui/Button";
2 |
3 | const ConfirmationModal = ({
4 | isOpen,
5 | onClose,
6 | onConfirm,
7 | title = "Are you sure?",
8 | confirmLabel = "Yes",
9 | cancelLabel = "No",
10 | }) => {
11 | if (!isOpen) return null;
12 |
13 | return (
14 |
15 |
16 |
17 |
18 |
{title}
19 |
20 | {
22 | onConfirm();
23 | onClose();
24 | }}
25 | label={confirmLabel}
26 | variant="primary"
27 | />
28 |
33 |
34 |
35 |
36 |
37 |
38 | );
39 | };
40 |
41 | export default ConfirmationModal;
42 |
--------------------------------------------------------------------------------
/frontend/src/components/ui/Form.jsx:
--------------------------------------------------------------------------------
1 | export default function Form({ onSubmit, children }) {
2 | return (
3 |
6 | );
7 | }
8 |
--------------------------------------------------------------------------------
/frontend/src/components/ui/Input.jsx:
--------------------------------------------------------------------------------
1 | import { forwardRef } from "react";
2 |
3 | const Input = forwardRef(
4 | ({ type, placeholder, required, label, defaultValue }, ref) => {
5 | return (
6 |
7 | {label && (
8 |
9 | {label}
10 |
11 | )}
12 |
20 |
21 | );
22 | }
23 | );
24 |
25 | export default Input;
26 |
--------------------------------------------------------------------------------
/frontend/src/components/ui/Notification.jsx:
--------------------------------------------------------------------------------
1 | const Notification = ({ type, messages }) => {
2 | if (!messages || messages.length === 0) return null;
3 |
4 | const textColor = type === "error" ? "text-red-400" : "text-green-400";
5 |
6 | return (
7 |
8 | {messages.map((message, index) => (
9 |
10 | {message}
11 |
12 | ))}
13 |
14 | );
15 | };
16 |
17 | export default Notification;
18 |
--------------------------------------------------------------------------------
/frontend/src/hooks/useChangePassword.js:
--------------------------------------------------------------------------------
1 | import { useRef, useState } from "react";
2 |
3 | import axiosClient from "../services/api/axios-client";
4 | import { useStateContext } from "../services/contexts/ContextProvider";
5 |
6 | export default function useChangePassword() {
7 | const { setUser } = useStateContext();
8 | const [errors, setErrors] = useState([]);
9 | const [success, setSuccess] = useState([]);
10 | const currentPasswordRef = useRef();
11 | const newPasswordRef = useRef();
12 | const newPasswordConfirmationRef = useRef();
13 |
14 | const onSubmit = async (ev) => {
15 | ev.preventDefault();
16 | const payload = {
17 | current_password: currentPasswordRef.current.value,
18 | new_password: newPasswordRef.current.value,
19 | new_password_confirmation: newPasswordConfirmationRef.current.value,
20 | };
21 |
22 | setErrors([]);
23 | setSuccess([]);
24 |
25 | axiosClient
26 | .put("/change-password", payload)
27 | .then(({ data }) => {
28 | setUser(data);
29 | setSuccess(["Password was successfully updated"]);
30 | currentPasswordRef.current.value = "";
31 | newPasswordRef.current.value = "";
32 | newPasswordConfirmationRef.current.value = "";
33 | })
34 | .catch((err) => {
35 | const response = err.response;
36 | if (response && response.status === 422) {
37 | setErrors(Object.values(response.data.errors).flat());
38 | } else {
39 | setErrors(["An unexpected error occurred. Please try again later."]);
40 | }
41 | });
42 | };
43 |
44 | return {
45 | currentPasswordRef,
46 | newPasswordRef,
47 | newPasswordConfirmationRef,
48 | errors,
49 | success,
50 | onSubmit,
51 | };
52 | }
53 |
--------------------------------------------------------------------------------
/frontend/src/hooks/useLogin.js:
--------------------------------------------------------------------------------
1 | import { useRef, useState } from "react";
2 |
3 | import axiosClient from "../services/api/axios-client.js";
4 | import { useStateContext } from "../services/contexts/ContextProvider.jsx";
5 |
6 | export default function useLogin() {
7 | const emailRef = useRef();
8 | const passwordRef = useRef();
9 | const [errors, setErrors] = useState([]);
10 | const { setUser, setToken } = useStateContext();
11 |
12 | const onSubmit = async (ev) => {
13 | ev.preventDefault();
14 | const payload = {
15 | email: emailRef.current.value,
16 | password: passwordRef.current.value,
17 | };
18 |
19 | setErrors([]);
20 |
21 | axiosClient
22 | .post("/login", payload)
23 | .then(({ data }) => {
24 | setUser(data.user);
25 | setToken(data.token);
26 | })
27 | .catch((err) => {
28 | const response = err.response;
29 | if (response && response.status === 422) {
30 | setErrors(Object.values(response.data.errors).flat());
31 | } else {
32 | setErrors(["An unexpected error occurred. Please try again later."]);
33 | }
34 | });
35 | };
36 |
37 | return {
38 | emailRef,
39 | passwordRef,
40 | errors,
41 | onSubmit,
42 | };
43 | }
44 |
--------------------------------------------------------------------------------
/frontend/src/hooks/useProfile.js:
--------------------------------------------------------------------------------
1 | import { useRef, useState } from "react";
2 | import { useNavigate } from "react-router-dom";
3 |
4 | import axiosClient from "../services/api/axios-client";
5 | import { useStateContext } from "../services/contexts/ContextProvider";
6 |
7 | export default function useProfile() {
8 | const { user, setUser, setToken } = useStateContext();
9 | const [errors, setErrors] = useState([]);
10 | const [success, setSuccess] = useState([]);
11 | const [isModalOpen, setIsModalOpen] = useState(false);
12 | const nameRef = useRef();
13 | const emailRef = useRef();
14 | const navigate = useNavigate();
15 |
16 | const onSubmit = async (ev) => {
17 | ev.preventDefault();
18 | const payload = {
19 | name: nameRef.current.value,
20 | email: emailRef.current.value,
21 | };
22 |
23 | setErrors([]);
24 | setSuccess([]);
25 |
26 | axiosClient
27 | .put("/user", payload)
28 | .then(({ data }) => {
29 | setUser(data);
30 | setSuccess(["Profile was successfully updated"]);
31 | })
32 | .catch((err) => {
33 | const response = err.response;
34 | if (response && response.status === 422) {
35 | setErrors(Object.values(response.data.errors).flat());
36 | } else {
37 | setErrors(["An unexpected error occurred. Please try again later."]);
38 | }
39 | });
40 | };
41 |
42 | const openModal = () => {
43 | setIsModalOpen(true);
44 | };
45 |
46 | const closeModal = () => {
47 | setIsModalOpen(false);
48 | };
49 |
50 | const confirmDelete = () => {
51 | setErrors([]);
52 | setSuccess([]);
53 |
54 | axiosClient
55 | .delete("/user")
56 | .then(() => {
57 | setUser({});
58 | setToken(null);
59 | navigate("/login");
60 | })
61 | .catch(() => {
62 | setErrors(["An unexpected error occurred. Please try again later."]);
63 | });
64 | };
65 |
66 | return {
67 | nameRef,
68 | emailRef,
69 | errors,
70 | success,
71 | onSubmit,
72 | openModal,
73 | closeModal,
74 | confirmDelete,
75 | user,
76 | isModalOpen,
77 | };
78 | }
79 |
--------------------------------------------------------------------------------
/frontend/src/hooks/useSignUp.js:
--------------------------------------------------------------------------------
1 | import { useRef, useState } from "react";
2 |
3 | import axiosClient from "../services/api/axios-client.js";
4 | import { useStateContext } from "../services/contexts/ContextProvider.jsx";
5 |
6 | export default function useSignUp() {
7 | const nameRef = useRef();
8 | const emailRef = useRef();
9 | const passwordRef = useRef();
10 | const passwordConfirmationRef = useRef();
11 | const [errors, setErrors] = useState([]);
12 | const { setUser, setToken } = useStateContext();
13 |
14 | const onSubmit = async (ev) => {
15 | ev.preventDefault();
16 | const payload = {
17 | name: nameRef.current.value,
18 | email: emailRef.current.value,
19 | password: passwordRef.current.value,
20 | password_confirmation: passwordConfirmationRef.current.value,
21 | };
22 |
23 | setErrors([]);
24 |
25 | axiosClient
26 | .post("/signup", payload)
27 | .then(({ data }) => {
28 | setUser(data.user);
29 | setToken(data.token);
30 | })
31 | .catch((err) => {
32 | const response = err.response;
33 | if (response && response.status === 422) {
34 | setErrors(Object.values(response.data.errors).flat());
35 | } else {
36 | setErrors(["An unexpected error occurred. Please try again later."]);
37 | }
38 | });
39 | };
40 |
41 | return {
42 | nameRef,
43 | emailRef,
44 | passwordRef,
45 | passwordConfirmationRef,
46 | errors,
47 | onSubmit,
48 | };
49 | }
50 |
--------------------------------------------------------------------------------
/frontend/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
--------------------------------------------------------------------------------
/frontend/src/layouts/DefaultLayout.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react";
2 | import { Navigate, Outlet } from "react-router-dom";
3 |
4 | import NavBar from "../components/NavBar";
5 | import axiosClient from "../services/api/axios-client";
6 | import { useStateContext } from "../services/contexts/ContextProvider";
7 |
8 | export default function DefaultLayout() {
9 | const { user, token, setUser, setToken } = useStateContext();
10 |
11 | if (!token) {
12 | return ;
13 | }
14 |
15 | const onLogout = (ev) => {
16 | ev.preventDefault();
17 | axiosClient.post("/logout").then(() => {
18 | setUser({});
19 | setToken(null);
20 | });
21 | };
22 |
23 | useEffect(() => {
24 | axiosClient.get("/user").then(({ data }) => {
25 | setUser(data);
26 | });
27 | }, []);
28 |
29 | return (
30 |
31 |
32 |
33 |
34 |
35 |
36 | );
37 | }
38 |
--------------------------------------------------------------------------------
/frontend/src/layouts/GuestLayout.jsx:
--------------------------------------------------------------------------------
1 | import { Navigate, Outlet } from "react-router-dom";
2 |
3 | import { useStateContext } from "../services/contexts/ContextProvider";
4 |
5 | export default function GuestLayout() {
6 | const { token } = useStateContext();
7 |
8 | if (token) {
9 | return ;
10 | }
11 |
12 | return (
13 |
14 |
15 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/frontend/src/main.jsx:
--------------------------------------------------------------------------------
1 | import { StrictMode } from "react";
2 | import { createRoot } from "react-dom/client";
3 | import { RouterProvider } from "react-router-dom";
4 |
5 | import "./index.css";
6 | import router from "./routes/router.jsx";
7 | import { ContextProvider } from "./services/contexts/ContextProvider.jsx";
8 |
9 | createRoot(document.getElementById("root")).render(
10 |
11 |
12 |
13 |
14 |
15 | );
16 |
--------------------------------------------------------------------------------
/frontend/src/pages/AboutPage.jsx:
--------------------------------------------------------------------------------
1 | import PageContainer from "../components/PageContainer";
2 |
3 | export default function AboutPage() {
4 | return (
5 |
6 |
7 | This application serves as a minimal template for rapid development.
8 |
9 |
10 |
11 | It features user authentication and profile editing, providing a solid
12 | foundation for more complex applications. The backend API is powered
13 | by Laravel, ensuring robust performance, while the React frontend
14 | offers a responsive user experience.
15 |
16 |
17 | Styled with Tailwind CSS, the application adopts a utility-first
18 | approach for easy design and consistent styling.
19 |
20 |
21 | Whether you're a beginner or an experienced developer, this template
22 | simplifies the setup process, allowing you to focus on building key
23 | features.
24 |
25 |
26 | Dive into the code, customize it to fit your needs, and start
27 | developing your next web application with confidence!
28 |
29 |
30 |
31 | );
32 | }
33 |
--------------------------------------------------------------------------------
/frontend/src/pages/ChangePasswordPage.jsx:
--------------------------------------------------------------------------------
1 | import PageContainer from "../components/PageContainer";
2 | import Button from "../components/ui/Button";
3 | import Form from "../components/ui/Form";
4 | import Input from "../components/ui/Input";
5 | import Notification from "../components/ui/Notification";
6 | import useChangePassword from "../hooks/useChangePassword";
7 |
8 | export default function ChangePasswordPage() {
9 | const {
10 | currentPasswordRef,
11 | newPasswordRef,
12 | newPasswordConfirmationRef,
13 | errors,
14 | success,
15 | onSubmit,
16 | } = useChangePassword();
17 |
18 | return (
19 |
20 |
44 |
45 | {errors.length > 0 && }
46 | {success.length > 0 && }
47 |
48 | );
49 | }
50 |
--------------------------------------------------------------------------------
/frontend/src/pages/ContactPage.jsx:
--------------------------------------------------------------------------------
1 | import PageContainer from "../components/PageContainer";
2 |
3 | export default function ContactPage() {
4 | return (
5 |
6 |
7 | Feel free to reach out if you have any questions or would like to
8 | connect.
9 |
10 |
11 |
12 |
17 |
18 |
Cristian Scheid
19 |
Full Stack Developer
20 |
21 |
22 |
54 |
55 | I'm always open to discussing new projects, creative ideas, or
56 | opportunities to collaborate. Let's connect and bring our ideas to
57 | life!
58 |
59 |
60 |
61 | );
62 | }
63 |
--------------------------------------------------------------------------------
/frontend/src/pages/HomePage.jsx:
--------------------------------------------------------------------------------
1 | import PageContainer from "../components/PageContainer";
2 |
3 | export default function HomePage() {
4 | return (
5 |
6 |
7 | Backend API powered by Laravel, featuring a smooth React frontend.
8 |
9 |
10 | );
11 | }
12 |
--------------------------------------------------------------------------------
/frontend/src/pages/NotFoundPage.jsx:
--------------------------------------------------------------------------------
1 | import { Link } from "react-router-dom";
2 |
3 | import PageContainer from "../components/PageContainer";
4 |
5 | export default function NotFoundPage() {
6 | return (
7 |
8 |
9 |
10 | Oops! The page you are looking for could not be found.
11 |
12 |
13 | It might be that the URL is incorrect or the page has been removed.
14 |
15 |
16 | Please check the URL or return to the{" "}
17 |
18 | Home Page
19 |
20 | .
21 |
22 |
23 |
24 | );
25 | }
26 |
--------------------------------------------------------------------------------
/frontend/src/pages/ProfilePage.jsx:
--------------------------------------------------------------------------------
1 | import PageContainer from "../components/PageContainer";
2 | import Button from "../components/ui/Button";
3 | import ConfirmationModal from "../components/ui/ConfirmationModal";
4 | import Form from "../components/ui/Form";
5 | import Input from "../components/ui/Input";
6 | import Notification from "../components/ui/Notification";
7 | import useProfile from "../hooks/useProfile";
8 |
9 | export default function ProfilePage() {
10 | const {
11 | nameRef,
12 | emailRef,
13 | errors,
14 | success,
15 | onSubmit,
16 | openModal,
17 | closeModal,
18 | confirmDelete,
19 | user,
20 | isModalOpen,
21 | } = useProfile();
22 |
23 | return (
24 |
25 |
47 |
48 | {
52 | confirmDelete();
53 | closeModal();
54 | }}
55 | title="Are you sure you want to delete your account?"
56 | confirmLabel="Yes, delete"
57 | cancelLabel="No, cancel"
58 | />
59 |
60 | {errors.length > 0 && }
61 | {success.length > 0 && }
62 |
63 | );
64 | }
65 |
--------------------------------------------------------------------------------
/frontend/src/pages/auth/LoginPage.jsx:
--------------------------------------------------------------------------------
1 | import AuthPageContainer from "../../components/AuthPageContainer.jsx";
2 | import Button from "../../components/ui/Button.jsx";
3 | import Form from "../../components/ui/Form.jsx";
4 | import Input from "../../components/ui/Input.jsx";
5 | import Notification from "../../components/ui/Notification.jsx";
6 | import useLogin from "../../hooks/useLogin.js";
7 |
8 | export default function LoginPage() {
9 | const { emailRef, passwordRef, errors, onSubmit } = useLogin();
10 |
11 | return (
12 |
18 |
35 |
36 | {errors.length > 0 && }
37 |
38 | );
39 | }
40 |
--------------------------------------------------------------------------------
/frontend/src/pages/auth/SignUpPage.jsx:
--------------------------------------------------------------------------------
1 | import AuthPageContainer from "../../components/AuthPageContainer.jsx";
2 | import Button from "../../components/ui/Button.jsx";
3 | import Form from "../../components/ui/Form.jsx";
4 | import Input from "../../components/ui/Input.jsx";
5 | import Notification from "../../components/ui/Notification.jsx";
6 | import useSignUp from "../../hooks/useSignUp.js";
7 |
8 | export default function SignUpPage() {
9 | const {
10 | nameRef,
11 | emailRef,
12 | passwordRef,
13 | passwordConfirmationRef,
14 | errors,
15 | onSubmit,
16 | } = useSignUp();
17 |
18 | return (
19 |
25 |
56 |
57 | {errors.length > 0 && }
58 |
59 | );
60 | }
61 |
--------------------------------------------------------------------------------
/frontend/src/routes/router.jsx:
--------------------------------------------------------------------------------
1 | import { createBrowserRouter } from "react-router-dom";
2 |
3 | import DefaultLayout from "../layouts/DefaultLayout.jsx";
4 | import GuestLayout from "../layouts/GuestLayout.jsx";
5 | import AboutPage from "../pages/AboutPage.jsx";
6 | import LoginPage from "../pages/auth/LoginPage.jsx";
7 | import SignUpPage from "../pages/auth/SignUpPage.jsx";
8 | import ChangePasswordPage from "../pages/ChangePasswordPage.jsx";
9 | import ContactPage from "../pages/ContactPage.jsx";
10 | import HomePage from "../pages/HomePage.jsx";
11 | import NotFoundPage from "../pages/NotFoundPage.jsx";
12 | import ProfilePage from "../pages/ProfilePage.jsx";
13 |
14 | const router = createBrowserRouter([
15 | {
16 | path: "/",
17 | element: ,
18 | children: [
19 | {
20 | path: "/",
21 | element: ,
22 | },
23 | {
24 | path: "/about",
25 | element: ,
26 | },
27 | {
28 | path: "/contact",
29 | element: ,
30 | },
31 | {
32 | path: "/profile",
33 | element: ,
34 | },
35 | {
36 | path: "/change-password",
37 | element: ,
38 | },
39 | ],
40 | },
41 | {
42 | path: "/",
43 | element: ,
44 | children: [
45 | {
46 | path: "/login",
47 | element: ,
48 | },
49 | {
50 | path: "/signup",
51 | element: ,
52 | },
53 | ],
54 | },
55 | {
56 | path: "*",
57 | element: ,
58 | },
59 | ]);
60 |
61 | export default router;
62 |
--------------------------------------------------------------------------------
/frontend/src/services/api/axios-client.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 |
3 | const axiosClient = axios.create({
4 | baseURL: `${import.meta.env.VITE_API_BASE_URL}/api`,
5 | });
6 |
7 | axiosClient.interceptors.request.use((config) => {
8 | const token = localStorage.getItem("ACCESS_TOKEN");
9 | config.headers.Authorization = `Bearer ${token}`;
10 | return config;
11 | });
12 |
13 | axiosClient.interceptors.response.use(
14 | (response) => {
15 | return response;
16 | },
17 | (error) => {
18 | const { response } = error;
19 | if (response.status === 401) {
20 | localStorage.removeItem("ACCESS_TOKEN");
21 | }
22 | throw error;
23 | }
24 | );
25 |
26 | export default axiosClient;
27 |
--------------------------------------------------------------------------------
/frontend/src/services/contexts/ContextProvider.jsx:
--------------------------------------------------------------------------------
1 | import { createContext, useContext, useState } from "react";
2 |
3 | const StateContext = createContext({
4 | user: null,
5 | token: null,
6 | setUser: () => {},
7 | setToken: () => {},
8 | });
9 |
10 | export const ContextProvider = ({ children }) => {
11 | const [user, setUser] = useState({});
12 | const [token, _setToken] = useState(localStorage.getItem("ACCESS_TOKEN"));
13 |
14 | const setToken = (token) => {
15 | _setToken(token);
16 | if (token) {
17 | localStorage.setItem("ACCESS_TOKEN", token);
18 | } else {
19 | localStorage.removeItem("ACCESS_TOKEN");
20 | }
21 | };
22 |
23 | return (
24 |
32 | {children}
33 |
34 | );
35 | };
36 |
37 | export const useStateContext = () => useContext(StateContext);
38 |
--------------------------------------------------------------------------------
/frontend/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | export default {
3 | content: [
4 | "./index.html",
5 | "./src/**/*.{js,ts,jsx,tsx}",
6 | ],
7 | theme: {
8 | extend: {},
9 | },
10 | plugins: [],
11 | }
12 |
--------------------------------------------------------------------------------
/frontend/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | })
8 |
--------------------------------------------------------------------------------