├── .php-cs-fixer.dist.php ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json └── src ├── Concerns ├── GetsOnboarded.php └── Onboardable.php ├── Facades └── Onboard.php ├── OnboardServiceProvider.php ├── OnboardingManager.php ├── OnboardingStep.php └── OnboardingSteps.php /.php-cs-fixer.dist.php: -------------------------------------------------------------------------------- 1 | in([ 5 | __DIR__ . '/src', 6 | __DIR__ . '/tests', 7 | ]) 8 | ->name('*.php') 9 | ->notName('*.blade.php') 10 | ->ignoreDotFiles(true) 11 | ->ignoreVCS(true); 12 | 13 | return (new PhpCsFixer\Config()) 14 | ->setRules([ 15 | '@PSR12' => true, 16 | 'array_syntax' => ['syntax' => 'short'], 17 | 'ordered_imports' => ['sort_algorithm' => 'alpha'], 18 | 'no_unused_imports' => true, 19 | 'not_operator_with_successor_space' => true, 20 | 'trailing_comma_in_multiline' => true, 21 | 'phpdoc_scalar' => true, 22 | 'unary_operator_spaces' => true, 23 | 'binary_operator_spaces' => true, 24 | 'blank_line_before_statement' => [ 25 | 'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'try'], 26 | ], 27 | 'phpdoc_single_line_var_spacing' => true, 28 | 'phpdoc_var_without_name' => true, 29 | 'class_attributes_separation' => [ 30 | 'elements' => [ 31 | 'method' => 'one', 32 | ], 33 | ], 34 | 'method_argument_space' => [ 35 | 'on_multiline' => 'ensure_fully_multiline', 36 | 'keep_multiple_spaces_after_comma' => true, 37 | ], 38 | 'single_trait_insert_per_statement' => true, 39 | ]) 40 | ->setFinder($finder); 41 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `laravel-onboard` will be documented in this file. 4 | 5 | ## 2.6.2 - 2025-03-21 6 | 7 | ### What's Changed 8 | 9 | * Fix deprecation warning in PHP 8.4 by @austincarpenter in https://github.com/spatie/laravel-onboard/pull/45 10 | 11 | ### New Contributors 12 | 13 | * @austincarpenter made their first contribution in https://github.com/spatie/laravel-onboard/pull/45 14 | 15 | **Full Changelog**: https://github.com/spatie/laravel-onboard/compare/2.6.1...2.6.2 16 | 17 | ## 2.6.1 - 2025-02-14 18 | 19 | ### What's Changed 20 | 21 | * Bump dependabot/fetch-metadata from 1.6.0 to 2.2.0 by @dependabot in https://github.com/spatie/laravel-onboard/pull/40 22 | * Bump dependabot/fetch-metadata from 2.2.0 to 2.3.0 by @dependabot in https://github.com/spatie/laravel-onboard/pull/42 23 | * Laravel 12.x Compatibility by @laravel-shift in https://github.com/spatie/laravel-onboard/pull/43 24 | 25 | ### New Contributors 26 | 27 | * @laravel-shift made their first contribution in https://github.com/spatie/laravel-onboard/pull/43 28 | 29 | **Full Changelog**: https://github.com/spatie/laravel-onboard/compare/2.6.0...2.6.1 30 | 31 | ## 2.6.0 - 2024-02-14 32 | 33 | ### What's Changed 34 | 35 | * Add Laravel 11 support by @alexanderkroneis in https://github.com/spatie/laravel-onboard/pull/35 36 | 37 | **Full Changelog**: https://github.com/spatie/laravel-onboard/compare/2.5.0...2.6.0 38 | 39 | ## 2.5.0 - 2023-11-01 40 | 41 | ### What's Changed 42 | 43 | - Bump dependabot/fetch-metadata from 1.3.5 to 1.3.6 by @dependabot in https://github.com/spatie/laravel-onboard/pull/25 44 | - update Onboard facade docblock by @alphaolomi in https://github.com/spatie/laravel-onboard/pull/26 45 | - Fix inconsistent tabs/space usage in README by @arkoe in https://github.com/spatie/laravel-onboard/pull/27 46 | - Bump dependabot/fetch-metadata from 1.3.6 to 1.4.0 by @dependabot in https://github.com/spatie/laravel-onboard/pull/28 47 | - Bump dependabot/fetch-metadata from 1.4.0 to 1.5.1 by @dependabot in https://github.com/spatie/laravel-onboard/pull/30 48 | - Bump dependabot/fetch-metadata from 1.5.1 to 1.6.0 by @dependabot in https://github.com/spatie/laravel-onboard/pull/31 49 | - Alternative way to add onboarding steps by @fedeisas in https://github.com/spatie/laravel-onboard/pull/34 50 | - Bump stefanzweifel/git-auto-commit-action from 4 to 5 by @dependabot in https://github.com/spatie/laravel-onboard/pull/33 51 | - Bump actions/checkout from 3 to 4 by @dependabot in https://github.com/spatie/laravel-onboard/pull/32 52 | 53 | ### New Contributors 54 | 55 | - @alphaolomi made their first contribution in https://github.com/spatie/laravel-onboard/pull/26 56 | - @arkoe made their first contribution in https://github.com/spatie/laravel-onboard/pull/27 57 | - @fedeisas made their first contribution in https://github.com/spatie/laravel-onboard/pull/34 58 | 59 | **Full Changelog**: https://github.com/spatie/laravel-onboard/compare/2.4.1...2.5.0 60 | 61 | ## 2.4.1 - 2023-01-25 62 | 63 | - support L10 64 | 65 | ## 2.4.0 - 2022-09-24 66 | 67 | ### What's Changed 68 | 69 | - Allow steps to be limited to specific classes by @RhysLees in https://github.com/spatie/laravel-onboard/pull/15 70 | 71 | ### New Contributors 72 | 73 | - @RhysLees made their first contribution in https://github.com/spatie/laravel-onboard/pull/15 74 | 75 | **Full Changelog**: https://github.com/spatie/laravel-onboard/compare/2.3.0...2.4.0 76 | 77 | ## 2.3.0 - 2022-08-17 78 | 79 | ### What's Changed 80 | 81 | - Add callable attributes support by @talelmishali in https://github.com/spatie/laravel-onboard/pull/12 82 | 83 | ### New Contributors 84 | 85 | - @talelmishali made their first contribution in https://github.com/spatie/laravel-onboard/pull/12 86 | 87 | **Full Changelog**: https://github.com/spatie/laravel-onboard/compare/2.2.0...2.3.0 88 | 89 | ## 2.2.0 - 2022-08-10 90 | 91 | ### What's Changed 92 | 93 | - Added variable name clarification by @titonova in https://github.com/spatie/laravel-onboard/pull/5 94 | - Bump dependabot/fetch-metadata from 1.3.1 to 1.3.3 by @dependabot in https://github.com/spatie/laravel-onboard/pull/6 95 | - Add Arrayable support by @mpociot in https://github.com/spatie/laravel-onboard/pull/11 96 | 97 | ### New Contributors 98 | 99 | - @titonova made their first contribution in https://github.com/spatie/laravel-onboard/pull/5 100 | - @dependabot made their first contribution in https://github.com/spatie/laravel-onboard/pull/6 101 | - @mpociot made their first contribution in https://github.com/spatie/laravel-onboard/pull/11 102 | 103 | **Full Changelog**: https://github.com/spatie/laravel-onboard/compare/2.1.1...2.2.0 104 | 105 | ## 2.1.1 - 2022-06-23 106 | 107 | ### What's Changed 108 | 109 | - Update binding method by @tompec in https://github.com/spatie/laravel-onboard/pull/4 110 | 111 | **Full Changelog**: https://github.com/spatie/laravel-onboard/compare/2.1.0...2.1.1 112 | 113 | ## 2.1.0 - 2022-06-22 114 | 115 | ### What's Changed 116 | 117 | - Fix code example by @tompec in https://github.com/spatie/laravel-onboard/pull/1 118 | - Excluding steps based on condition by @MohmmedAshraf in https://github.com/spatie/laravel-onboard/pull/3 119 | 120 | ### New Contributors 121 | 122 | - @tompec made their first contribution in https://github.com/spatie/laravel-onboard/pull/1 123 | - @MohmmedAshraf made their first contribution in https://github.com/spatie/laravel-onboard/pull/3 124 | 125 | **Full Changelog**: https://github.com/spatie/laravel-onboard/compare/2.0.0...2.1.0 126 | 127 | ## 2.0.0 - 2022-06-17 128 | 129 | ### What's changed 130 | 131 | - You can now add onboarding to any model using the trait & interface 132 | - Added dependency injection to the `completeIf` callback 133 | - The `completeIf` callback is now cached using `spatie/once` to only run once per request 134 | - Added a `percentageCompleted` method 135 | 136 | ### Upgrading from v1 to v2 137 | 138 | - Support for PHP 7.4 has been dropped 139 | - Support for Laravel 7 and 8 has been dropped 140 | - The `\Spatie\Onboard\OnboardFacade` has been moved to `\Spatie\Onboard\Facades\Onboard` 141 | - The `\Spatie\Onboard\GetsOnboarded` trait has been moved to `\Spatie\Onboard\Concerns\GetsOnboarded` 142 | - You should add the new `\Spatie\Onboard\Concerns\Onboardable` interface to your `User` model 143 | - The `$user` parameter in the `completeIf` callback has been renamed to `$model` and it now supports dependency injection 144 | 145 | **Full Changelog**: https://github.com/spatie/laravel-onboard/compare/1.0.0...2.0.0 146 | 147 | ## 1.0.0 - 2022-06-17 148 | 149 | ### First release 150 | 151 | - Compatible with https://github.com/calebporzio/onboard 152 | - Should only need to change the namespace 153 | 154 | **Full Changelog**: https://github.com/spatie/laravel-onboard/compare/0.0.2...1.0.0 155 | 156 | ## 0.0.2 - 2022-06-17 157 | 158 | Experimental release 159 | 160 | **Full Changelog**: https://github.com/spatie/laravel-onboard/compare/0.0.1...0.0.2 161 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) spatie 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A Laravel package to help track user onboarding steps 2 | 3 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/spatie/laravel-onboard.svg?style=flat-square)](https://packagist.org/packages/spatie/laravel-onboard) 4 | [![Total Downloads](https://img.shields.io/packagist/dt/spatie/laravel-onboard.svg?style=flat-square)](https://packagist.org/packages/spatie/laravel-onboard) 5 | 6 | This package lets you set up an onboarding flow for your application's users. 7 | 8 | Here's an example of how it's set up: 9 | 10 | ```php 11 | use App\User; 12 | use Spatie\Onboard\Facades\Onboard; 13 | 14 | Onboard::addStep('Complete Profile') 15 | ->link('/profile') 16 | ->cta('Complete') 17 | ->completeIf(function (User $model) { 18 | return $model->profile->isComplete(); 19 | }); 20 | 21 | Onboard::addStep('Create Your First Post') 22 | ->link('/post/create') 23 | ->cta('Create Post') 24 | ->completeIf(function (User $model) { 25 | return $model->posts->count() > 0; 26 | }); 27 | ``` 28 | 29 | You can then render this onboarding flow however you want in your templates: 30 | 31 | ```blade 32 | @if (auth()->user()->onboarding()->inProgress()) 33 |
34 | @foreach (auth()->user()->onboarding()->steps as $step) 35 | 36 | @if($step->complete()) 37 | 38 | {{ $loop->iteration }}. {{ $step->title }} 39 | @else 40 | 41 | {{ $loop->iteration }}. {{ $step->title }} 42 | @endif 43 | 44 | 45 | complete() ? 'disabled' : '' }}> 46 | {{ $step->cta }} 47 | 48 | @endforeach 49 |
50 | @endif 51 | ``` 52 | 53 | ## Support us 54 | 55 | [](https://spatie.be/github-ad-click/laravel-onboard) 56 | 57 | We invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source). You can support us by [buying one of our paid products](https://spatie.be/open-source/support-us). 58 | 59 | We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on [our contact page](https://spatie.be/about-us). We publish all received postcards on [our virtual postcard wall](https://spatie.be/open-source/postcards). 60 | 61 | ## Installation 62 | 63 | You can install the package via composer: 64 | 65 | ```bash 66 | composer require spatie/laravel-onboard 67 | ``` 68 | 69 | ## Usage 70 | 71 | Add the `Spatie\Onboard\Concerns\GetsOnboarded` trait and `Spatie\Onboard\Concerns\Onboardable` interface to any model or class in your app, for example the `User` model: 72 | 73 | ```php 74 | class User extends Model implements \Spatie\Onboard\Concerns\Onboardable 75 | { 76 | use \Spatie\Onboard\Concerns\GetsOnboarded; 77 | ... 78 | ``` 79 | 80 | ### Example configuration 81 | 82 | Configure your steps in your `App\Providers\AppServiceProvider.php` 83 | 84 | ```php 85 | use App\User; 86 | use Spatie\Onboard\Facades\Onboard; 87 | 88 | class AppServiceProvider extends ServiceProvider 89 | { 90 | // ... 91 | 92 | public function boot() 93 | { 94 | Onboard::addStep('Complete Profile') 95 | ->link('/profile') 96 | ->cta('Complete') 97 | /** 98 | * The completeIf will pass the class that you've added the 99 | * interface & trait to. You can use Laravel's dependency 100 | * injection here to inject anything else as well. 101 | */ 102 | ->completeIf(function (User $model) { 103 | return $model->profile->isComplete(); 104 | }); 105 | 106 | Onboard::addStep('Create Your First Post') 107 | ->link('/post/create') 108 | ->cta('Create Post') 109 | ->completeIf(function (User $model) { 110 | return $model->posts->count() > 0; 111 | }); 112 | ``` 113 | 114 | The variable name passed to the `completeIf` callback must be `$model`. 115 | 116 | ### Usage 117 | 118 | Now you can access these steps along with their state wherever you like. Here is an example blade template: 119 | 120 | ```blade 121 | @if (auth()->user()->onboarding()->inProgress()) 122 |
123 | @foreach (auth()->user()->onboarding()->steps as $step) 124 | 125 | @if($step->complete()) 126 | 127 | {{ $loop->iteration }}. {{ $step->title }} 128 | @else 129 | 130 | {{ $loop->iteration }}. {{ $step->title }} 131 | @endif 132 | 133 | 134 | complete() ? 'disabled' : '' }}> 135 | {{ $step->cta }} 136 | 137 | @endforeach 138 |
139 | @endif 140 | ``` 141 | 142 | Check out all the available features below: 143 | 144 | ```php 145 | /** @var \Spatie\Onboard\OnboardingManager $onboarding **/ 146 | $onboarding = Auth::user()->onboarding(); 147 | 148 | $onboarding->inProgress(); 149 | 150 | $onboarding->percentageCompleted(); 151 | 152 | $onboarding->finished(); 153 | 154 | $onboarding->steps()->each(function($step) { 155 | $step->title; 156 | $step->cta; 157 | $step->link; 158 | $step->complete(); 159 | $step->incomplete(); 160 | }); 161 | ``` 162 | 163 | Excluding steps based on condition: 164 | 165 | ```php 166 | Onboard::addStep('Excluded Step') 167 | ->excludeIf(function (User $model) { 168 | return $model->isAdmin(); 169 | }); 170 | ``` 171 | 172 | Limiting steps to a specific class: 173 | 174 | ```php 175 | Onboard::addStep('Limited Step', User::class) 176 | ->link('/post/create'); 177 | 178 | // or 179 | 180 | Onboard::addStep('Limited Step', 'App\Models\User') 181 | ->link('/post/create'); 182 | ``` 183 | 184 | When using limited steps, steps that are not limited will be available to all classes. For example: 185 | 186 | ```php 187 | // Defining User steps 188 | Onboard::addStep('Limited User Step', User::class) 189 | ->link('/post/create'); 190 | 191 | // Defining Team steps 192 | Onboard::addStep('Limited Team Step', Team::class) 193 | ->link('/post/create'); 194 | 195 | // Defining a step that is available to all classes 196 | Onboard::addStep('Normal Step') 197 | ->link('/post/create'); 198 | ``` 199 | 200 | The above will result in 1 step being available to all classes, and 2 steps being available to the `User` and `Team` classes: 201 | 202 | `Other` classes will only see the `Normal Step`. 203 | `User` classes will both see the `Normal Step` and `Limited User Step`. 204 | `Team` classes will both see the `Normal Step` and `Limited Team Step`. 205 | 206 | 207 | Definining custom attributes and accessing them: 208 | 209 | ```php 210 | // Defining the attributes 211 | Onboard::addStep('Step w/ custom attributes') 212 | ->attributes([ 213 | 'name' => 'Waldo', 214 | 'shirt_color' => 'Red & White', 215 | ]); 216 | 217 | // Accessing them 218 | $step->name; 219 | $step->shirt_color; 220 | ``` 221 | 222 | ### Example middleware 223 | 224 | If you want to ensure that your User is redirected to the next unfinished onboarding step, whenever they access your web application, you can use the following middleware as a starting point: 225 | 226 | ```php 227 | user()->onboarding()->inProgress()) { 239 | return redirect()->to( 240 | auth()->user()->onboarding()->nextUnfinishedStep()->link 241 | ); 242 | } 243 | 244 | return $next($request); 245 | } 246 | } 247 | ``` 248 | 249 | **Quick tip:** Don't add this middleware to routes that update the state of the onboarding steps, your users will not be able to progress because they will be redirected back to the onboarding step. 250 | 251 | ## Testing 252 | 253 | ```bash 254 | composer test 255 | ``` 256 | 257 | ## Changelog 258 | 259 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 260 | 261 | ## Contributing 262 | 263 | Please see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for details. 264 | 265 | ## Security Vulnerabilities 266 | 267 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities. 268 | 269 | ## Credits 270 | - [Rias Van der Veken](https://github.com/riasvdv) 271 | - [All Contributors](../../contributors) 272 | 273 | The original code from this package came from [Onboard](https://github.com/calebporzio/onboard) by [Caleb Porzio](https://github.com/calebporzio), who was gratious enough to let us continue development. 274 | 275 | ## License 276 | 277 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 278 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spatie/laravel-onboard", 3 | "description": "A Laravel package to help track user onboarding steps", 4 | "keywords": [ 5 | "spatie", 6 | "laravel", 7 | "laravel-onboard" 8 | ], 9 | "homepage": "https://github.com/spatie/laravel-onboard", 10 | "license": "MIT", 11 | "authors": [ 12 | { 13 | "name": "Rias Van der Veken", 14 | "email": "rias@spatie.be", 15 | "role": "Developer" 16 | } 17 | ], 18 | "require": { 19 | "php": "^8.0", 20 | "illuminate/contracts": "^9.0|^10.0|^11.0|^12.0", 21 | "spatie/laravel-package-tools": "^1.9.2", 22 | "spatie/once": "^3.1" 23 | }, 24 | "require-dev": { 25 | "orchestra/testbench": "^7.0|^8.0|^9.0|^10.0", 26 | "pestphp/pest": "^1.21|^2.0|^3.0", 27 | "pestphp/pest-plugin-laravel": "^1.1|^2.0|^3.0", 28 | "phpstan/extension-installer": "^1.1|^2.0", 29 | "phpstan/phpstan-deprecation-rules": "^1.0", 30 | "phpstan/phpstan-phpunit": "^1.0", 31 | "spatie/laravel-ray": "^1.26" 32 | }, 33 | "autoload": { 34 | "psr-4": { 35 | "Spatie\\Onboard\\": "src", 36 | "Spatie\\Onboard\\Database\\Factories\\": "database/factories" 37 | } 38 | }, 39 | "autoload-dev": { 40 | "psr-4": { 41 | "Spatie\\Onboard\\Tests\\": "tests" 42 | } 43 | }, 44 | "scripts": { 45 | "analyse": "vendor/bin/phpstan analyse", 46 | "test": "vendor/bin/pest", 47 | "test-coverage": "vendor/bin/pest --coverage", 48 | "format": "vendor/bin/php-cs-fixer fix --allow-risky=yes" 49 | }, 50 | "config": { 51 | "sort-packages": true, 52 | "allow-plugins": { 53 | "pestphp/pest-plugin": true, 54 | "phpstan/extension-installer": true 55 | } 56 | }, 57 | "extra": { 58 | "laravel": { 59 | "providers": [ 60 | "Spatie\\Onboard\\OnboardServiceProvider" 61 | ], 62 | "aliases": { 63 | "Onboard": "Spatie\\Onboard\\OnboardFacade" 64 | } 65 | } 66 | }, 67 | "minimum-stability": "dev", 68 | "prefer-stable": true 69 | } 70 | -------------------------------------------------------------------------------- /src/Concerns/GetsOnboarded.php: -------------------------------------------------------------------------------- 1 | $this]); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Concerns/Onboardable.php: -------------------------------------------------------------------------------- 1 | steps(Onboardable $model) 14 | */ 15 | class Onboard extends Facade 16 | { 17 | protected static function getFacadeAccessor(): string 18 | { 19 | return OnboardingSteps::class; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/OnboardServiceProvider.php: -------------------------------------------------------------------------------- 1 | name('laravel-onboard'); 14 | 15 | $this->app->singleton(OnboardingSteps::class); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/OnboardingManager.php: -------------------------------------------------------------------------------- 1 | */ 10 | public Collection $steps; 11 | 12 | public function __construct($model, OnboardingSteps $onboardingSteps) 13 | { 14 | $this->steps = $onboardingSteps->steps($model); 15 | } 16 | 17 | /** @return Collection */ 18 | public function steps(): Collection 19 | { 20 | return $this->steps; 21 | } 22 | 23 | public function inProgress(): bool 24 | { 25 | return ! $this->finished(); 26 | } 27 | 28 | public function finished(): bool 29 | { 30 | return $this->steps 31 | ->filter(fn (OnboardingStep $step) => $step->incomplete()) 32 | ->filter(fn (OnboardingStep $step) => $step->notExcluded()) 33 | ->isEmpty(); 34 | } 35 | 36 | public function nextUnfinishedStep(): ?OnboardingStep 37 | { 38 | return $this->steps->first(fn (OnboardingStep $step) => $step->incomplete()); 39 | } 40 | 41 | public function percentageCompleted(): float 42 | { 43 | $totalCompleteSteps = $this->steps 44 | ->filter(fn (OnboardingStep $step) => $step->complete()) 45 | ->count(); 46 | 47 | return $totalCompleteSteps / $this->steps->count() * 100; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/OnboardingStep.php: -------------------------------------------------------------------------------- 1 | attributes(['title' => $title]); 27 | } 28 | 29 | public function cta(string $cta): self 30 | { 31 | $this->attributes(['cta' => $cta]); 32 | 33 | return $this; 34 | } 35 | 36 | public function link(string $link): self 37 | { 38 | $this->attributes(['link' => $link]); 39 | 40 | return $this; 41 | } 42 | 43 | public function excludeIf(callable $callback): self 44 | { 45 | $this->excludeIf = $callback; 46 | 47 | return $this; 48 | } 49 | 50 | public function completeIf(callable $callback): self 51 | { 52 | $this->completeIf = $callback; 53 | 54 | return $this; 55 | } 56 | 57 | public function setModel(Onboardable $model): self 58 | { 59 | $this->model = $model; 60 | 61 | return $this; 62 | } 63 | 64 | public function setCallableAttributes(): void 65 | { 66 | if (is_null($this->callableAttributes)) { 67 | return; 68 | } 69 | 70 | $this->attributes(once(fn () => app()->call($this->callableAttributes, ['model' => $this->model]))); 71 | } 72 | 73 | public function initiate(Onboardable $model): self 74 | { 75 | $this->setModel($model); 76 | 77 | $this->setCallableAttributes(); 78 | 79 | return $this; 80 | } 81 | 82 | public function excluded(): bool 83 | { 84 | if ($this->excludeIf && $this->model) { 85 | return once(fn () => app()->call($this->excludeIf, ['model' => $this->model])); 86 | } 87 | 88 | return false; 89 | } 90 | 91 | public function notExcluded(): bool 92 | { 93 | return ! $this->excluded(); 94 | } 95 | 96 | public function complete(): bool 97 | { 98 | if ($this->completeIf && $this->model) { 99 | return once(fn () => app()->call($this->completeIf, ['model' => $this->model])); 100 | } 101 | 102 | return false; 103 | } 104 | 105 | public function incomplete(): bool 106 | { 107 | return ! $this->complete(); 108 | } 109 | 110 | public function attribute(string $key, mixed $default = null): mixed 111 | { 112 | return Arr::get($this->attributes, $key, $default); 113 | } 114 | 115 | public function attributes(array|callable $attributes): self 116 | { 117 | if (is_callable($attributes)) { 118 | $this->callableAttributes = $attributes; 119 | } 120 | 121 | if (is_array($attributes)) { 122 | $this->attributes = array_merge($this->attributes, $attributes); 123 | } 124 | 125 | return $this; 126 | } 127 | 128 | public function __get(string $key): mixed 129 | { 130 | return $this->attribute($key); 131 | } 132 | 133 | public function __set(string $key, mixed $value): void 134 | { 135 | $this->attributes[$key] = $value; 136 | } 137 | 138 | public function __isset(string $key): bool 139 | { 140 | return isset($this->attributes[$key]); 141 | } 142 | 143 | public function toArray() 144 | { 145 | return array_merge($this->attributes, [ 146 | 'complete' => $this->complete(), 147 | 'excluded' => $this->excluded(), 148 | ]); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/OnboardingSteps.php: -------------------------------------------------------------------------------- 1 | */ 11 | protected array $steps = []; 12 | 13 | public function addStep(string $title, ?string $model = null): OnboardingStep 14 | { 15 | $step = new OnboardingStep($title); 16 | 17 | return $this->addingStep($step, $model); 18 | } 19 | 20 | public function addingStep(OnboardingStep $step, ?string $model = null): OnboardingStep 21 | { 22 | if ($model && new $model() instanceof Onboardable) { 23 | return $this->steps[$model][] = $step; 24 | } 25 | 26 | return $this->steps['default'][] = $step; 27 | } 28 | 29 | public function steps(Onboardable $model): Collection 30 | { 31 | return collect($this->getStepsArray($model)) 32 | ->map(fn (OnboardingStep $step) => $step->initiate($model)) 33 | ->filter(fn (OnboardingStep $step) => $step->notExcluded()); 34 | } 35 | 36 | private function getStepsArray(Onboardable $model): array 37 | { 38 | $key = get_class($model); 39 | 40 | if (key_exists($key, $this->steps)) { 41 | return array_merge( 42 | $this->steps[$key], 43 | $this->steps['default'] ?? [] 44 | ); 45 | } 46 | 47 | return $this->steps['default'] ?? []; 48 | } 49 | } 50 | --------------------------------------------------------------------------------