Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app-modules/onboarding/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"extra": {
"laravel": {
"providers": [
"He4rt\\Onboarding\\Providers\\OnboardingServiceProvider"
"He4rt\\Onboarding\\OnboardingServiceProvider"
]
}
}
Expand Down
Empty file.
30 changes: 30 additions & 0 deletions app-modules/onboarding/database/factories/OnboardingFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace He4rt\Onboarding\Database\Factories;

use He4rt\Identity\Tenant\Models\Tenant;
use He4rt\Identity\User\Models\User;
use He4rt\Onboarding\Enums\OnboardingStatus;
use He4rt\Onboarding\Enums\OnboardingType;
use He4rt\Onboarding\Models\Onboarding;
use Illuminate\Database\Eloquent\Factories\Factory;

/** @extends Factory<Onboarding> */
final class OnboardingFactory extends Factory
{
protected $model = Onboarding::class;

public function definition(): array
{
return [
'tenant_id' => Tenant::factory(),
'user_id' => User::factory(),
'type' => OnboardingType::Welcome,
'status' => OnboardingStatus::InProgress,
'completed_at' => null,
'paused_at' => null,
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace He4rt\Onboarding\Database\Factories;

use He4rt\Onboarding\Enums\OnboardingStepStatus;
use He4rt\Onboarding\Models\Onboarding;
use He4rt\Onboarding\Models\OnboardingStep;
use Illuminate\Database\Eloquent\Factories\Factory;

/** @extends Factory<OnboardingStep> */
final class OnboardingStepFactory extends Factory
{
protected $model = OnboardingStep::class;

public function definition(): array
{
return [
'onboarding_id' => Onboarding::factory(),
'step_key' => 'form',
'status' => OnboardingStepStatus::Pending,
'data' => null,
];
}
}
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

use He4rt\Onboarding\Enums\OnboardingStatus;
use He4rt\Onboarding\Enums\OnboardingType;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
public function up(): void
{
Schema::create('onboardings', static function (Blueprint $table): void {
$table->uuid('id')->primary();
$table->foreignUuid('tenant_id')->constrained('tenants')->cascadeOnDelete();
$table->foreignUuid('user_id')->constrained('users')->cascadeOnDelete();
$table->string('type')->comment(OnboardingType::stringifyCases());
$table->string('status')->comment(OnboardingStatus::stringifyCases());
$table->timestampTz('completed_at')->nullable();
$table->timestampTz('paused_at')->nullable();
$table->timestampsTz();

$table->unique(['tenant_id', 'user_id', 'type']);
});
}

public function down(): void
{
Schema::dropIfExists('onboardings');
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

use He4rt\Onboarding\Enums\OnboardingStepStatus;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
public function up(): void
{
Schema::create('onboarding_steps', static function (Blueprint $table): void {
$table->uuid('id')->primary();
$table->foreignUuid('onboarding_id')->constrained('onboardings')->cascadeOnDelete();
$table->string('step_key');
$table->string('status')->comment(OnboardingStepStatus::stringifyCases());
$table->jsonb('data')->nullable();
$table->timestampTz('completed_at')->nullable();
$table->timestampsTz();

$table->unique(['onboarding_id', 'step_key']);
});
}

public function down(): void
{
Schema::dropIfExists('onboarding_steps');
}
};
41 changes: 41 additions & 0 deletions app-modules/onboarding/src/Actions/StartOnboarding.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace He4rt\Onboarding\Actions;

use He4rt\Identity\Tenant\Models\Tenant;
use He4rt\Identity\User\Models\User;
use He4rt\Onboarding\Enums\OnboardingStatus;
use He4rt\Onboarding\Enums\OnboardingStepStatus;
use He4rt\Onboarding\Enums\OnboardingType;
use He4rt\Onboarding\Models\Onboarding;
use Illuminate\Support\Facades\DB;

final class StartOnboarding
{
public function handle(Tenant $tenant, User $user, OnboardingType $type): Onboarding
{
// Resolve o flow antes de qualquer escrita: tipo sem handler falha alto,
// sem deixar um onboarding órfão (em vez de persistir um estado travado).
$flow = $type->handler();

return DB::transaction(static function () use ($tenant, $user, $type, $flow): Onboarding {
$onboarding = Onboarding::query()->firstOrCreate(
[
'tenant_id' => $tenant->getKey(),
'user_id' => $user->getKey(),
'type' => $type,
],
['status' => OnboardingStatus::InProgress],
);

$onboarding->steps()->firstOrCreate(
['step_key' => $flow->steps()[0]],
['status' => OnboardingStepStatus::Pending],
);

return $onboarding;
});
}
}
35 changes: 35 additions & 0 deletions app-modules/onboarding/src/Contracts/OnboardingFlow.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

namespace He4rt\Onboarding\Contracts;

use He4rt\Onboarding\Enums\OnboardingType;
use He4rt\Onboarding\Models\Onboarding;

interface OnboardingFlow
{
/**
* The ordered step keys that make up this flow.
*
* @return list<string>
*/
public function steps(): array;

/**
* The onboarding types that must be completed before this one can start.
*
* @return list<OnboardingType>
*/
public function prerequisites(): array;

/**
* Move the onboarding to its next step, completing it when the flow is done.
*/
public function advance(Onboarding $onboarding): void;

/**
* Whether every step of the flow has been completed.
*/
public function isComplete(Onboarding $onboarding): bool;
}
17 changes: 17 additions & 0 deletions app-modules/onboarding/src/Enums/OnboardingStatus.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

declare(strict_types=1);

namespace He4rt\Onboarding\Enums;

use App\Enums\Concerns\StringifyEnum;

enum OnboardingStatus: string
{
use StringifyEnum;

case InProgress = 'in_progress';
case Paused = 'paused';
case Completed = 'completed';
case Rejected = 'rejected';
}
15 changes: 15 additions & 0 deletions app-modules/onboarding/src/Enums/OnboardingStepStatus.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace He4rt\Onboarding\Enums;

use App\Enums\Concerns\StringifyEnum;

enum OnboardingStepStatus: string
{
use StringifyEnum;

case Pending = 'pending';
case Done = 'done';
}
26 changes: 26 additions & 0 deletions app-modules/onboarding/src/Enums/OnboardingType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace He4rt\Onboarding\Enums;

use App\Enums\Concerns\StringifyEnum;
use He4rt\Onboarding\Contracts\OnboardingFlow;
use He4rt\Onboarding\Flows\WelcomeOnboardingFlow;
use LogicException;

enum OnboardingType: string
{
use StringifyEnum;

case Welcome = 'welcome';
case Squads = 'squads';

public function handler(): OnboardingFlow
{
return match ($this) {
self::Welcome => resolve(WelcomeOnboardingFlow::class),
default => throw new LogicException(sprintf("OnboardingFlow para o tipo '%s' ainda não foi implementado.", $this->value)),
};
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
}
48 changes: 48 additions & 0 deletions app-modules/onboarding/src/Flows/WelcomeOnboardingFlow.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

declare(strict_types=1);

namespace He4rt\Onboarding\Flows;

use He4rt\Onboarding\Contracts\OnboardingFlow;
use He4rt\Onboarding\Enums\OnboardingStatus;
use He4rt\Onboarding\Enums\OnboardingStepStatus;
use He4rt\Onboarding\Models\Onboarding;

final class WelcomeOnboardingFlow implements OnboardingFlow
{
public function steps(): array
{
return ['form'];
}

public function prerequisites(): array
{
return [];
}

public function advance(Onboarding $onboarding): void
{
$advanced = $onboarding->steps()
->where('status', OnboardingStepStatus::Pending)->oldest()
->first()
?->update([
'status' => OnboardingStepStatus::Done,
'completed_at' => now(),
]);

if ($advanced && $onboarding->status !== OnboardingStatus::Completed && $this->isComplete($onboarding)) {
$onboarding->update([
'status' => OnboardingStatus::Completed,
'completed_at' => now(),
]);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

public function isComplete(Onboarding $onboarding): bool
{
return $onboarding->steps()
->where('status', OnboardingStepStatus::Done)
->count() === count($this->steps());
}
}
68 changes: 68 additions & 0 deletions app-modules/onboarding/src/Models/Onboarding.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

declare(strict_types=1);

namespace He4rt\Onboarding\Models;

use Carbon\CarbonInterface;
use He4rt\Identity\Tenant\Models\Tenant;
use He4rt\Identity\User\Models\User;
use He4rt\Onboarding\Database\Factories\OnboardingFactory;
use He4rt\Onboarding\Enums\OnboardingStatus;
use He4rt\Onboarding\Enums\OnboardingType;
use Illuminate\Database\Eloquent\Attributes\Table;
use Illuminate\Database\Eloquent\Attributes\UseFactory;
use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;

/**
* @property string $id
* @property string $tenant_id
* @property string $user_id
* @property OnboardingType $type
* @property OnboardingStatus $status
* @property CarbonInterface|null $completed_at
* @property CarbonInterface|null $paused_at
* @property CarbonInterface|null $created_at
* @property CarbonInterface|null $updated_at
*/
#[Table('onboardings')]
#[UseFactory(OnboardingFactory::class)]
final class Onboarding extends Model
{
/** @use HasFactory<OnboardingFactory> */
use HasFactory;
use HasUuids;

/** @return BelongsTo<Tenant, $this> */
public function tenant(): BelongsTo
{
return $this->belongsTo(Tenant::class);
}

/** @return BelongsTo<User, $this> */
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}

/** @return HasMany<OnboardingStep, $this> */
public function steps(): HasMany
{
return $this->hasMany(OnboardingStep::class);
}

/** @return array<string, mixed> */
protected function casts(): array
{
return [
'type' => OnboardingType::class,
'status' => OnboardingStatus::class,
'completed_at' => 'datetime',
'paused_at' => 'datetime',
];
}
Comment thread
danielhe4rt marked this conversation as resolved.
}
Loading