├── .github
└── workflows
│ ├── phpunit.xml.stub
│ └── tests.yml
├── .gitignore
├── .php-cs-fixer.php
├── LICENSE
├── README.md
├── composer.json
├── phpstan.neon
├── screenshots
└── screen-record.gif
└── src
├── HandlesAuthScaffolding.php
├── HandlesCodeHelperScaffolding.php
├── HandlesGeneralScaffolding.php
├── TtallPreset.php
├── TtallPresetServiceProvider.php
└── stubs
├── auth
├── app
│ └── Http
│ │ └── Livewire
│ │ ├── Auth
│ │ ├── Login.php
│ │ ├── Passwords
│ │ │ ├── Confirm.php
│ │ │ ├── Email.php
│ │ │ └── Reset.php
│ │ ├── Register.php
│ │ └── Verify.php
│ │ └── Home.php
├── resources
│ └── views
│ │ ├── components
│ │ ├── button.blade.php
│ │ └── form-input.blade.php
│ │ ├── layouts
│ │ └── auth.blade.php
│ │ └── livewire
│ │ ├── auth
│ │ ├── login.blade.php
│ │ ├── passwords
│ │ │ ├── confirm.blade.php
│ │ │ ├── email.blade.php
│ │ │ └── reset.blade.php
│ │ ├── register.blade.php
│ │ └── verify.blade.php
│ │ └── home.blade.php
├── routes
│ └── web.php
└── tests
│ └── Feature
│ └── Auth
│ ├── LoginTest.php
│ ├── LogoutTest.php
│ ├── Passwords
│ ├── ConfirmTest.php
│ ├── EmailTest.php
│ └── ResetTest.php
│ ├── RegisterTest.php
│ └── VerifyTest.php
└── default
├── .eslintignore
├── .eslintrc.json
├── .gitignore
├── .php-cs-fixer.php
├── .prettierrc
├── phpstan.neon
├── postcss.config.js
├── public
└── favicon.ico
├── resources
├── css
│ └── app.css
├── js
│ ├── app.js
│ ├── bootstrap.js
│ └── turbolinks.js
└── views
│ ├── components
│ ├── logo.blade.php
│ └── navbar.blade.php
│ ├── layouts
│ ├── app.blade.php
│ └── base.blade.php
│ ├── vendor
│ └── pagination
│ │ ├── default.blade.php
│ │ └── simple-default.blade.php
│ └── welcome.blade.php
├── routes
└── web.php
├── tailwind.config.js
├── tests
├── Feature
│ └── ExampleTest.php
└── TestCase.php
└── webpack.mix.js
/.github/workflows/phpunit.xml.stub:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | name: tests
2 |
3 | on:
4 | push:
5 | pull_request:
6 |
7 | jobs:
8 | run:
9 | runs-on: ubuntu-latest
10 | strategy:
11 | fail-fast: false
12 | matrix:
13 | php: [8.1, 8.0]
14 | laravel: [^8.0]
15 | name: PHP ${{ matrix.php }} - Laravel ${{ matrix.laravel }}
16 | steps:
17 | - name: Checkout code
18 | uses: actions/checkout@v2
19 | - name: Cache dependencies
20 | uses: actions/cache@v1
21 | with:
22 | path: ~/.composer/cache/files
23 | key: dependencies-laravel-${{ matrix.laravel }}-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }}
24 | - name: Set up PHP
25 | uses: shivammathur/setup-php@v2
26 | with:
27 | php-version: ${{ matrix.php }}
28 | extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite
29 | coverage: none
30 | - name: Create Laravel app
31 | run: composer create-project laravel/laravel=${{ matrix.laravel }} ../app --prefer-dist
32 | - name: Install dependencies
33 | run: |
34 | cd ../app
35 | composer config repositories.local '{"type": "path", "url": "../ttall"}' --file composer.json
36 | composer require pktharindu/ttall @dev
37 | composer update
38 | - name: Install preset
39 | run: |
40 | cd ../app
41 | php artisan ui ttall --auth --option=code-helpers
42 | - name: Overwrite configuration
43 | run: |
44 | cd ../app
45 | rm phpunit.xml
46 | cp ../ttall/.github/workflows/phpunit.xml.stub ./phpunit.xml
47 | - name: Execute tests
48 | run: |
49 | cd ../app
50 | vendor/bin/phpunit --verbose
51 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor
2 | composer.phar
3 | composer.lock
4 | .DS_Store
5 | .php-cs-fixer.cache
--------------------------------------------------------------------------------
/.php-cs-fixer.php:
--------------------------------------------------------------------------------
1 | ['syntax' => 'short'],
8 | 'binary_operator_spaces' => [
9 | 'default' => 'single_space',
10 | 'operators' => ['=>' => null],
11 | ],
12 | 'blank_line_after_namespace' => true,
13 | 'blank_line_after_opening_tag' => true,
14 | 'blank_line_before_statement' => [
15 | 'statements' => ['return'],
16 | ],
17 | 'braces' => true,
18 | 'cast_spaces' => true,
19 | 'class_attributes_separation' => [
20 | 'elements' => [
21 | 'const' => 'one',
22 | 'method' => 'one',
23 | 'property' => 'one',
24 | ],
25 | ],
26 | 'class_definition' => [
27 | 'multi_line_extends_each_single_line' => true,
28 | 'single_item_single_line' => true,
29 | 'single_line' => true,
30 | ],
31 | 'concat_space' => [
32 | 'spacing' => 'none',
33 | ],
34 | 'declare_equal_normalize' => true,
35 | 'elseif' => true,
36 | 'encoding' => true,
37 | 'full_opening_tag' => true,
38 | 'fully_qualified_strict_types' => true, // added by Shift
39 | 'function_declaration' => true,
40 | 'function_typehint_space' => true,
41 | 'general_phpdoc_tag_rename' => true,
42 | 'heredoc_to_nowdoc' => true,
43 | 'include' => true,
44 | 'increment_style' => ['style' => 'post'],
45 | 'indentation_type' => true,
46 | 'linebreak_after_opening_tag' => true,
47 | 'line_ending' => true,
48 | 'lowercase_cast' => true,
49 | 'constant_case' => ['case' => 'lower'],
50 | 'lowercase_keywords' => true,
51 | 'lowercase_static_reference' => true, // added from Symfony
52 | 'magic_method_casing' => true, // added from Symfony
53 | 'magic_constant_casing' => true,
54 | 'method_argument_space' => true,
55 | 'native_function_casing' => true,
56 | 'no_alias_functions' => true,
57 | 'no_extra_blank_lines' => [
58 | 'tokens' => [
59 | 'extra',
60 | 'throw',
61 | 'use',
62 | 'use_trait',
63 | ],
64 | ],
65 | 'no_blank_lines_after_class_opening' => true,
66 | 'no_blank_lines_after_phpdoc' => true,
67 | 'no_closing_tag' => true,
68 | 'no_empty_phpdoc' => true,
69 | 'no_empty_statement' => true,
70 | 'no_leading_import_slash' => true,
71 | 'no_leading_namespace_whitespace' => true,
72 | 'no_mixed_echo_print' => [
73 | 'use' => 'echo',
74 | ],
75 | 'no_multiline_whitespace_around_double_arrow' => true,
76 | 'multiline_whitespace_before_semicolons' => [
77 | 'strategy' => 'no_multi_line',
78 | ],
79 | 'no_short_bool_cast' => true,
80 | 'no_singleline_whitespace_before_semicolons' => true,
81 | 'no_spaces_after_function_name' => true,
82 | 'no_spaces_around_offset' => [
83 | 'positions' => ['inside', 'outside'],
84 | ],
85 | 'no_spaces_inside_parenthesis' => true,
86 | 'no_trailing_comma_in_list_call' => true,
87 | 'no_trailing_comma_in_singleline_array' => true,
88 | 'no_trailing_whitespace' => true,
89 | 'no_trailing_whitespace_in_comment' => true,
90 | 'no_unneeded_control_parentheses' => [
91 | 'statements' => ['break', 'clone', 'continue', 'echo_print', 'return', 'switch_case', 'yield'],
92 | ],
93 | 'no_unreachable_default_argument_value' => true,
94 | 'no_useless_return' => true,
95 | 'no_whitespace_before_comma_in_array' => true,
96 | 'no_whitespace_in_blank_line' => true,
97 | 'normalize_index_brace' => true,
98 | 'not_operator_with_successor_space' => true,
99 | 'object_operator_without_whitespace' => true,
100 | 'ordered_imports' => [
101 | 'sort_algorithm' => 'alpha',
102 | ],
103 | 'phpdoc_indent' => true,
104 | 'phpdoc_inline_tag_normalizer' => true,
105 | 'phpdoc_no_access' => true,
106 | 'phpdoc_no_package' => true,
107 | 'phpdoc_no_useless_inheritdoc' => true,
108 | 'phpdoc_scalar' => true,
109 | 'phpdoc_single_line_var_spacing' => true,
110 | 'phpdoc_summary' => true,
111 | 'phpdoc_to_comment' => true,
112 | 'phpdoc_tag_type' => true,
113 | 'phpdoc_trim' => true,
114 | 'phpdoc_types' => true,
115 | 'phpdoc_var_without_name' => true,
116 | 'psr_autoloading' => true,
117 | 'self_accessor' => true,
118 | 'short_scalar_cast' => true,
119 | 'simplified_null_return' => false, // disabled by Shift
120 | 'single_blank_line_at_eof' => true,
121 | 'single_blank_line_before_namespace' => true,
122 | 'single_class_element_per_statement' => true, // here
123 | 'single_import_per_statement' => true,
124 | 'single_line_after_imports' => true,
125 | 'single_line_comment_style' => [
126 | 'comment_types' => ['hash']
127 | ],
128 | 'single_quote' => true,
129 | 'space_after_semicolon' => true,
130 | 'standardize_not_equals' => true,
131 | 'switch_case_semicolon_to_colon' => true,
132 | 'switch_case_space' => true,
133 | 'ternary_operator_spaces' => true,
134 | 'trailing_comma_in_multiline' => ['elements' => ['arrays']],
135 | 'trim_array_spaces' => true,
136 | 'unary_operator_spaces' => true,
137 | 'visibility_required' => [
138 | 'elements' => ['property', 'method', 'const'],
139 | ],
140 | 'whitespace_after_comma_in_array' => true,
141 | ];
142 |
143 | $finder = Finder::create()
144 | ->notPath('vendor')
145 | ->in(getcwd())
146 | ->name('*.php')
147 | ->notName('*.blade.php')
148 | ->notName('index.php')
149 | ->notName('server.php')
150 | ->ignoreDotFiles(true)
151 | ->ignoreVCS(true);
152 |
153 | return (new Config())
154 | ->setFinder($finder)
155 | ->setRules($rules)
156 | ->setRiskyAllowed(true)
157 | ->setUsingCache(true);
158 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) P. K. Tharindu
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # TTALL Preset For Laravel 7 and Up
2 |
3 | [](/LICENSE)
4 | [](https://github.com/pktharindu/ttall/actions)
5 | [](https://packagist.org/packages/pktharindu/ttall)
6 |
7 | An opinionated Laravel front-end scaffolding preset for TTALL stack - Tailwindcss | Turbolinks | Alpine.js | Laravel | Livewire 🚀
8 |
9 | It comes bundled with some helpful packages and their configurations (optional):
10 |
11 | It uses concerns of [laravel/ui](https://github.com/laravel/ui) through Livewire actions. So, security features of laravel/ui (ex: login throttling) are built right in. It also comes bundled with some helpful packages and their configurations (optional):
12 |
13 | - Laravel debugbar
14 | - Laravel IDE Helper
15 | - Php CS Fixer
16 | - Larastan
17 | - Eslint (Airbnb rules)
18 | - Prettier
19 | - Composer Git Hooks
20 |
21 | 
22 |
23 | If you like this package, show some love by starring the repo. ⭐❤
24 |
25 | ## Contents
26 |
27 | - [TTALL Preset For Laravel 7 and Up](#ttall-preset-for-laravel-7-and-up)
28 | - [Contents](#contents)
29 | - [Installation](#installation)
30 | - [For Basic Presets (without authentication)](#for-basic-presets-without-authentication)
31 | - [For Presets with Authentication](#for-presets-with-authentication)
32 | - [Configuration](#configuration)
33 | - [Options](#options)
34 | - [Code Helpers](#code-helpers)
35 | - [Scripts](#scripts)
36 | - [Support](#support)
37 | - [Credits](#credits)
38 | - [License](#license)
39 |
40 |
41 | ## Installation
42 |
43 | To install this preset on your laravel application, run:
44 |
45 | ``` bash
46 | composer require pktharindu/ttall --dev
47 | ```
48 |
49 | ### For Basic Presets (without authentication)
50 |
51 | To scaffold the basic preset without authentication, run:
52 | ``` bash
53 | php artisan ui ttall
54 | ```
55 |
56 | ### For Presets with Authentication
57 |
58 | To scaffold the basic preset, auth route entry and auth views in one go, run:
59 | ``` bash
60 | php artisan ui ttall --auth
61 | ```
62 | Finally run `composer update && npm install && npm run dev` to install the new composer packages and compile your fresh scaffolding.
63 |
64 | ## Configuration
65 |
66 | Add a new i18n string in the `resources/lang/XX/pagination.php` file for each language that your app uses:
67 |
68 | ```php
69 | 'previous' => '« Previous',
70 | 'next' => 'Next »',
71 | 'goto_page' => 'Goto page #:page', // Add this line
72 | ```
73 |
74 | This will help with accessibility.
75 |
76 | ```html
77 |
78 |
79 | 2
80 |
81 |
82 | ```
83 |
84 | ## Options
85 |
86 | As this preset is designed to get you up-and-running quickly, it comes bundled with some extra options that will take you even further. To utilize these options, use the `--option` flag when installing the preset.
87 |
88 | Usage Example:
89 |
90 | ```bash
91 | php artisan ui ttall --option=code-helpers
92 | ```
93 |
94 | ### Code Helpers
95 |
96 | `code-helpers` option will install and configure the below packages to help you with the development:
97 |
98 | - Laravel debugbar
99 | - Laravel IDE Helper
100 | - Php CS Fixer
101 | - Larastan
102 | - Eslint (Airbnb rules)
103 | - Prettier
104 | - Composer Git Hooks
105 |
106 | #### Scripts
107 |
108 | A composer's script is added automatically to tell `Laravel IDE Helper` to rescan your `Facades` files and update git hooks after every `composer update` :
109 |
110 | ```json
111 | "scripts": {
112 | "post-update-cmd": [
113 | "Illuminate\\Foundation\\ComposerScripts::postUpdate",
114 | "@php artisan ide-helper:generate",
115 | "cghooks update"
116 | ]
117 | }
118 | ```
119 |
120 | Also, Git Hooks are added to format your php files automatically before each commit.
121 |
122 | ```json
123 | "extra": {
124 | "hooks": {
125 | "pre-commit": [
126 | "STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM -- '*.php')",
127 | "php-cs-fixer fix",
128 | "git add $STAGED_FILES"
129 | ]
130 | }
131 | },
132 | ```
133 |
134 | Scripts are also added to your `package.json` and `composer.json` to run specific actions :
135 |
136 | - `composer format` : will use `php-cs-fixer` to format your php files
137 | - `composer test` : will use the `php artisan test` command to run your phpunit tests
138 | - `composer analyse` : will use `larastan` to analyse your code
139 | - `npm run format` : will format your js files on `resources/js` folder
140 | - `npm run lint` : will find issues in your js files based on Airbnb's rules and try to fix them
141 |
142 | ## Support
143 |
144 | If you require any support please contact me on [Twitter](https://twitter.com/CallMeTharindu) or open an issue on this repository.
145 |
146 | ## Credits
147 |
148 | - [P. K. Tharindu](https://github.com/pktharindu)
149 | - [All Contributors](../../contributors)
150 |
151 | This Package is inspired by [laravel-frontend-presets/tall](https://github.com/laravel-frontend-presets/tall) and [YannickYayo/laravel-preset-ttall](https://github.com/YannickYayo/laravel-preset-ttall). I wanted to have a combination of both. Thanks to all authors of these packages.
152 |
153 | ## License
154 |
155 | Licensed under the MIT license, see [LICENSE](/LICENSE) for details.
156 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pktharindu/ttall",
3 | "description": "TTALL Preset For Laravel 7 and Up.",
4 | "keywords": [
5 | "tailwind",
6 | "tailwindcss",
7 | "turbolinks",
8 | "alpine",
9 | "alpinejs",
10 | "laravel",
11 | "livewire",
12 | "tall",
13 | "ttall",
14 | "preset"
15 | ],
16 | "license": "MIT",
17 | "authors": [{
18 | "name": "P. K. Tharindu",
19 | "email": "pktharindu@outlook.com"
20 | }
21 | ],
22 | "require": {
23 | "php": "^7.2.5|^8.0",
24 | "laravel/framework": "^8.0|^9.0",
25 | "laravel/ui": "^3.0",
26 | "livewire/livewire": "^2.0"
27 | },
28 | "require-dev": {
29 | "phpstan/phpstan": "^0.12.14",
30 | "friendsofphp/php-cs-fixer": "^2.16"
31 | },
32 | "scripts": {
33 | "format": "php-cs-fixer fix --path-mode=intersection --config=.php-cs-fixer.php ./",
34 | "analyse": "phpstan analyse"
35 | },
36 | "autoload": {
37 | "psr-4": {
38 | "Pktharindu\\TtallPreset\\": "src/"
39 | }
40 | },
41 | "extra": {
42 | "laravel": {
43 | "providers": [
44 | "Pktharindu\\TtallPreset\\TtallPresetServiceProvider"
45 | ]
46 | }
47 | },
48 | "minimum-stability": "dev",
49 | "prefer-stable": true
50 | }
51 |
--------------------------------------------------------------------------------
/phpstan.neon:
--------------------------------------------------------------------------------
1 | parameters:
2 |
3 | paths:
4 | - src
5 |
6 | # The level 8 is the highest level
7 | level: 5
8 |
9 | ignoreErrors:
10 | # - '#Function base_path not found.#'
11 |
12 | checkMissingIterableValueType: false
13 |
--------------------------------------------------------------------------------
/screenshots/screen-record.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pktharindu/ttall/3ce4cb8d44b50c1ea1afce075a041da35e83294d/screenshots/screen-record.gif
--------------------------------------------------------------------------------
/src/HandlesAuthScaffolding.php:
--------------------------------------------------------------------------------
1 | copyDirectory(__DIR__.'/stubs/auth', base_path());
16 |
17 | collect($filesystem->allFiles(base_path('vendor/laravel/ui/stubs/migrations')))
18 | ->each(function (SplFileInfo $file) use ($filesystem) {
19 | $filesystem->copy(
20 | $file->getPathname(),
21 | database_path('migrations/'.$file->getFilename())
22 | );
23 | });
24 | }
25 |
26 | protected static function scaffoldAuthController(): void
27 | {
28 | if (! is_dir($directory = app_path('Http/Controllers/Auth'))) {
29 | mkdir($directory, 0755, true);
30 | }
31 |
32 | $filesystem = new Filesystem();
33 |
34 | collect($filesystem->allFiles(base_path('vendor/laravel/ui/stubs/Auth')))
35 | ->filter(function (SplFileInfo $file) {
36 | return in_array($file->getFilenameWithoutExtension(), [
37 | 'LoginController',
38 | 'VerificationController',
39 | ], true);
40 | })
41 | ->each(function (SplFileInfo $file) use ($filesystem) {
42 | $filesystem->copy(
43 | $file->getPathname(),
44 | app_path('Http/Controllers/Auth/'.Str::replaceLast('.stub', '.php', $file->getFilename()))
45 | );
46 | });
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/HandlesCodeHelperScaffolding.php:
--------------------------------------------------------------------------------
1 | '^3.6',
35 | 'barryvdh/laravel-ide-helper' => '^2.10',
36 | 'brainmaestro/composer-git-hooks' => '^2.8',
37 | 'friendsofphp/php-cs-fixer' => '^3.0',
38 | 'nunomaduro/larastan' => '^0.6.2',
39 | ], $composer);
40 | } else {
41 | return array_merge([], $composer);
42 | }
43 | }
44 |
45 | protected static function updateCodeHelperPackagesScripts(): void
46 | {
47 | if (! file_exists(base_path('package.json'))) {
48 | return;
49 | }
50 |
51 | $packages = json_decode(file_get_contents(base_path('package.json')), true);
52 |
53 | $packages['scripts'] = static::updateCodeHelperPackagesScriptsArray(
54 | array_key_exists('scripts', $packages) ? $packages['scripts'] : []
55 | );
56 |
57 | ksort($packages['scripts']);
58 |
59 | file_put_contents(
60 | base_path('package.json'),
61 | json_encode($packages, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT).PHP_EOL
62 | );
63 | }
64 |
65 | protected static function updateCodeHelperPackagesScriptsArray(array $packages): array
66 | {
67 | return array_merge([
68 | 'format' => "prettier --write 'resources/js/*.{js,jsx}'",
69 | 'lint' => "eslint '**/*.{js,jsx}' --quiet --fix",
70 | ], $packages);
71 | }
72 |
73 | protected static function updateCodeHelperComposerScripts(): void
74 | {
75 | if (! file_exists(base_path('composer.json'))) {
76 | return;
77 | }
78 |
79 | $composer = json_decode(file_get_contents(base_path('composer.json')), true);
80 |
81 | $composer['scripts'] = static::updateCodeHelperComposerScriptsArray(
82 | array_key_exists('scripts', $composer) ? $composer['scripts'] : []
83 | );
84 |
85 | ksort($composer['scripts']);
86 |
87 | file_put_contents(
88 | base_path('composer.json'),
89 | json_encode($composer, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT).PHP_EOL
90 | );
91 | }
92 |
93 | protected static function updateCodeHelperComposerScriptsArray(array $composer): array
94 | {
95 | return array_merge([
96 | 'post-update-cmd' => [
97 | 'Illuminate\\Foundation\\ComposerScripts::postUpdate',
98 | '@php artisan ide-helper:generate',
99 | 'cghooks update',
100 | ],
101 | 'format' => 'php-cs-fixer fix --path-mode=intersection --config=.php-cs-fixer.php ./',
102 | 'test' => '@php artisan test',
103 | 'analyse' => 'phpstan analyse',
104 | ], $composer);
105 | }
106 |
107 | protected static function updateCodeHelperComposerExtra(): void
108 | {
109 | if (! file_exists(base_path('composer.json'))) {
110 | return;
111 | }
112 |
113 | $composer = json_decode(file_get_contents(base_path('composer.json')), true);
114 |
115 | $composer['extra']['hooks'] = static::updateCodeHelperComposerExtraHooksArray(
116 | array_key_exists('hooks', $composer['extra']) ? $composer['extra']['hooks'] : []
117 | );
118 |
119 | ksort($composer['extra']);
120 |
121 | file_put_contents(
122 | base_path('composer.json'),
123 | json_encode($composer, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT).PHP_EOL
124 | );
125 | }
126 |
127 | protected static function updateCodeHelperComposerExtraHooksArray(array $composer): array
128 | {
129 | return array_merge([
130 | 'pre-commit' => [
131 | "STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM -- '*.php')",
132 | './vendor/bin/php-cs-fixer fix',
133 | 'git add $STAGED_FILES',
134 | ],
135 | ], $composer);
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/src/HandlesGeneralScaffolding.php:
--------------------------------------------------------------------------------
1 | '^3.0',
40 | 'livewire/livewire' => '^2.6',
41 | ], $composer);
42 | }
43 |
44 | protected static function updatePackagesScripts(): void
45 | {
46 | if (! file_exists(base_path('package.json'))) {
47 | return;
48 | }
49 |
50 | $packages = json_decode(file_get_contents(base_path('package.json')), true);
51 |
52 | ksort($packages['scripts']);
53 |
54 | file_put_contents(
55 | base_path('package.json'),
56 | json_encode($packages, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT).PHP_EOL
57 | );
58 | }
59 |
60 | protected static function scaffoldDefaults(): void
61 | {
62 | $filesystem = new Filesystem();
63 |
64 | $filesystem->copyDirectory(__DIR__.'/stubs/default', base_path());
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/TtallPreset.php:
--------------------------------------------------------------------------------
1 | '^10.3.4',
16 | 'alpinejs' => '^3.3.5',
17 | 'postcss' => '^8.2.1',
18 | 'postcss-import' => '^12.0.1',
19 | 'tailwindcss' => '^2.2.15',
20 | 'turbolinks' => '^5.2.0',
21 | ];
22 |
23 | public const DEV_NPM_PACKAGES_TO_ADD = [
24 | 'eslint' => '^7.7.0',
25 | 'eslint-config-airbnb' => '^18.2.0',
26 | 'eslint-config-prettier' => '^6.11.0',
27 | 'eslint-plugin-import' => '^2.22.0',
28 | 'eslint-plugin-jsx-a11y' => '^6.3.1',
29 | 'eslint-plugin-react' => '^7.20.6',
30 | 'eslint-plugin-react-hooks' => '^4.1.0',
31 | 'prettier' => '^2.0.5',
32 | 'laravel-mix' => '^6.0.0',
33 | ];
34 |
35 | public const NPM_PACKAGES_TO_REMOVE = [
36 | 'axios',
37 | 'laravel-mix',
38 | 'lodash',
39 | ];
40 |
41 | public static function install(): void
42 | {
43 | static::updatePackages();
44 | static::updatePackages(false);
45 | static::updateComposerPackages();
46 | static::updateComposerPackages(false);
47 | static::updatePackagesScripts();
48 | static::scaffoldDefaults();
49 | }
50 |
51 | public static function installAuth(): void
52 | {
53 | static::scaffoldAuth();
54 |
55 | static::scaffoldAuthController();
56 | }
57 |
58 | public static function installCodeHelpers(): void
59 | {
60 | static::updateCodeHelperComposerPackages();
61 | static::updateCodeHelperComposerPackages(false);
62 | static::updateCodeHelperPackagesScripts();
63 | static::updateCodeHelperComposerScripts();
64 | static::updateCodeHelperComposerExtra();
65 | }
66 |
67 | protected static function updatePackageArray(array $packages, string $dev): array
68 | {
69 | if ($dev == 'devDependencies') {
70 | return array_merge(
71 | static::DEV_NPM_PACKAGES_TO_ADD,
72 | Arr::except($packages, static::NPM_PACKAGES_TO_REMOVE)
73 | );
74 | }
75 |
76 | return array_merge(
77 | static::NPM_PACKAGES_TO_ADD,
78 | Arr::except($packages, static::NPM_PACKAGES_TO_REMOVE)
79 | );
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/TtallPresetServiceProvider.php:
--------------------------------------------------------------------------------
1 | line("\n".$asciiLogo."\n");
32 | $command->info('Ttall scaffolding installed successfully.');
33 |
34 | if ($command->option('auth')) {
35 | TtallPreset::installAuth();
36 |
37 | $command->info('Ttall auth scaffolding installed successfully.');
38 | }
39 |
40 | $options = $command->option('option') ?? [];
41 |
42 | if (in_array('code-helpers', $options, true) || in_array('all', $options, true)) {
43 | TtallPreset::installCodeHelpers();
44 |
45 | $command->info('Code helpers installed successfully.');
46 | }
47 |
48 | $command->line('Please run "composer update && npm install && npm run dev" to install the new composer\'s packages and compile your fresh scaffolding.');
49 |
50 | if ($command->confirm('Would you like to show some love by starring the repo?')) {
51 | if (PHP_OS_FAMILY == 'Darwin') {
52 | exec('open https://github.com/pktharindu/ttall');
53 | }
54 | if (PHP_OS_FAMILY == 'Windows') {
55 | exec('start https://github.com/pktharindu/ttall');
56 | }
57 | if (PHP_OS_FAMILY == 'Linux') {
58 | exec('xdg-open https://github.com/pktharindu/ttall');
59 | }
60 |
61 | $command->line('Thanks! Means the world to me! 🥰');
62 | } else {
63 | $command->line('I understand, but am not going to pretend I\'m not sad about it...');
64 | }
65 | });
66 |
67 | Paginator::defaultView('pagination::default');
68 |
69 | Paginator::defaultSimpleView('pagination::simple-default');
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/stubs/auth/app/Http/Livewire/Auth/Login.php:
--------------------------------------------------------------------------------
1 | login(new Request(collect($this)->toArray()));
23 | }
24 |
25 | protected function sendLoginResponse(Request $request)
26 | {
27 | session()->regenerate();
28 |
29 | $this->clearLoginAttempts($request);
30 |
31 | if ($response = $this->authenticated($request, $this->guard()->user())) {
32 | return $response;
33 | }
34 |
35 | return redirect()->intended(RouteServiceProvider::HOME);
36 | }
37 |
38 | public function render()
39 | {
40 | return view('livewire.auth.login')->extends('layouts.auth');
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/stubs/auth/app/Http/Livewire/Auth/Passwords/Confirm.php:
--------------------------------------------------------------------------------
1 | validate([
17 | 'password' => 'required|password',
18 | ]);
19 |
20 | session()->put('auth.password_confirmed_at', time());
21 |
22 | return redirect()->intended($this->redirectPath());
23 | }
24 |
25 | public function render()
26 | {
27 | return view('livewire.auth.passwords.confirm')->extends('layouts.auth');
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/stubs/auth/app/Http/Livewire/Auth/Passwords/Email.php:
--------------------------------------------------------------------------------
1 | validate([
15 | 'email' => 'required|email',
16 | ]);
17 |
18 | $response = $this->broker()->sendResetLink(['email' => $this->email]);
19 |
20 | return $response == Password::RESET_LINK_SENT
21 | ? $this->sendResetLinkResponse($response)
22 | : $this->addError('email', trans($response));
23 | }
24 |
25 | public function broker()
26 | {
27 | return Password::broker();
28 | }
29 |
30 | protected function sendResetLinkResponse($response)
31 | {
32 | session()->flash('status', trans($response));
33 |
34 | return redirect()->back();
35 | }
36 |
37 | public function render()
38 | {
39 | return view('livewire.auth.passwords.email')->extends('layouts.auth');
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/stubs/auth/app/Http/Livewire/Auth/Passwords/Reset.php:
--------------------------------------------------------------------------------
1 | token = $token;
26 | }
27 |
28 | public function passwordReset()
29 | {
30 | $this->validate([
31 | 'token' => 'required',
32 | 'email' => 'required|email',
33 | 'password' => 'required|confirmed|min:8',
34 | ]);
35 |
36 | $response = $this->broker()->reset(
37 | $this->credentials(),
38 | function ($user, $password) {
39 | $this->resetPassword($user, $password);
40 | }
41 | );
42 |
43 | return $response == Password::PASSWORD_RESET
44 | ? $this->sendResetResponse($response)
45 | : $this->addError('email', trans($response));
46 | }
47 |
48 | public function broker()
49 | {
50 | return Password::broker();
51 | }
52 |
53 | protected function guard()
54 | {
55 | return Auth::guard();
56 | }
57 |
58 | protected function credentials()
59 | {
60 | return [
61 | 'email' => $this->email,
62 | 'password' => $this->password,
63 | 'password_confirmation' => $this->password_confirmation,
64 | 'token' => $this->token,
65 | ];
66 | }
67 |
68 | protected function resetPassword($user, $password)
69 | {
70 | $user->password = Hash::make($password);
71 |
72 | $user->setRememberToken(Str::random(60));
73 |
74 | $user->save();
75 |
76 | event(new PasswordReset($user));
77 |
78 | $this->guard()->login($user);
79 | }
80 |
81 | protected function sendResetResponse($response)
82 | {
83 | session()->flash('status', trans($response));
84 |
85 | return redirect(RouteServiceProvider::HOME);
86 | }
87 |
88 | public function render()
89 | {
90 | return view('livewire.auth.passwords.reset')->extends('layouts.auth');
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/stubs/auth/app/Http/Livewire/Auth/Register.php:
--------------------------------------------------------------------------------
1 | validate([
25 | 'name' => ['required', 'string', 'max:255'],
26 | 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
27 | 'password' => ['required', 'string', 'min:8', 'confirmed'],
28 | ]);
29 |
30 | event(new Registered($user = $this->create()));
31 |
32 | $this->guard()->login($user);
33 |
34 | return redirect()->intended(RouteServiceProvider::HOME);
35 | }
36 |
37 | protected function create()
38 | {
39 | return User::create([
40 | 'name' => $this->name,
41 | 'email' => $this->email,
42 | 'password' => Hash::make($this->password),
43 | ]);
44 | }
45 |
46 | protected function guard()
47 | {
48 | return Auth::guard();
49 | }
50 |
51 | public function render()
52 | {
53 | return view('livewire.auth.register')->extends('layouts.auth');
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/stubs/auth/app/Http/Livewire/Auth/Verify.php:
--------------------------------------------------------------------------------
1 | redirectIfVerified();
14 |
15 | Auth::user()->sendEmailVerificationNotification();
16 |
17 | session()->flash('resent', true);
18 | }
19 |
20 | public function mount()
21 | {
22 | $this->redirectIfVerified();
23 | }
24 |
25 | protected function redirectIfVerified()
26 | {
27 | if (Auth::user()->hasVerifiedEmail()) {
28 | return redirect(RouteServiceProvider::HOME);
29 | }
30 | }
31 |
32 | public function render()
33 | {
34 | return view('livewire.auth.verify')->extends('layouts.auth');
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/stubs/auth/app/Http/Livewire/Home.php:
--------------------------------------------------------------------------------
1 |
4 | {{ $slot }}
5 |
--------------------------------------------------------------------------------
/src/stubs/auth/resources/views/components/form-input.blade.php:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
8 |
9 |
10 | @error($name)
11 | {{ $message }}
12 | @enderror
--------------------------------------------------------------------------------
/src/stubs/auth/resources/views/layouts/auth.blade.php:
--------------------------------------------------------------------------------
1 | @extends('layouts.base')
2 |
3 | @section('body')
4 |
5 | @yield('content')
6 |
7 | @isset($slot)
8 | {{ $slot }}
9 | @endisset
10 |
11 | @endsection
--------------------------------------------------------------------------------
/src/stubs/auth/resources/views/livewire/auth/login.blade.php:
--------------------------------------------------------------------------------
1 | @section('title', __('Sign in to your account'))
2 |
3 |
--------------------------------------------------------------------------------
/src/stubs/auth/resources/views/livewire/auth/passwords/confirm.blade.php:
--------------------------------------------------------------------------------
1 | @section('title', __('Confirm your password'))
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
12 |
13 |
14 | {{ __('Please confirm your password before continuing') }}
15 |
16 |
17 |
18 |
45 |
--------------------------------------------------------------------------------
/src/stubs/auth/resources/views/livewire/auth/passwords/email.blade.php:
--------------------------------------------------------------------------------
1 | @section('title', __('Reset password'))
2 |
3 |
4 |
21 |
22 |
23 |
24 | @if (session('status'))
25 |
26 |
27 |
34 |
35 |
36 |
37 | {{ session('status') }}
38 |
39 |
40 |
41 |
42 | @endif
43 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/src/stubs/auth/resources/views/livewire/auth/passwords/reset.blade.php:
--------------------------------------------------------------------------------
1 | @section('title', __('Reset password'))
2 |
3 |
45 |
--------------------------------------------------------------------------------
/src/stubs/auth/resources/views/livewire/auth/register.blade.php:
--------------------------------------------------------------------------------
1 | @section('title', __('Create a new account'))
2 |
3 |
--------------------------------------------------------------------------------
/src/stubs/auth/resources/views/livewire/auth/verify.blade.php:
--------------------------------------------------------------------------------
1 | @section('title', __('Verify your email address'))
2 |
3 |
4 |
26 |
27 |
28 |
30 |
31 |
32 |
39 |
40 |
41 |
42 | {{ __('A fresh verification link has been sent to your email address.') }}
43 |
44 |
45 |
46 |
47 |
48 |
49 |
{{ __('Before proceeding, please check your email for a verification link.') }}
50 |
51 |
52 | {{ __('If you did not receive the email,') }} {{ __('click here to request another') }}.
54 |
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/src/stubs/auth/resources/views/livewire/home.blade.php:
--------------------------------------------------------------------------------
1 | @section('title', __('Home'))
2 |
3 |
4 |
5 |
6 |
7 |

9 |
10 |
Dashboard
11 |
You are logged in!
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/stubs/auth/routes/web.php:
--------------------------------------------------------------------------------
1 | group(function () {
28 | Route::get('login', Login::class)
29 | ->name('login');
30 |
31 | Route::get('register', Register::class)
32 | ->name('register');
33 | });
34 |
35 | Route::get('password/reset', Email::class)
36 | ->name('password.request');
37 |
38 | Route::get('password/reset/{token}', Reset::class)
39 | ->name('password.reset');
40 |
41 | Route::middleware('auth')->group(function () {
42 | Route::get('email/verify', Verify::class)
43 | ->middleware('throttle:6,1')
44 | ->name('verification.notice');
45 |
46 | Route::get('password/confirm', Confirm::class)
47 | ->name('password.confirm');
48 | });
49 |
50 | Route::middleware('auth')->group(function () {
51 | Route::get('home', Home::class)
52 | ->name('home');
53 |
54 | Route::get('email/verify/{id}/{hash}', [VerificationController::class, 'verify'])
55 | ->middleware('signed')
56 | ->name('verification.verify');
57 |
58 | Route::post('logout', [LoginController::class, 'logout'])
59 | ->name('logout');
60 | });
61 |
--------------------------------------------------------------------------------
/src/stubs/auth/tests/Feature/Auth/LoginTest.php:
--------------------------------------------------------------------------------
1 | get(route('login'))
21 | ->assertSuccessful()
22 | ->assertSeeLivewire('auth.login');
23 | }
24 |
25 | /** @test */
26 | public function is_redirected_if_already_logged_in()
27 | {
28 | $user = User::factory()->create();
29 |
30 | $this->be($user);
31 |
32 | $this->get(route('login'))
33 | ->assertRedirect(RouteServiceProvider::HOME);
34 | }
35 |
36 | /** @test */
37 | public function a_user_can_login()
38 | {
39 | $user = User::factory()->create(['password' => Hash::make('password')]);
40 |
41 | Livewire::test('auth.login')
42 | ->set('email', $user->email)
43 | ->set('password', 'password')
44 | ->call('authenticate');
45 |
46 | $this->assertAuthenticatedAs($user);
47 | }
48 |
49 | /** @test */
50 | public function is_redirected_to_the_home_page_after_login()
51 | {
52 | $user = User::factory()->create(['password' => Hash::make('password')]);
53 |
54 | Livewire::test('auth.login')
55 | ->set('email', $user->email)
56 | ->set('password', 'password')
57 | ->call('authenticate')
58 | ->assertRedirect(RouteServiceProvider::HOME);
59 | }
60 |
61 | /** @test */
62 | public function email_is_required()
63 | {
64 | $user = User::factory()->create(['password' => Hash::make('password')]);
65 |
66 | Livewire::test('auth.login')
67 | ->set('password', 'password')
68 | ->call('authenticate')
69 | ->assertHasErrors(['email' => 'required']);
70 | }
71 |
72 | /** @test */
73 | public function password_is_required()
74 | {
75 | $user = User::factory()->create(['password' => Hash::make('password')]);
76 |
77 | Livewire::test('auth.login')
78 | ->set('email', $user->email)
79 | ->call('authenticate')
80 | ->assertHasErrors(['password' => 'required']);
81 | }
82 |
83 | /** @test */
84 | public function bad_login_attempt_shows_message()
85 | {
86 | $user = User::factory()->create();
87 |
88 | Livewire::test('auth.login')
89 | ->set('email', $user->email)
90 | ->set('password', 'bad-password')
91 | ->call('authenticate')
92 | ->assertHasErrors('email');
93 |
94 | $this->assertFalse(Auth::check());
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/stubs/auth/tests/Feature/Auth/LogoutTest.php:
--------------------------------------------------------------------------------
1 | create();
18 | $this->be($user);
19 |
20 | $this->post(route('logout'))
21 | ->assertRedirect('/');
22 |
23 | $this->assertFalse(Auth::check());
24 | }
25 |
26 | /** @test */
27 | public function an_unauthenticated_user_can_not_log_out()
28 | {
29 | $this->post(route('logout'))
30 | ->assertRedirect(route('login'));
31 |
32 | $this->assertFalse(Auth::check());
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/stubs/auth/tests/Feature/Auth/Passwords/ConfirmTest.php:
--------------------------------------------------------------------------------
1 | middleware(['web', 'password.confirm']);
23 | }
24 |
25 | /** @test */
26 | public function a_user_must_confirm_their_password_before_visiting_a_protected_page()
27 | {
28 | $user = User::factory()->create();
29 | $this->be($user);
30 |
31 | $this->get('/must-be-confirmed')
32 | ->assertRedirect(route('password.confirm'));
33 |
34 | $this->followingRedirects()
35 | ->get('/must-be-confirmed')
36 | ->assertSeeLivewire('auth.passwords.confirm');
37 | }
38 |
39 | /** @test */
40 | public function a_user_must_enter_a_password_to_confirm_it()
41 | {
42 | Livewire::test('auth.passwords.confirm')
43 | ->call('confirm')
44 | ->assertHasErrors(['password' => 'required']);
45 | }
46 |
47 | /** @test */
48 | public function a_user_must_enter_their_own_password_to_confirm_it()
49 | {
50 | $user = User::factory()->create([
51 | 'password' => Hash::make('password'),
52 | ]);
53 |
54 | Livewire::test('auth.passwords.confirm')
55 | ->set('password', 'not-password')
56 | ->call('confirm')
57 | ->assertHasErrors(['password' => 'password']);
58 | }
59 |
60 | /** @test */
61 | public function a_user_who_confirms_their_password_will_get_redirected()
62 | {
63 | $user = User::factory()->create([
64 | 'password' => Hash::make('password'),
65 | ]);
66 |
67 | $this->be($user);
68 |
69 | $this->withSession(['url.intended' => '/must-be-confirmed']);
70 |
71 | Livewire::test('auth.passwords.confirm')
72 | ->set('password', 'password')
73 | ->call('confirm')
74 | ->assertRedirect('/must-be-confirmed');
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/stubs/auth/tests/Feature/Auth/Passwords/EmailTest.php:
--------------------------------------------------------------------------------
1 | get(route('password.request'))
18 | ->assertSuccessful()
19 | ->assertSeeLivewire('auth.passwords.email');
20 | }
21 |
22 | /** @test */
23 | public function a_user_must_enter_an_email_address()
24 | {
25 | Livewire::test('auth.passwords.email')
26 | ->call('sendResetLinkEmail')
27 | ->assertHasErrors(['email' => 'required']);
28 | }
29 |
30 | /** @test */
31 | public function a_user_must_enter_a_valid_email_address()
32 | {
33 | Livewire::test('auth.passwords.email')
34 | ->set('email', 'email')
35 | ->call('sendResetLinkEmail')
36 | ->assertHasErrors(['email' => 'email']);
37 | }
38 |
39 | /** @test */
40 | public function a_user_who_enters_a_valid_email_address_will_get_sent_an_email()
41 | {
42 | $user = User::factory()->create();
43 |
44 | Livewire::test('auth.passwords.email')
45 | ->set('email', $user->email)
46 | ->call('sendResetLinkEmail');
47 |
48 | $this->assertDatabaseHas('password_resets', [
49 | 'email' => $user->email,
50 | ]);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/stubs/auth/tests/Feature/Auth/Passwords/ResetTest.php:
--------------------------------------------------------------------------------
1 | create();
23 |
24 | $token = Str::random(16);
25 |
26 | DB::table('password_resets')->insert([
27 | 'email' => $user->email,
28 | 'token' => Hash::make($token),
29 | 'created_at' => Carbon::now(),
30 | ]);
31 |
32 | $this->get(route('password.reset', [
33 | 'email' => $user->email,
34 | 'token' => $token,
35 | ]))
36 | ->assertSuccessful()
37 | ->assertSeeLivewire('auth.passwords.reset');
38 | }
39 |
40 | /** @test */
41 | public function can_reset_password()
42 | {
43 | $user = User::factory()->create();
44 |
45 | $token = Str::random(16);
46 |
47 | DB::table('password_resets')->insert([
48 | 'email' => $user->email,
49 | 'token' => Hash::make($token),
50 | 'created_at' => Carbon::now(),
51 | ]);
52 |
53 | Livewire::test('auth.passwords.reset', [
54 | 'token' => $token,
55 | ])
56 | ->set('email', $user->email)
57 | ->set('password', 'new-password')
58 | ->set('password_confirmation', 'new-password')
59 | ->call('passwordReset');
60 |
61 | $this->assertTrue(Auth::attempt([
62 | 'email' => $user->email,
63 | 'password' => 'new-password',
64 | ]));
65 | }
66 |
67 | /** @test */
68 | public function token_is_required()
69 | {
70 | Livewire::test('auth.passwords.reset', [
71 | 'token' => null,
72 | ])
73 | ->call('passwordReset')
74 | ->assertHasErrors(['token' => 'required']);
75 | }
76 |
77 | /** @test */
78 | public function email_is_required()
79 | {
80 | Livewire::test('auth.passwords.reset', [
81 | 'token' => Str::random(16),
82 | ])
83 | ->set('email', null)
84 | ->call('passwordReset')
85 | ->assertHasErrors(['email' => 'required']);
86 | }
87 |
88 | /** @test */
89 | public function email_is_valid_email()
90 | {
91 | Livewire::test('auth.passwords.reset', [
92 | 'token' => Str::random(16),
93 | ])
94 | ->set('email', 'email')
95 | ->call('passwordReset')
96 | ->assertHasErrors(['email' => 'email']);
97 | }
98 |
99 | /** @test */
100 | public function password_is_required()
101 | {
102 | Livewire::test('auth.passwords.reset', [
103 | 'token' => Str::random(16),
104 | ])
105 | ->set('password', '')
106 | ->call('passwordReset')
107 | ->assertHasErrors(['password' => 'required']);
108 | }
109 |
110 | /** @test */
111 | public function password_is_minimum_of_eight_characters()
112 | {
113 | Livewire::test('auth.passwords.reset', [
114 | 'token' => Str::random(16),
115 | ])
116 | ->set('password', 'secret')
117 | ->call('passwordReset')
118 | ->assertHasErrors(['password' => 'min']);
119 | }
120 |
121 | /** @test */
122 | public function password_matches_password_confirmation()
123 | {
124 | Livewire::test('auth.passwords.reset', [
125 | 'token' => Str::random(16),
126 | ])
127 | ->set('password', 'new-password')
128 | ->set('password_confirmation', 'not-new-password')
129 | ->call('passwordReset')
130 | ->assertHasErrors(['password' => 'confirmed']);
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/src/stubs/auth/tests/Feature/Auth/RegisterTest.php:
--------------------------------------------------------------------------------
1 | get(route('register'))
22 | ->assertSuccessful()
23 | ->assertSeeLivewire('auth.register');
24 | }
25 |
26 | /** @test */
27 | public function is_redirected_if_already_logged_in()
28 | {
29 | $user = User::factory()->create();
30 |
31 | $this->be($user);
32 |
33 | $this->get(route('register'))
34 | ->assertRedirect(RouteServiceProvider::HOME);
35 | }
36 |
37 | /** @test */
38 | public function a_user_can_register()
39 | {
40 | Event::fake();
41 |
42 | Livewire::test('auth.register')
43 | ->set('name', 'Tall Stack')
44 | ->set('email', 'tallstack@example.com')
45 | ->set('password', 'password')
46 | ->set('password_confirmation', 'password')
47 | ->call('signup')
48 | ->assertRedirect(RouteServiceProvider::HOME);
49 |
50 | $this->assertTrue(User::whereEmail('tallstack@example.com')->exists());
51 | $this->assertEquals('tallstack@example.com', Auth::user()->email);
52 |
53 | Event::assertDispatched(Registered::class);
54 | }
55 |
56 | /** @test */
57 | public function name_is_required()
58 | {
59 | Livewire::test('auth.register')
60 | ->set('name', '')
61 | ->call('signup')
62 | ->assertHasErrors(['email' => 'required']);
63 | }
64 |
65 | /** @test */
66 | public function email_is_required()
67 | {
68 | Livewire::test('auth.register')
69 | ->set('email', '')
70 | ->call('signup')
71 | ->assertHasErrors(['email' => 'required']);
72 | }
73 |
74 | /** @test */
75 | public function email_is_valid_email()
76 | {
77 | Livewire::test('auth.register')
78 | ->set('email', 'tallstack')
79 | ->call('signup')
80 | ->assertHasErrors(['email' => 'email']);
81 | }
82 |
83 | /** @test */
84 | public function email_hasnt_been_taken_already()
85 | {
86 | User::factory()->create(['email' => 'tallstack@example.com']);
87 |
88 | Livewire::test('auth.register')
89 | ->set('email', 'tallstack@example.com')
90 | ->call('signup')
91 | ->assertHasErrors(['email' => 'unique']);
92 | }
93 |
94 | /** @test */
95 | public function see_email_hasnt_already_been_taken_validation_message_as_user_types()
96 | {
97 | User::factory()->create(['email' => 'tallstack@example.com']);
98 |
99 | Livewire::test('auth.register')
100 | ->set('email', 'smallstack@gmail.com')
101 | ->assertHasNoErrors()
102 | ->set('email', 'tallstack@example.com')
103 | ->call('signup')
104 | ->assertHasErrors(['email' => 'unique']);
105 | }
106 |
107 | /** @test */
108 | public function password_is_required()
109 | {
110 | Livewire::test('auth.register')
111 | ->set('password', '')
112 | ->set('password_confirmation', 'password')
113 | ->call('signup')
114 | ->assertHasErrors(['password' => 'required']);
115 | }
116 |
117 | /** @test */
118 | public function password_is_minimum_of_eight_characters()
119 | {
120 | Livewire::test('auth.register')
121 | ->set('password', 'secret')
122 | ->set('password_confirmation', 'secret')
123 | ->call('signup')
124 | ->assertHasErrors(['password' => 'min']);
125 | }
126 |
127 | /** @test */
128 | public function password_matches_password_confirmation()
129 | {
130 | Livewire::test('auth.register')
131 | ->set('email', 'tallstack@example.com')
132 | ->set('password', 'password')
133 | ->set('password_confirmation', 'not-password')
134 | ->call('signup')
135 | ->assertHasErrors(['password' => 'confirmed']);
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/src/stubs/auth/tests/Feature/Auth/VerifyTest.php:
--------------------------------------------------------------------------------
1 | create([
24 | 'email_verified_at' => null,
25 | ]);
26 |
27 | Auth::login($user);
28 |
29 | $this->get(route('verification.notice'))
30 | ->assertSuccessful()
31 | ->assertSeeLivewire('auth.verify');
32 | }
33 |
34 | /** @test */
35 | public function can_resend_verification_email()
36 | {
37 | $user = User::factory()->create();
38 |
39 | Livewire::actingAs($user);
40 |
41 | Livewire::test('auth.verify')
42 | ->call('resend')
43 | ->assertSessionHas('resent', true);
44 | }
45 |
46 | /** @test */
47 | public function can_verify()
48 | {
49 | $user = User::factory()->create([
50 | 'email_verified_at' => null,
51 | ]);
52 |
53 | Auth::login($user);
54 |
55 | $url = URL::temporarySignedRoute('verification.verify', Carbon::now()->addMinutes(Config::get('auth.verification.expire', 60)), [
56 | 'id' => $user->getKey(),
57 | 'hash' => sha1($user->getEmailForVerification()),
58 | ]);
59 |
60 | $this->get($url)
61 | ->assertRedirect(RouteServiceProvider::HOME);
62 |
63 | $this->assertTrue($user->hasVerifiedEmail());
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/stubs/default/.eslintignore:
--------------------------------------------------------------------------------
1 | public/js
2 | vendor/
--------------------------------------------------------------------------------
/src/stubs/default/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["airbnb", "prettier"],
3 | "env": {
4 | "browser": true,
5 | "node": true
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/stubs/default/.gitignore:
--------------------------------------------------------------------------------
1 | /.idea
2 | /node_modules
3 | /public/hot
4 | /public/storage
5 | /storage/*.key
6 | /storage/*.index
7 | /storage/*.index-journal
8 | /vendor
9 | .env
10 | .env.backup
11 | .phpunit.result.cache
12 | docker-compose.override.yml
13 | Homestead.json
14 | Homestead.yaml
15 | npm-debug.log
16 | yarn-error.log
17 | /.vscode
18 | auth.json
19 | .php-cs-fixer.cache
20 | _ide_helper.php
21 | _ide_helper_actions.php
22 | _ide_helper_models.php
23 | .phpstorm.meta.php
24 | draft.yaml
25 | .blueprint
26 | cghooks.lock
27 |
--------------------------------------------------------------------------------
/src/stubs/default/.php-cs-fixer.php:
--------------------------------------------------------------------------------
1 | ['syntax' => 'short'],
8 | 'binary_operator_spaces' => [
9 | 'default' => 'single_space',
10 | 'operators' => ['=>' => null],
11 | ],
12 | 'blank_line_after_namespace' => true,
13 | 'blank_line_after_opening_tag' => true,
14 | 'blank_line_before_statement' => [
15 | 'statements' => ['return'],
16 | ],
17 | 'braces' => true,
18 | 'cast_spaces' => true,
19 | 'class_attributes_separation' => [
20 | 'elements' => [
21 | 'const' => 'one',
22 | 'method' => 'one',
23 | 'property' => 'one',
24 | ],
25 | ],
26 | 'class_definition' => [
27 | 'multi_line_extends_each_single_line' => true,
28 | 'single_item_single_line' => true,
29 | 'single_line' => true,
30 | ],
31 | 'concat_space' => [
32 | 'spacing' => 'none',
33 | ],
34 | 'declare_equal_normalize' => true,
35 | 'elseif' => true,
36 | 'encoding' => true,
37 | 'full_opening_tag' => true,
38 | 'fully_qualified_strict_types' => true, // added by Shift
39 | 'function_declaration' => true,
40 | 'function_typehint_space' => true,
41 | 'general_phpdoc_tag_rename' => true,
42 | 'heredoc_to_nowdoc' => true,
43 | 'include' => true,
44 | 'increment_style' => ['style' => 'post'],
45 | 'indentation_type' => true,
46 | 'linebreak_after_opening_tag' => true,
47 | 'line_ending' => true,
48 | 'lowercase_cast' => true,
49 | 'constant_case' => ['case' => 'lower'],
50 | 'lowercase_keywords' => true,
51 | 'lowercase_static_reference' => true, // added from Symfony
52 | 'magic_method_casing' => true, // added from Symfony
53 | 'magic_constant_casing' => true,
54 | 'method_argument_space' => true,
55 | 'native_function_casing' => true,
56 | 'no_alias_functions' => true,
57 | 'no_extra_blank_lines' => [
58 | 'tokens' => [
59 | 'extra',
60 | 'throw',
61 | 'use',
62 | 'use_trait',
63 | ],
64 | ],
65 | 'no_blank_lines_after_class_opening' => true,
66 | 'no_blank_lines_after_phpdoc' => true,
67 | 'no_closing_tag' => true,
68 | 'no_empty_phpdoc' => true,
69 | 'no_empty_statement' => true,
70 | 'no_leading_import_slash' => true,
71 | 'no_leading_namespace_whitespace' => true,
72 | 'no_mixed_echo_print' => [
73 | 'use' => 'echo',
74 | ],
75 | 'no_multiline_whitespace_around_double_arrow' => true,
76 | 'multiline_whitespace_before_semicolons' => [
77 | 'strategy' => 'no_multi_line',
78 | ],
79 | 'no_short_bool_cast' => true,
80 | 'no_singleline_whitespace_before_semicolons' => true,
81 | 'no_spaces_after_function_name' => true,
82 | 'no_spaces_around_offset' => [
83 | 'positions' => ['inside', 'outside'],
84 | ],
85 | 'no_spaces_inside_parenthesis' => true,
86 | 'no_trailing_comma_in_list_call' => true,
87 | 'no_trailing_comma_in_singleline_array' => true,
88 | 'no_trailing_whitespace' => true,
89 | 'no_trailing_whitespace_in_comment' => true,
90 | 'no_unneeded_control_parentheses' => [
91 | 'statements' => ['break', 'clone', 'continue', 'echo_print', 'return', 'switch_case', 'yield'],
92 | ],
93 | 'no_unreachable_default_argument_value' => true,
94 | 'no_useless_return' => true,
95 | 'no_whitespace_before_comma_in_array' => true,
96 | 'no_whitespace_in_blank_line' => true,
97 | 'normalize_index_brace' => true,
98 | 'not_operator_with_successor_space' => true,
99 | 'object_operator_without_whitespace' => true,
100 | 'ordered_imports' => [
101 | 'sort_algorithm' => 'alpha',
102 | ],
103 | 'phpdoc_indent' => true,
104 | 'phpdoc_inline_tag_normalizer' => true,
105 | 'phpdoc_no_access' => true,
106 | 'phpdoc_no_package' => true,
107 | 'phpdoc_no_useless_inheritdoc' => true,
108 | 'phpdoc_scalar' => true,
109 | 'phpdoc_single_line_var_spacing' => true,
110 | 'phpdoc_summary' => true,
111 | 'phpdoc_to_comment' => true,
112 | 'phpdoc_tag_type' => true,
113 | 'phpdoc_trim' => true,
114 | 'phpdoc_types' => true,
115 | 'phpdoc_var_without_name' => true,
116 | 'psr_autoloading' => true,
117 | 'self_accessor' => true,
118 | 'short_scalar_cast' => true,
119 | 'simplified_null_return' => false, // disabled by Shift
120 | 'single_blank_line_at_eof' => true,
121 | 'single_blank_line_before_namespace' => true,
122 | 'single_class_element_per_statement' => true, // here
123 | 'single_import_per_statement' => true,
124 | 'single_line_after_imports' => true,
125 | 'single_line_comment_style' => [
126 | 'comment_types' => ['hash']
127 | ],
128 | 'single_quote' => true,
129 | 'space_after_semicolon' => true,
130 | 'standardize_not_equals' => true,
131 | 'switch_case_semicolon_to_colon' => true,
132 | 'switch_case_space' => true,
133 | 'ternary_operator_spaces' => true,
134 | 'trailing_comma_in_multiline' => ['elements' => ['arrays']],
135 | 'trim_array_spaces' => true,
136 | 'unary_operator_spaces' => true,
137 | 'visibility_required' => [
138 | 'elements' => ['property', 'method', 'const'],
139 | ],
140 | 'whitespace_after_comma_in_array' => true,
141 | ];
142 |
143 | $finder = Finder::create()
144 | ->in([
145 | __DIR__ . '/app',
146 | __DIR__ . '/config',
147 | __DIR__ . '/database',
148 | __DIR__ . '/resources',
149 | __DIR__ . '/routes',
150 | __DIR__ . '/tests',
151 | ])
152 | ->name('*.php')
153 | ->notName('*.blade.php')
154 | ->ignoreDotFiles(true)
155 | ->ignoreVCS(true);
156 |
157 | return (new Config())
158 | ->setFinder($finder)
159 | ->setRules($rules)
160 | ->setRiskyAllowed(true)
161 | ->setUsingCache(true);
162 |
--------------------------------------------------------------------------------
/src/stubs/default/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 100,
3 | "singleQuote": true,
4 | "endOfLine": "lf",
5 | "tabWidth": 4,
6 | "semi": true
7 | }
8 |
--------------------------------------------------------------------------------
/src/stubs/default/phpstan.neon:
--------------------------------------------------------------------------------
1 | includes:
2 | - ./vendor/nunomaduro/larastan/extension.neon
3 |
4 | parameters:
5 |
6 | paths:
7 | - app
8 |
9 | # The level 8 is the highest level
10 | level: 5
11 |
12 | ignoreErrors:
13 | - '#Unsafe usage of new static#'
14 | - '#Access to an undefined property Illuminate\\Support\\HigherOrderCollectionProxy::\$[a-zA-Z0-9_]+#'
15 |
16 | excludes_analyse:
17 | - ./app/Http/Middleware/Authenticate.php
18 |
19 | checkMissingIterableValueType: false
20 |
--------------------------------------------------------------------------------
/src/stubs/default/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/src/stubs/default/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pktharindu/ttall/3ce4cb8d44b50c1ea1afce075a041da35e83294d/src/stubs/default/public/favicon.ico
--------------------------------------------------------------------------------
/src/stubs/default/resources/css/app.css:
--------------------------------------------------------------------------------
1 | /**
2 | * This injects Tailwind's base styles, which is a combination of
3 | * Normalize.css and some additional base styles.
4 | *
5 | * You can see the styles here:
6 | * https://unpkg.com/tailwindcss/dist/base.css
7 | */
8 | @tailwind base;
9 |
10 | /**
11 | * Remove the default box-shadow for invalid elements to prevent
12 | * inputs in Livewire components showing with a
13 | * red border by default in Firefox.
14 | *
15 | * See: https://github.com/laravel-frontend-presets/tall/issues/7
16 | */
17 | input:invalid, textarea:invalid, select:invalid {
18 | box-shadow: none;
19 | }
20 |
21 | /**
22 | * This injects any component classes registered by plugins.
23 | */
24 | @tailwind components;
25 |
26 | /**
27 | * Here you would add any of your custom component classes; stuff that you'd
28 | * want loaded *before* the utilities so that the utilities could still
29 | * override them.
30 | *
31 | * Example:
32 | *
33 | * .btn { ... }
34 | * .form-input { ... }
35 | */
36 |
37 | .turbolinks-progress-bar {
38 | @apply bg-indigo-500;
39 | }
40 |
41 | /**
42 | * This injects all of Tailwind's utility classes, generated based on your
43 | * config file.
44 | */
45 | @tailwind utilities;
46 |
47 | /**
48 | * Here you would add any custom utilities you need that don't come out of the
49 | * box with Tailwind.
50 | *
51 | * Example :
52 | *
53 | * .bg-pattern-graph-paper { ... }
54 | * .skew-45 { ... }
55 | */
56 |
57 | /**
58 | * x-cloak attributes are removed from elements when Alpine initializes.
59 | * This is useful for hiding pre-initialized DOM.
60 | *
61 | * See: https://github.com/alpinejs/alpine#x-cloak
62 | */
63 | [x-cloak] {
64 | display: none !important;
65 | }
--------------------------------------------------------------------------------
/src/stubs/default/resources/js/app.js:
--------------------------------------------------------------------------------
1 | import './bootstrap';
2 |
--------------------------------------------------------------------------------
/src/stubs/default/resources/js/bootstrap.js:
--------------------------------------------------------------------------------
1 | import 'alpinejs';
2 |
3 | /**
4 | * Echo exposes an expressive API for subscribing to channels and listening
5 | * for events that are broadcast by Laravel. Echo and event broadcasting
6 | * allows your team to easily build robust real-time web applications.
7 | */
8 |
9 | // import Echo from 'laravel-echo'
10 |
11 | // window.Pusher = require('pusher-js');
12 |
13 | // window.Echo = new Echo({
14 | // broadcaster: 'pusher',
15 | // key: process.env.MIX_PUSHER_APP_KEY,
16 | // cluster: process.env.MIX_PUSHER_APP_CLUSTER,
17 | // forceTLS: true
18 | // });
19 |
--------------------------------------------------------------------------------
/src/stubs/default/resources/js/turbolinks.js:
--------------------------------------------------------------------------------
1 | import Turbolinks from 'turbolinks';
2 |
3 | Turbolinks.start();
4 |
--------------------------------------------------------------------------------
/src/stubs/default/resources/views/components/logo.blade.php:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/stubs/default/resources/views/components/navbar.blade.php:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/stubs/default/resources/views/layouts/app.blade.php:
--------------------------------------------------------------------------------
1 | @extends('layouts.base')
2 |
3 | @section('body')
4 |
5 | @yield('content')
6 |
7 | @isset($slot)
8 | {{ $slot }}
9 | @endisset
10 | @endsection
--------------------------------------------------------------------------------
/src/stubs/default/resources/views/layouts/base.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | @hasSection('title')
12 | @yield('title') - {{ config('app.name') }}
13 | @else
14 | {{ config('app.name') }}
15 | @endif
16 |
17 |
18 |
19 |
20 |
21 | @stack('before-styles')
22 |
23 |
24 | @livewireStyles
25 | @stack('after-styles')
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | @yield('body')
39 |
40 | @livewireScripts
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/src/stubs/default/resources/views/vendor/pagination/default.blade.php:
--------------------------------------------------------------------------------
1 | @if ($paginator->hasPages())
2 |
69 | @endif
70 |
--------------------------------------------------------------------------------
/src/stubs/default/resources/views/vendor/pagination/simple-default.blade.php:
--------------------------------------------------------------------------------
1 | @if ($paginator->hasPages())
2 |
41 | @endif
42 |
43 |
--------------------------------------------------------------------------------
/src/stubs/default/resources/views/welcome.blade.php:
--------------------------------------------------------------------------------
1 | @extends('layouts.base')
2 |
3 | @section('body')
4 |
5 |
6 | @if (Route::has('login'))
7 |
28 | @endif
29 |
30 |
31 |
62 |
63 | @endsection
64 |
--------------------------------------------------------------------------------
/src/stubs/default/routes/web.php:
--------------------------------------------------------------------------------
1 | name('home');
17 |
--------------------------------------------------------------------------------
/src/stubs/default/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable global-require */
2 | const defaultTheme = require('tailwindcss/defaultTheme');
3 |
4 | module.exports = {
5 | theme: {
6 | extend: {
7 | fontFamily: {
8 | sans: ['Inter var', ...defaultTheme.fontFamily.sans],
9 | },
10 | },
11 | },
12 |
13 | variants: {},
14 |
15 | purge: {
16 | content: [
17 | './app/**/*.php',
18 | './resources/**/*.html',
19 | './resources/**/*.js',
20 | './resources/**/*.jsx',
21 | './resources/**/*.ts',
22 | './resources/**/*.tsx',
23 | './resources/**/*.php',
24 | './resources/**/*.vue',
25 | './resources/**/*.twig',
26 | ],
27 |
28 | options: {
29 | defaultExtractor: (content) => content.match(/[\w-/.:]+(?get('/')->assertSuccessful();
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/stubs/default/tests/TestCase.php:
--------------------------------------------------------------------------------
1 | swap(Mix::class, function () {
20 | return '';
21 | });
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/stubs/default/webpack.mix.js:
--------------------------------------------------------------------------------
1 | const mix = require("laravel-mix");
2 |
3 | /*
4 | |--------------------------------------------------------------------------
5 | | Mix Asset Management
6 | |--------------------------------------------------------------------------
7 | |
8 | | Mix provides a clean, fluent API for defining some Webpack build steps
9 | | for your Laravel application. By default, we are compiling the Sass
10 | | file for the application as well as bundling up all the JS files.
11 | |
12 | */
13 |
14 | mix.js('resources/js/app.js', 'public/js')
15 | .js('resources/js/turbolinks.js', 'public/js')
16 | .postCss('resources/css/app.css', 'public/css', [
17 | require('postcss-import'),
18 | require('tailwindcss'),
19 | require('autoprefixer'),
20 | ]).sourceMaps();
21 |
22 | if (mix.inProduction()) {
23 | mix.version();
24 | }
--------------------------------------------------------------------------------