-
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: Improved feature banner persistence
- Loading branch information
1 parent
2fb9a74
commit d84a232
Showing
6 changed files
with
320 additions
and
40 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace App\Models; | ||
|
||
use Database\Factories\UserDismissalFactory; | ||
use Illuminate\Database\Eloquent\Builder; | ||
use Illuminate\Database\Eloquent\Factories\HasFactory; | ||
use Illuminate\Database\Eloquent\Model; | ||
use Illuminate\Database\Eloquent\Relations\BelongsTo; | ||
|
||
/** | ||
* Represents a user's dismissal or completion of various features or guides. | ||
* | ||
* This model can be used to track when a user has dismissed a feature, | ||
* completed an intro guide, or any similar action that should be remembered. | ||
* | ||
* @method static Builder|UserDismissal ofType(string $type) | ||
* @method static UserDismissalFactory factory(...$parameters) | ||
*/ | ||
class UserDismissal extends Model | ||
{ | ||
/** @use HasFactory<UserDismissalFactory> */ | ||
use HasFactory; | ||
|
||
/** | ||
* The attributes that are mass assignable. | ||
* | ||
* @var array<int, string> | ||
*/ | ||
protected $fillable = [ | ||
'user_id', | ||
'dismissable_type', | ||
'dismissable_id', | ||
'dismissed_at', | ||
]; | ||
|
||
/** | ||
* Check if a specific item has been dismissed by the user. | ||
*/ | ||
public static function isDismissed(int $userId, string $type, string|int $id): bool | ||
{ | ||
return static::where('user_id', $userId) | ||
->where('dismissable_type', $type) | ||
->where('dismissable_id', $id) | ||
->exists(); | ||
} | ||
|
||
/** | ||
* Dismiss a specific item for a user. | ||
*/ | ||
public static function dismiss(int $userId, string $type, string|int $id): static | ||
{ | ||
/** @var static $dismissal */ | ||
$dismissal = static::create([ | ||
'user_id' => $userId, | ||
'dismissable_type' => $type, | ||
'dismissable_id' => $id, | ||
'dismissed_at' => now(), | ||
]); | ||
|
||
return $dismissal; | ||
} | ||
|
||
/** | ||
* Create a new factory instance for the model. | ||
*/ | ||
protected static function newFactory(): UserDismissalFactory | ||
{ | ||
return UserDismissalFactory::new(); | ||
} | ||
|
||
/** | ||
* Get the user that owns the dismissal. | ||
* | ||
* @return BelongsTo<User, UserDismissal> | ||
*/ | ||
public function user(): BelongsTo | ||
{ | ||
return $this->belongsTo(User::class); | ||
} | ||
|
||
/** | ||
* Scope a query to only include dismissals of a specific type. | ||
* | ||
* @param Builder<UserDismissal> $builder | ||
* @return Builder<UserDismissal> | ||
*/ | ||
public function scopeOfType(Builder $builder, string $type): Builder | ||
{ | ||
return $builder->where('dismissable_type', $type); | ||
} | ||
|
||
/** | ||
* The attributes that should be cast. | ||
* | ||
* @return array<string, string> | ||
*/ | ||
protected function casts(): array | ||
{ | ||
return [ | ||
'dismissed_at' => 'datetime', | ||
]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
<?php | ||
|
||
namespace Database\Factories; | ||
|
||
use App\Models\User; | ||
use App\Models\UserDismissal; | ||
use Illuminate\Database\Eloquent\Factories\Factory; | ||
|
||
/** | ||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\UserDismissal> | ||
*/ | ||
class UserDismissalFactory extends Factory | ||
{ | ||
/** | ||
* The name of the factory's corresponding model. | ||
* | ||
* @var string | ||
*/ | ||
protected $model = UserDismissal::class; | ||
|
||
/** | ||
* Define the model's default state. | ||
* | ||
* @return array<string, mixed> | ||
*/ | ||
public function definition(): array | ||
{ | ||
return [ | ||
'user_id' => User::factory(), | ||
'dismissable_type' => $this->faker->randomElement(['feature']), | ||
'dismissable_id' => $this->faker->word(), | ||
'dismissed_at' => $this->faker->dateTimeBetween('-1 year', 'now'), | ||
]; | ||
} | ||
|
||
/** | ||
* Indicate that the dismissal is for a feature. | ||
*/ | ||
public function feature(): Factory | ||
{ | ||
return $this->state(function (array $attributes) { | ||
return [ | ||
'dismissable_type' => 'feature', | ||
'dismissable_id' => $this->faker->randomElement(['new_backup_system', 'dark_mode', 'api_integration']), | ||
]; | ||
}); | ||
} | ||
} |
28 changes: 28 additions & 0 deletions
28
database/migrations/2024_09_06_200156_create_user_dismissals_table.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
<?php | ||
|
||
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('user_dismissals', function (Blueprint $table) { | ||
$table->id(); | ||
$table->foreignId('user_id')->constrained()->onDelete('cascade'); | ||
$table->string('dismissable_type'); | ||
$table->string('dismissable_id'); | ||
$table->timestamp('dismissed_at'); | ||
$table->timestamps(); | ||
|
||
$table->unique(['user_id', 'dismissable_type', 'dismissable_id']); | ||
$table->index(['dismissable_type', 'dismissable_id']); | ||
}); | ||
} | ||
|
||
public function down(): void | ||
{ | ||
Schema::dropIfExists('user_dismissals'); | ||
} | ||
}; |
Oops, something went wrong.