From 25be1e6686a7cb630cf2594a321ff540b103801d Mon Sep 17 00:00:00 2001 From: korridor <26689068+korridor@users.noreply.github.com> Date: Sun, 21 Jan 2024 19:52:19 +0100 Subject: [PATCH] Renamed jetstream team to organization --- app/Actions/Fortify/CreateNewUser.php | 6 +- ...amMember.php => AddOrganizationMember.php} | 22 ++--- ...{CreateTeam.php => CreateOrganization.php} | 20 ++-- ...{DeleteTeam.php => DeleteOrganization.php} | 6 +- app/Actions/Jetstream/DeleteUser.php | 4 +- .../Jetstream/InviteOrganizationMember.php | 94 +++++++++++++++++++ app/Actions/Jetstream/InviteTeamMember.php | 90 ------------------ ...ember.php => RemoveOrganizationMember.php} | 22 ++--- ...ateTeamName.php => UpdateOrganization.php} | 15 ++- app/Models/Client.php | 8 +- app/Models/Membership.php | 16 +++- app/Models/{Team.php => Organization.php} | 8 +- app/Models/OrganizationInvitation.php | 52 ++++++++++ app/Models/Project.php | 6 +- app/Models/Tag.php | 6 +- app/Models/Task.php | 6 +- app/Models/TeamInvitation.php | 33 ------- app/Models/TimeEntry.php | 6 +- app/Models/User.php | 16 +++- ...{TeamPolicy.php => OrganizationPolicy.php} | 28 +++--- app/Providers/AppServiceProvider.php | 8 +- app/Providers/AuthServiceProvider.php | 5 +- app/Providers/JetstreamServiceProvider.php | 28 +++--- composer.json | 1 + composer.lock | 66 ++++++++++++- database/factories/ClientFactory.php | 6 +- ...eamFactory.php => OrganizationFactory.php} | 5 +- database/factories/ProjectFactory.php | 6 +- database/factories/TagFactory.php | 6 +- database/factories/TaskFactory.php | 6 +- database/factories/TimeEntryFactory.php | 6 +- database/factories/UserFactory.php | 15 +-- ..._21_100000_create_organizations_table.php} | 2 +- ...200000_create_organization_user_table.php} | 6 +- ...create_organization_invitations_table.php} | 6 +- ...2024_01_20_110218_create_clients_table.php | 2 +- ...024_01_20_110439_create_projects_table.php | 2 +- .../2024_01_20_110444_create_tasks_table.php | 2 +- .../2024_01_20_110452_create_tags_table.php | 2 +- ...01_20_110837_create_time_entries_table.php | 2 +- database/seeders/DatabaseSeeder.php | 19 +++- resources/js/Layouts/AppLayout.vue | 12 +-- .../Pages/Teams/Partials/DeleteTeamForm.vue | 2 +- .../Teams/Partials/TeamMemberManager.vue | 22 ++--- .../Teams/Partials/UpdateTeamNameForm.vue | 4 +- .../views/emails/team-invitation.blade.php | 2 +- tests/Feature/ApiTokenPermissionsTest.php | 2 +- tests/Feature/CreateApiTokenTest.php | 2 +- tests/Feature/CreateTeamTest.php | 6 +- tests/Feature/DeleteApiTokenTest.php | 2 +- tests/Feature/DeleteTeamTest.php | 8 +- tests/Feature/EmailVerificationTest.php | 2 +- tests/Feature/InviteTeamMemberTest.php | 21 ++--- tests/Feature/LeaveTeamTest.php | 4 +- tests/Feature/PasswordConfirmationTest.php | 2 +- tests/Feature/RemoveTeamMemberTest.php | 4 +- tests/Feature/UpdateTeamMemberRoleTest.php | 4 +- tests/Feature/UpdateTeamNameTest.php | 6 +- tests/Unit/Model/ClientModelTest.php | 4 +- tests/Unit/Model/ProjectModelTest.php | 4 +- tests/Unit/Model/TagModelTest.php | 4 +- tests/Unit/Model/TaskModelTest.php | 4 +- tests/Unit/Model/TimeEntryModelTest.php | 4 +- 63 files changed, 461 insertions(+), 329 deletions(-) rename app/Actions/Jetstream/{AddTeamMember.php => AddOrganizationMember.php} (69%) rename app/Actions/Jetstream/{CreateTeam.php => CreateOrganization.php} (59%) rename app/Actions/Jetstream/{DeleteTeam.php => DeleteOrganization.php} (60%) create mode 100644 app/Actions/Jetstream/InviteOrganizationMember.php delete mode 100644 app/Actions/Jetstream/InviteTeamMember.php rename app/Actions/Jetstream/{RemoveTeamMember.php => RemoveOrganizationMember.php} (57%) rename app/Actions/Jetstream/{UpdateTeamName.php => UpdateOrganization.php} (55%) rename app/Models/{Team.php => Organization.php} (85%) create mode 100644 app/Models/OrganizationInvitation.php delete mode 100644 app/Models/TeamInvitation.php rename app/Policies/{TeamPolicy.php => OrganizationPolicy.php} (55%) rename database/factories/{TeamFactory.php => OrganizationFactory.php} (80%) rename database/migrations/{2020_05_21_100000_create_teams_table.php => 2020_05_21_100000_create_organizations_table.php} (90%) rename database/migrations/{2020_05_21_200000_create_team_user_table.php => 2020_05_21_200000_create_organization_user_table.php} (76%) rename database/migrations/{2020_05_21_300000_create_team_invitations_table.php => 2020_05_21_300000_create_organization_invitations_table.php} (78%) diff --git a/app/Actions/Fortify/CreateNewUser.php b/app/Actions/Fortify/CreateNewUser.php index 86452d17..292463ef 100644 --- a/app/Actions/Fortify/CreateNewUser.php +++ b/app/Actions/Fortify/CreateNewUser.php @@ -4,7 +4,7 @@ namespace App\Actions\Fortify; -use App\Models\Team; +use App\Models\Organization; use App\Models\User; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Hash; @@ -46,9 +46,9 @@ public function create(array $input): User */ protected function createTeam(User $user): void { - $user->ownedTeams()->save(Team::forceCreate([ + $user->ownedTeams()->save(Organization::forceCreate([ 'user_id' => $user->id, - 'name' => explode(' ', $user->name, 2)[0]."'s Team", + 'name' => explode(' ', $user->name, 2)[0]."'s Organization", 'personal_team' => true, ])); } diff --git a/app/Actions/Jetstream/AddTeamMember.php b/app/Actions/Jetstream/AddOrganizationMember.php similarity index 69% rename from app/Actions/Jetstream/AddTeamMember.php rename to app/Actions/Jetstream/AddOrganizationMember.php index 56bb5ca8..acee7c3f 100644 --- a/app/Actions/Jetstream/AddTeamMember.php +++ b/app/Actions/Jetstream/AddOrganizationMember.php @@ -4,7 +4,7 @@ namespace App\Actions\Jetstream; -use App\Models\Team; +use App\Models\Organization; use App\Models\User; use Closure; use Illuminate\Support\Facades\Gate; @@ -15,32 +15,32 @@ use Laravel\Jetstream\Jetstream; use Laravel\Jetstream\Rules\Role; -class AddTeamMember implements AddsTeamMembers +class AddOrganizationMember implements AddsTeamMembers { /** * Add a new team member to the given team. */ - public function add(User $user, Team $team, string $email, ?string $role = null): void + public function add(User $user, Organization $organization, string $email, ?string $role = null): void { - Gate::forUser($user)->authorize('addTeamMember', $team); + Gate::forUser($user)->authorize('addTeamMember', $organization); - $this->validate($team, $email, $role); + $this->validate($organization, $email, $role); $newTeamMember = Jetstream::findUserByEmailOrFail($email); - AddingTeamMember::dispatch($team, $newTeamMember); + AddingTeamMember::dispatch($organization, $newTeamMember); - $team->users()->attach( + $organization->users()->attach( $newTeamMember, ['role' => $role] ); - TeamMemberAdded::dispatch($team, $newTeamMember); + TeamMemberAdded::dispatch($organization, $newTeamMember); } /** * Validate the add member operation. */ - protected function validate(Team $team, string $email, ?string $role): void + protected function validate(Organization $organization, string $email, ?string $role): void { Validator::make([ 'email' => $email, @@ -48,7 +48,7 @@ protected function validate(Team $team, string $email, ?string $role): void ], $this->rules(), [ 'email.exists' => __('We were unable to find a registered user with this email address.'), ])->after( - $this->ensureUserIsNotAlreadyOnTeam($team, $email) + $this->ensureUserIsNotAlreadyOnTeam($organization, $email) )->validateWithBag('addTeamMember'); } @@ -70,7 +70,7 @@ protected function rules(): array /** * Ensure that the user is not already on the team. */ - protected function ensureUserIsNotAlreadyOnTeam(Team $team, string $email): Closure + protected function ensureUserIsNotAlreadyOnTeam(Organization $team, string $email): Closure { return function ($validator) use ($team, $email) { $validator->errors()->addIf( diff --git a/app/Actions/Jetstream/CreateTeam.php b/app/Actions/Jetstream/CreateOrganization.php similarity index 59% rename from app/Actions/Jetstream/CreateTeam.php rename to app/Actions/Jetstream/CreateOrganization.php index 57d9fb52..99ea48ff 100644 --- a/app/Actions/Jetstream/CreateTeam.php +++ b/app/Actions/Jetstream/CreateOrganization.php @@ -4,22 +4,27 @@ namespace App\Actions\Jetstream; -use App\Models\Team; +use App\Models\Organization; use App\Models\User; +use Illuminate\Auth\Access\AuthorizationException; use Illuminate\Support\Facades\Gate; use Illuminate\Support\Facades\Validator; +use Illuminate\Validation\ValidationException; use Laravel\Jetstream\Contracts\CreatesTeams; use Laravel\Jetstream\Events\AddingTeam; use Laravel\Jetstream\Jetstream; -class CreateTeam implements CreatesTeams +class CreateOrganization implements CreatesTeams { /** * Validate and create a new team for the given user. * * @param array $input + * + * @throws AuthorizationException + * @throws ValidationException */ - public function create(User $user, array $input): Team + public function create(User $user, array $input): Organization { Gate::forUser($user)->authorize('create', Jetstream::newTeamModel()); @@ -29,11 +34,14 @@ public function create(User $user, array $input): Team AddingTeam::dispatch($user); - $user->switchTeam($team = $user->ownedTeams()->create([ + /** @var Organization $organization */ + $organization = $user->ownedTeams()->create([ 'name' => $input['name'], 'personal_team' => false, - ])); + ]); + + $user->switchTeam($organization); - return $team; + return $organization; } } diff --git a/app/Actions/Jetstream/DeleteTeam.php b/app/Actions/Jetstream/DeleteOrganization.php similarity index 60% rename from app/Actions/Jetstream/DeleteTeam.php rename to app/Actions/Jetstream/DeleteOrganization.php index dd206590..8682e49d 100644 --- a/app/Actions/Jetstream/DeleteTeam.php +++ b/app/Actions/Jetstream/DeleteOrganization.php @@ -4,15 +4,15 @@ namespace App\Actions\Jetstream; -use App\Models\Team; +use App\Models\Organization; use Laravel\Jetstream\Contracts\DeletesTeams; -class DeleteTeam implements DeletesTeams +class DeleteOrganization implements DeletesTeams { /** * Delete the given team. */ - public function delete(Team $team): void + public function delete(Organization $team): void { $team->purge(); } diff --git a/app/Actions/Jetstream/DeleteUser.php b/app/Actions/Jetstream/DeleteUser.php index ce38676e..0fcda600 100644 --- a/app/Actions/Jetstream/DeleteUser.php +++ b/app/Actions/Jetstream/DeleteUser.php @@ -4,7 +4,7 @@ namespace App\Actions\Jetstream; -use App\Models\Team; +use App\Models\Organization; use App\Models\User; use Illuminate\Support\Facades\DB; use Laravel\Jetstream\Contracts\DeletesTeams; @@ -47,7 +47,7 @@ protected function deleteTeams(User $user): void { $user->teams()->detach(); - $user->ownedTeams->each(function (Team $team) { + $user->ownedTeams->each(function (Organization $team) { $this->deletesTeams->delete($team); }); } diff --git a/app/Actions/Jetstream/InviteOrganizationMember.php b/app/Actions/Jetstream/InviteOrganizationMember.php new file mode 100644 index 00000000..e6aef0f4 --- /dev/null +++ b/app/Actions/Jetstream/InviteOrganizationMember.php @@ -0,0 +1,94 @@ +authorize('addTeamMember', $organization); + + $this->validate($organization, $email, $role); + + InvitingTeamMember::dispatch($organization, $email, $role); + + $invitation = $organization->teamInvitations()->create([ + 'email' => $email, + 'role' => $role, + ]); + + Mail::to($email)->send(new TeamInvitation($invitation)); + } + + /** + * Validate the invite member operation. + */ + protected function validate(Organization $organization, string $email, ?string $role): void + { + Validator::make([ + 'email' => $email, + 'role' => $role, + ], $this->rules($organization), [ + 'email.unique' => __('This user has already been invited to the team.'), + ])->after( + $this->ensureUserIsNotAlreadyOnTeam($organization, $email) + )->validateWithBag('addTeamMember'); + } + + /** + * Get the validation rules for inviting a team member. + * + * @return array + */ + protected function rules(Organization $organization): array + { + return array_filter([ + 'email' => [ + 'required', + 'email', + new UniqueEloquent(OrganizationInvitation::class, 'email', function (Builder $builder) use ($organization) { + /** @var Builder $builder */ + return $builder->whereBelongsTo($organization, 'organization'); + }), + ], + 'role' => Jetstream::hasRoles() + ? ['required', 'string', new Role] + : null, + ]); + } + + /** + * Ensure that the user is not already on the team. + */ + protected function ensureUserIsNotAlreadyOnTeam(Organization $organization, string $email): Closure + { + return function ($validator) use ($organization, $email) { + $validator->errors()->addIf( + $organization->hasUserWithEmail($email), + 'email', + __('This user already belongs to the team.') + ); + }; + } +} diff --git a/app/Actions/Jetstream/InviteTeamMember.php b/app/Actions/Jetstream/InviteTeamMember.php deleted file mode 100644 index 3c0dedcd..00000000 --- a/app/Actions/Jetstream/InviteTeamMember.php +++ /dev/null @@ -1,90 +0,0 @@ -authorize('addTeamMember', $team); - - $this->validate($team, $email, $role); - - InvitingTeamMember::dispatch($team, $email, $role); - - $invitation = $team->teamInvitations()->create([ - 'email' => $email, - 'role' => $role, - ]); - - Mail::to($email)->send(new TeamInvitation($invitation)); - } - - /** - * Validate the invite member operation. - */ - protected function validate(Team $team, string $email, ?string $role): void - { - Validator::make([ - 'email' => $email, - 'role' => $role, - ], $this->rules($team), [ - 'email.unique' => __('This user has already been invited to the team.'), - ])->after( - $this->ensureUserIsNotAlreadyOnTeam($team, $email) - )->validateWithBag('addTeamMember'); - } - - /** - * Get the validation rules for inviting a team member. - * - * @return array - */ - protected function rules(Team $team): array - { - return array_filter([ - 'email' => [ - 'required', 'email', - Rule::unique('team_invitations')->where(function (Builder $query) use ($team) { - $query->where('team_id', $team->id); - }), - ], - 'role' => Jetstream::hasRoles() - ? ['required', 'string', new Role] - : null, - ]); - } - - /** - * Ensure that the user is not already on the team. - */ - protected function ensureUserIsNotAlreadyOnTeam(Team $team, string $email): Closure - { - return function ($validator) use ($team, $email) { - $validator->errors()->addIf( - $team->hasUserWithEmail($email), - 'email', - __('This user already belongs to the team.') - ); - }; - } -} diff --git a/app/Actions/Jetstream/RemoveTeamMember.php b/app/Actions/Jetstream/RemoveOrganizationMember.php similarity index 57% rename from app/Actions/Jetstream/RemoveTeamMember.php rename to app/Actions/Jetstream/RemoveOrganizationMember.php index d9f06b9d..d03a094a 100644 --- a/app/Actions/Jetstream/RemoveTeamMember.php +++ b/app/Actions/Jetstream/RemoveOrganizationMember.php @@ -4,7 +4,7 @@ namespace App\Actions\Jetstream; -use App\Models\Team; +use App\Models\Organization; use App\Models\User; use Illuminate\Auth\Access\AuthorizationException; use Illuminate\Support\Facades\Gate; @@ -12,28 +12,28 @@ use Laravel\Jetstream\Contracts\RemovesTeamMembers; use Laravel\Jetstream\Events\TeamMemberRemoved; -class RemoveTeamMember implements RemovesTeamMembers +class RemoveOrganizationMember implements RemovesTeamMembers { /** * Remove the team member from the given team. */ - public function remove(User $user, Team $team, User $teamMember): void + public function remove(User $user, Organization $organization, User $teamMember): void { - $this->authorize($user, $team, $teamMember); + $this->authorize($user, $organization, $teamMember); - $this->ensureUserDoesNotOwnTeam($teamMember, $team); + $this->ensureUserDoesNotOwnTeam($teamMember, $organization); - $team->removeUser($teamMember); + $organization->removeUser($teamMember); - TeamMemberRemoved::dispatch($team, $teamMember); + TeamMemberRemoved::dispatch($organization, $teamMember); } /** * Authorize that the user can remove the team member. */ - protected function authorize(User $user, Team $team, User $teamMember): void + protected function authorize(User $user, Organization $organization, User $teamMember): void { - if (! Gate::forUser($user)->check('removeTeamMember', $team) && + if (! Gate::forUser($user)->check('removeTeamMember', $organization) && $user->id !== $teamMember->id) { throw new AuthorizationException; } @@ -42,9 +42,9 @@ protected function authorize(User $user, Team $team, User $teamMember): void /** * Ensure that the currently authenticated user does not own the team. */ - protected function ensureUserDoesNotOwnTeam(User $teamMember, Team $team): void + protected function ensureUserDoesNotOwnTeam(User $teamMember, Organization $organization): void { - if ($teamMember->id === $team->owner->id) { + if ($teamMember->id === $organization->owner->id) { throw ValidationException::withMessages([ 'team' => [__('You may not leave a team that you created.')], ])->errorBag('removeTeamMember'); diff --git a/app/Actions/Jetstream/UpdateTeamName.php b/app/Actions/Jetstream/UpdateOrganization.php similarity index 55% rename from app/Actions/Jetstream/UpdateTeamName.php rename to app/Actions/Jetstream/UpdateOrganization.php index 3de075b9..ff0e10da 100644 --- a/app/Actions/Jetstream/UpdateTeamName.php +++ b/app/Actions/Jetstream/UpdateOrganization.php @@ -4,28 +4,33 @@ namespace App\Actions\Jetstream; -use App\Models\Team; +use App\Models\Organization; use App\Models\User; +use Illuminate\Auth\Access\AuthorizationException; use Illuminate\Support\Facades\Gate; use Illuminate\Support\Facades\Validator; +use Illuminate\Validation\ValidationException; use Laravel\Jetstream\Contracts\UpdatesTeamNames; -class UpdateTeamName implements UpdatesTeamNames +class UpdateOrganization implements UpdatesTeamNames { /** * Validate and update the given team's name. * * @param array $input + * + * @throws AuthorizationException + * @throws ValidationException */ - public function update(User $user, Team $team, array $input): void + public function update(User $user, Organization $organization, array $input): void { - Gate::forUser($user)->authorize('update', $team); + Gate::forUser($user)->authorize('update', $organization); Validator::make($input, [ 'name' => ['required', 'string', 'max:255'], ])->validateWithBag('updateTeamName'); - $team->forceFill([ + $organization->forceFill([ 'name' => $input['name'], ])->save(); } diff --git a/app/Models/Client.php b/app/Models/Client.php index 511987e1..e2046218 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -14,7 +14,9 @@ * @property string $id * @property string $name * @property string $organization_id - * @property-read Team $organization + * @property string $created_at + * @property string $updated_at + * @property-read Organization $organization * * @method static ClientFactory factory() */ @@ -33,10 +35,10 @@ class Client extends Model ]; /** - * @return BelongsTo + * @return BelongsTo */ public function organization(): BelongsTo { - return $this->belongsTo(Team::class, 'organization_id'); + return $this->belongsTo(Organization::class, 'organization_id'); } } diff --git a/app/Models/Membership.php b/app/Models/Membership.php index f2ccc525..fa7c627d 100644 --- a/app/Models/Membership.php +++ b/app/Models/Membership.php @@ -7,14 +7,24 @@ use Illuminate\Database\Eloquent\Concerns\HasUuids; use Laravel\Jetstream\Membership as JetstreamMembership; +/** + * @property string $id + * @property string $role + * @property string $organization_id + * @property string $user_id + * @property string $created_at + * @property string $updated_at + * @property-read Organization $organization + * @property-read User $user + */ class Membership extends JetstreamMembership { use HasUuids; /** - * Indicates if the IDs are auto-incrementing. + * The table associated with the pivot model. * - * @var bool + * @var string */ - public $incrementing = true; + protected $table = 'organization_user'; } diff --git a/app/Models/Team.php b/app/Models/Organization.php similarity index 85% rename from app/Models/Team.php rename to app/Models/Organization.php index f546bab8..24146da3 100644 --- a/app/Models/Team.php +++ b/app/Models/Organization.php @@ -4,7 +4,7 @@ namespace App\Models; -use Database\Factories\TeamFactory; +use Database\Factories\OrganizationFactory; use Illuminate\Database\Eloquent\Concerns\HasUuids; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\HasMany; @@ -17,10 +17,10 @@ * @property string $id * @property User $owner * - * @method HasMany teamInvitations() - * @method static TeamFactory factory() + * @method HasMany teamInvitations() + * @method static OrganizationFactory factory() */ -class Team extends JetstreamTeam +class Organization extends JetstreamTeam { use HasFactory; use HasUuids; diff --git a/app/Models/OrganizationInvitation.php b/app/Models/OrganizationInvitation.php new file mode 100644 index 00000000..b47aea10 --- /dev/null +++ b/app/Models/OrganizationInvitation.php @@ -0,0 +1,52 @@ + + */ + protected $fillable = [ + 'email', + 'role', + ]; + + /** + * Get the organization that the invitation belongs to. + */ + public function organization(): BelongsTo + { + return $this->belongsTo(Jetstream::teamModel(), 'organization_id'); + } + + /** + * Get the organization that the invitation belongs to. + */ + public function team(): BelongsTo + { + return $this->belongsTo(Jetstream::teamModel(), 'organization_id'); + } +} diff --git a/app/Models/Project.php b/app/Models/Project.php index 601c6798..4b6cee3e 100644 --- a/app/Models/Project.php +++ b/app/Models/Project.php @@ -17,7 +17,7 @@ * @property string $name * @property string $organization_id * @property string $client_id - * @property-read Team $organization + * @property-read Organization $organization * @property-read Client|null $client * @property-read Collection $tasks * @@ -38,11 +38,11 @@ class Project extends Model ]; /** - * @return BelongsTo + * @return BelongsTo */ public function organization(): BelongsTo { - return $this->belongsTo(Team::class, 'organization_id'); + return $this->belongsTo(Organization::class, 'organization_id'); } /** diff --git a/app/Models/Tag.php b/app/Models/Tag.php index 2a2d3057..9934f22d 100644 --- a/app/Models/Tag.php +++ b/app/Models/Tag.php @@ -14,7 +14,7 @@ * @property string $id * @property string $name * @property string $organization_id - * @property-read Team $organization + * @property-read Organization $organization * * @method static TagFactory factory() */ @@ -33,10 +33,10 @@ class Tag extends Model ]; /** - * @return BelongsTo + * @return BelongsTo */ public function organization(): BelongsTo { - return $this->belongsTo(Team::class, 'organization_id'); + return $this->belongsTo(Organization::class, 'organization_id'); } } diff --git a/app/Models/Task.php b/app/Models/Task.php index 4e0b0495..3909ed09 100644 --- a/app/Models/Task.php +++ b/app/Models/Task.php @@ -16,7 +16,7 @@ * @property string $project_id * @property string $organization_id * @property-read Project $project - * @property-read Team $organization + * @property-read Organization $organization * * @method static TaskFactory factory() */ @@ -43,10 +43,10 @@ public function project(): BelongsTo } /** - * @return BelongsTo + * @return BelongsTo */ public function organization(): BelongsTo { - return $this->belongsTo(Team::class, 'organization_id'); + return $this->belongsTo(Organization::class, 'organization_id'); } } diff --git a/app/Models/TeamInvitation.php b/app/Models/TeamInvitation.php deleted file mode 100644 index b21f1882..00000000 --- a/app/Models/TeamInvitation.php +++ /dev/null @@ -1,33 +0,0 @@ - - */ - protected $fillable = [ - 'email', - 'role', - ]; - - /** - * Get the team that the invitation belongs to. - */ - public function team(): BelongsTo - { - return $this->belongsTo(Jetstream::teamModel()); - } -} diff --git a/app/Models/TimeEntry.php b/app/Models/TimeEntry.php index e46e1351..ae9344b4 100644 --- a/app/Models/TimeEntry.php +++ b/app/Models/TimeEntry.php @@ -19,7 +19,7 @@ * @property bool $billable * @property array $tags * @property-read User $user - * @property-read Team $organization + * @property-read Organization $organization * @property-read Project|null $project * @property-read Task|null $task * @@ -52,11 +52,11 @@ public function user(): BelongsTo } /** - * @return BelongsTo + * @return BelongsTo */ public function organization(): BelongsTo { - return $this->belongsTo(Team::class, 'organization_id'); + return $this->belongsTo(Organization::class, 'organization_id'); } /** diff --git a/app/Models/User.php b/app/Models/User.php index 551bf126..b510c01c 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -8,6 +8,7 @@ use Filament\Panel; use Illuminate\Database\Eloquent\Concerns\HasUuids; use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; @@ -20,7 +21,7 @@ * @property string $id * @property string $name * - * @method HasMany ownedTeams() + * @method HasMany ownedTeams() * @method static UserFactory factory() */ class User extends Authenticatable @@ -79,4 +80,17 @@ public function canAccessPanel(Panel $panel): bool // TODO: Implement canAccessPanel() method. return false; } + + /** + * @return BelongsToMany + */ + public function organizations(): BelongsToMany + { + return $this->belongsToMany(Organization::class, Membership::class) + ->withPivot([ + 'role', + ]) + ->withTimestamps() + ->as('membership'); + } } diff --git a/app/Policies/TeamPolicy.php b/app/Policies/OrganizationPolicy.php similarity index 55% rename from app/Policies/TeamPolicy.php rename to app/Policies/OrganizationPolicy.php index 824404f9..32fbb092 100644 --- a/app/Policies/TeamPolicy.php +++ b/app/Policies/OrganizationPolicy.php @@ -4,11 +4,11 @@ namespace App\Policies; -use App\Models\Team; +use App\Models\Organization; use App\Models\User; use Illuminate\Auth\Access\HandlesAuthorization; -class TeamPolicy +class OrganizationPolicy { use HandlesAuthorization; @@ -23,9 +23,9 @@ public function viewAny(User $user): bool /** * Determine whether the user can view the model. */ - public function view(User $user, Team $team): bool + public function view(User $user, Organization $organization): bool { - return $user->belongsToTeam($team); + return $user->belongsToTeam($organization); } /** @@ -39,40 +39,40 @@ public function create(User $user): bool /** * Determine whether the user can update the model. */ - public function update(User $user, Team $team): bool + public function update(User $user, Organization $organization): bool { - return $user->ownsTeam($team); + return $user->ownsTeam($organization); } /** * Determine whether the user can add team members. */ - public function addTeamMember(User $user, Team $team): bool + public function addTeamMember(User $user, Organization $organization): bool { - return $user->ownsTeam($team); + return $user->ownsTeam($organization); } /** * Determine whether the user can update team member permissions. */ - public function updateTeamMember(User $user, Team $team): bool + public function updateTeamMember(User $user, Organization $organization): bool { - return $user->ownsTeam($team); + return $user->ownsTeam($organization); } /** * Determine whether the user can remove team members. */ - public function removeTeamMember(User $user, Team $team): bool + public function removeTeamMember(User $user, Organization $organization): bool { - return $user->ownsTeam($team); + return $user->ownsTeam($organization); } /** * Determine whether the user can delete the model. */ - public function delete(User $user, Team $team): bool + public function delete(User $user, Organization $organization): bool { - return $user->ownsTeam($team); + return $user->ownsTeam($organization); } } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 2faa5ae5..f1cc0727 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -5,8 +5,8 @@ namespace App\Providers; use App\Models\Membership; -use App\Models\Team; -use App\Models\TeamInvitation; +use App\Models\Organization; +use App\Models\OrganizationInvitation; use App\Models\User; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\Relation; @@ -31,8 +31,8 @@ public function boot(): void Model::preventSilentlyDiscardingAttributes(! $this->app->isProduction()); Relation::enforceMorphMap([ 'membership' => Membership::class, - 'team' => Team::class, - 'team_invitation' => TeamInvitation::class, + 'team' => Organization::class, + 'team_invitation' => OrganizationInvitation::class, 'user' => User::class, ]); } diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index 63ec34ea..e7a414c9 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -4,7 +4,8 @@ namespace App\Providers; -// use Illuminate\Support\Facades\Gate; +use App\Models\Organization; +use App\Policies\OrganizationPolicy; use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider; use Laravel\Jetstream\Jetstream; use Laravel\Passport\Passport; @@ -17,7 +18,7 @@ class AuthServiceProvider extends ServiceProvider * @var array */ protected $policies = [ - // + Organization::class => OrganizationPolicy::class, ]; /** diff --git a/app/Providers/JetstreamServiceProvider.php b/app/Providers/JetstreamServiceProvider.php index 99de7aa0..0f4742dc 100644 --- a/app/Providers/JetstreamServiceProvider.php +++ b/app/Providers/JetstreamServiceProvider.php @@ -4,13 +4,15 @@ namespace App\Providers; -use App\Actions\Jetstream\AddTeamMember; -use App\Actions\Jetstream\CreateTeam; -use App\Actions\Jetstream\DeleteTeam; +use App\Actions\Jetstream\AddOrganizationMember; +use App\Actions\Jetstream\CreateOrganization; +use App\Actions\Jetstream\DeleteOrganization; use App\Actions\Jetstream\DeleteUser; -use App\Actions\Jetstream\InviteTeamMember; -use App\Actions\Jetstream\RemoveTeamMember; -use App\Actions\Jetstream\UpdateTeamName; +use App\Actions\Jetstream\InviteOrganizationMember; +use App\Actions\Jetstream\RemoveOrganizationMember; +use App\Actions\Jetstream\UpdateOrganization; +use App\Models\Organization; +use App\Models\OrganizationInvitation; use Illuminate\Support\ServiceProvider; use Laravel\Jetstream\Jetstream; @@ -31,13 +33,15 @@ public function boot(): void { $this->configurePermissions(); - Jetstream::createTeamsUsing(CreateTeam::class); - Jetstream::updateTeamNamesUsing(UpdateTeamName::class); - Jetstream::addTeamMembersUsing(AddTeamMember::class); - Jetstream::inviteTeamMembersUsing(InviteTeamMember::class); - Jetstream::removeTeamMembersUsing(RemoveTeamMember::class); - Jetstream::deleteTeamsUsing(DeleteTeam::class); + Jetstream::createTeamsUsing(CreateOrganization::class); + Jetstream::updateTeamNamesUsing(UpdateOrganization::class); + Jetstream::addTeamMembersUsing(AddOrganizationMember::class); + Jetstream::inviteTeamMembersUsing(InviteOrganizationMember::class); + Jetstream::removeTeamMembersUsing(RemoveOrganizationMember::class); + Jetstream::deleteTeamsUsing(DeleteOrganization::class); Jetstream::deleteUsersUsing(DeleteUser::class); + Jetstream::useTeamModel(Organization::class); + Jetstream::useTeamInvitationModel(OrganizationInvitation::class); } /** diff --git a/composer.json b/composer.json index 996c581d..e708c66d 100644 --- a/composer.json +++ b/composer.json @@ -9,6 +9,7 @@ "filament/filament": "^3.2", "guzzlehttp/guzzle": "^7.2", "inertiajs/inertia-laravel": "^0.6.8", + "korridor/laravel-model-validation-rules": "^3.0", "laravel/framework": "^10.10", "laravel/jetstream": "^4.2", "laravel/passport": "*", diff --git a/composer.lock b/composer.lock index faab6e03..46003e72 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "34dbd2208afb93ce559614a5c014397e", + "content-hash": "68ea9179372fd4db583f50784a1ca6f7", "packages": [ { "name": "anourvalar/eloquent-serialize", @@ -2509,6 +2509,70 @@ }, "time": "2023-12-07T10:44:41+00:00" }, + { + "name": "korridor/laravel-model-validation-rules", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/korridor/laravel-model-validation-rules.git", + "reference": "23537e5bd296a042bbe0c3a1c6d556c89dfbad42" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/korridor/laravel-model-validation-rules/zipball/23537e5bd296a042bbe0c3a1c6d556c89dfbad42", + "reference": "23537e5bd296a042bbe0c3a1c6d556c89dfbad42", + "shasum": "" + }, + "require": { + "illuminate/database": "^10", + "illuminate/support": "^10", + "php": ">=8.1" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.6", + "orchestra/testbench": "^8.0", + "phpunit/phpunit": "^10", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Korridor\\LaravelModelValidationRules\\ModelValidationServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Korridor\\LaravelModelValidationRules\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "korridor", + "email": "26689068+korridor@users.noreply.github.com" + } + ], + "description": "A laravel validation rule that uses eloquent to validate if a model exists", + "homepage": "https://github.com/korridor/laravel-model-validation-rules", + "keywords": [ + "eloquent", + "exist", + "laravel", + "model", + "rule", + "validation" + ], + "support": { + "issues": "https://github.com/korridor/laravel-model-validation-rules/issues", + "source": "https://github.com/korridor/laravel-model-validation-rules/tree/3.0.0" + }, + "time": "2023-02-16T11:15:08+00:00" + }, { "name": "laravel/fortify", "version": "v1.20.0", diff --git a/database/factories/ClientFactory.php b/database/factories/ClientFactory.php index e80d854c..814cfdc8 100644 --- a/database/factories/ClientFactory.php +++ b/database/factories/ClientFactory.php @@ -5,7 +5,7 @@ namespace Database\Factories; use App\Models\Client; -use App\Models\Team; +use App\Models\Organization; use Illuminate\Database\Eloquent\Factories\Factory; /** @@ -22,11 +22,11 @@ public function definition(): array { return [ 'name' => $this->faker->company(), - 'organization_id' => Team::factory(), + 'organization_id' => Organization::factory(), ]; } - public function forOrganization(Team $organization): self + public function forOrganization(Organization $organization): self { return $this->state(function (array $attributes) use ($organization) { return [ diff --git a/database/factories/TeamFactory.php b/database/factories/OrganizationFactory.php similarity index 80% rename from database/factories/TeamFactory.php rename to database/factories/OrganizationFactory.php index c2cc4e0e..1f305b6f 100644 --- a/database/factories/TeamFactory.php +++ b/database/factories/OrganizationFactory.php @@ -4,13 +4,14 @@ namespace Database\Factories; +use App\Models\Organization; use App\Models\User; use Illuminate\Database\Eloquent\Factories\Factory; /** - * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Team> + * @extends Factory */ -class TeamFactory extends Factory +class OrganizationFactory extends Factory { /** * Define the model's default state. diff --git a/database/factories/ProjectFactory.php b/database/factories/ProjectFactory.php index 7b076600..709a2320 100644 --- a/database/factories/ProjectFactory.php +++ b/database/factories/ProjectFactory.php @@ -5,8 +5,8 @@ namespace Database\Factories; use App\Models\Client; +use App\Models\Organization; use App\Models\Project; -use App\Models\Team; use Illuminate\Database\Eloquent\Factories\Factory; /** @@ -24,12 +24,12 @@ public function definition(): array return [ 'name' => $this->faker->company(), 'color' => $this->faker->hexColor(), - 'organization_id' => Team::factory(), + 'organization_id' => Organization::factory(), 'client_id' => null, ]; } - public function forOrganization(Team $organization): self + public function forOrganization(Organization $organization): self { return $this->state(function (array $attributes) use ($organization) { return [ diff --git a/database/factories/TagFactory.php b/database/factories/TagFactory.php index 6ca77687..4d00a041 100644 --- a/database/factories/TagFactory.php +++ b/database/factories/TagFactory.php @@ -4,8 +4,8 @@ namespace Database\Factories; +use App\Models\Organization; use App\Models\Tag; -use App\Models\Team; use Illuminate\Database\Eloquent\Factories\Factory; /** @@ -22,11 +22,11 @@ public function definition(): array { return [ 'name' => $this->faker->name, - 'organization_id' => Team::factory(), + 'organization_id' => Organization::factory(), ]; } - public function forOrganization(Team $organization): self + public function forOrganization(Organization $organization): self { return $this->state(function (array $attributes) use ($organization) { return [ diff --git a/database/factories/TaskFactory.php b/database/factories/TaskFactory.php index d5fefbff..0921e9c4 100644 --- a/database/factories/TaskFactory.php +++ b/database/factories/TaskFactory.php @@ -4,9 +4,9 @@ namespace Database\Factories; +use App\Models\Organization; use App\Models\Project; use App\Models\Task; -use App\Models\Team; use Illuminate\Database\Eloquent\Factories\Factory; /** @@ -24,7 +24,7 @@ public function definition(): array return [ 'name' => $this->faker->word(), 'project_id' => Project::factory(), - 'organization_id' => Team::factory(), + 'organization_id' => Organization::factory(), ]; } @@ -37,7 +37,7 @@ public function forProject(Project $project): self }); } - public function forOrganization(Team $organization): self + public function forOrganization(Organization $organization): self { return $this->state(function (array $attributes) use ($organization) { return [ diff --git a/database/factories/TimeEntryFactory.php b/database/factories/TimeEntryFactory.php index c61e352d..1fdad027 100644 --- a/database/factories/TimeEntryFactory.php +++ b/database/factories/TimeEntryFactory.php @@ -4,9 +4,9 @@ namespace Database\Factories; +use App\Models\Organization; use App\Models\Project; use App\Models\Task; -use App\Models\Team; use App\Models\TimeEntry; use App\Models\User; use Illuminate\Database\Eloquent\Factories\Factory; @@ -32,7 +32,7 @@ public function definition(): array 'billable' => $this->faker->boolean(), 'tags' => [], 'user_id' => User::factory(), - 'organization_id' => Team::factory(), + 'organization_id' => Organization::factory(), ]; } @@ -45,7 +45,7 @@ public function forUser(User $user): self }); } - public function forOrganization(Team $organization): self + public function forOrganization(Organization $organization): self { return $this->state(function (array $attributes) use ($organization) { return [ diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php index 94854008..60f31ffb 100644 --- a/database/factories/UserFactory.php +++ b/database/factories/UserFactory.php @@ -4,14 +4,13 @@ namespace Database\Factories; -use App\Models\Team; +use App\Models\Organization; use App\Models\User; use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Str; -use Laravel\Jetstream\Features; /** - * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User> + * @extends Factory */ class UserFactory extends Factory { @@ -50,16 +49,12 @@ public function unverified(): static /** * Indicate that the user should have a personal team. */ - public function withPersonalTeam(?callable $callback = null): static + public function withPersonalOrganization(?callable $callback = null): static { - if (! Features::hasTeamFeatures()) { - return $this->state([]); - } - return $this->has( - Team::factory() + Organization::factory() ->state(fn (array $attributes, User $user) => [ - 'name' => $user->name.'\'s Team', + 'name' => $user->name.'\'s Organization', 'user_id' => $user->id, 'personal_team' => true, ]) diff --git a/database/migrations/2020_05_21_100000_create_teams_table.php b/database/migrations/2020_05_21_100000_create_organizations_table.php similarity index 90% rename from database/migrations/2020_05_21_100000_create_teams_table.php rename to database/migrations/2020_05_21_100000_create_organizations_table.php index 44a6ce42..f2a41ebb 100644 --- a/database/migrations/2020_05_21_100000_create_teams_table.php +++ b/database/migrations/2020_05_21_100000_create_organizations_table.php @@ -13,7 +13,7 @@ */ public function up(): void { - Schema::create('teams', function (Blueprint $table) { + Schema::create('organizations', function (Blueprint $table) { $table->uuid('id')->primary(); $table->foreignUuid('user_id')->index(); $table->string('name'); diff --git a/database/migrations/2020_05_21_200000_create_team_user_table.php b/database/migrations/2020_05_21_200000_create_organization_user_table.php similarity index 76% rename from database/migrations/2020_05_21_200000_create_team_user_table.php rename to database/migrations/2020_05_21_200000_create_organization_user_table.php index 7e223c5d..1097ffaa 100644 --- a/database/migrations/2020_05_21_200000_create_team_user_table.php +++ b/database/migrations/2020_05_21_200000_create_organization_user_table.php @@ -13,14 +13,14 @@ */ public function up(): void { - Schema::create('team_user', function (Blueprint $table) { + Schema::create('organization_user', function (Blueprint $table) { $table->uuid('id')->primary(); - $table->foreignUuid('team_id'); + $table->foreignUuid('organization_id'); $table->foreignUuid('user_id'); $table->string('role')->nullable(); $table->timestamps(); - $table->unique(['team_id', 'user_id']); + $table->unique(['organization_id', 'user_id']); }); } diff --git a/database/migrations/2020_05_21_300000_create_team_invitations_table.php b/database/migrations/2020_05_21_300000_create_organization_invitations_table.php similarity index 78% rename from database/migrations/2020_05_21_300000_create_team_invitations_table.php rename to database/migrations/2020_05_21_300000_create_organization_invitations_table.php index 12d4ceba..5c8c49c3 100644 --- a/database/migrations/2020_05_21_300000_create_team_invitations_table.php +++ b/database/migrations/2020_05_21_300000_create_organization_invitations_table.php @@ -13,16 +13,16 @@ */ public function up(): void { - Schema::create('team_invitations', function (Blueprint $table) { + Schema::create('organization_invitations', function (Blueprint $table) { $table->uuid('id')->primary(); - $table->foreignUuid('team_id') + $table->foreignUuid('organization_id') ->constrained() ->cascadeOnDelete(); $table->string('email'); $table->string('role')->nullable(); $table->timestamps(); - $table->unique(['team_id', 'email']); + $table->unique(['organization_id', 'email']); }); } diff --git a/database/migrations/2024_01_20_110218_create_clients_table.php b/database/migrations/2024_01_20_110218_create_clients_table.php index ed762797..463c7e5d 100644 --- a/database/migrations/2024_01_20_110218_create_clients_table.php +++ b/database/migrations/2024_01_20_110218_create_clients_table.php @@ -19,7 +19,7 @@ public function up(): void $table->uuid('organization_id'); $table->foreign('organization_id') ->references('id') - ->on('teams') + ->on('organizations') ->cascadeOnUpdate() ->restrictOnDelete(); $table->timestamps(); diff --git a/database/migrations/2024_01_20_110439_create_projects_table.php b/database/migrations/2024_01_20_110439_create_projects_table.php index f1a8aff9..8f5b32b4 100644 --- a/database/migrations/2024_01_20_110439_create_projects_table.php +++ b/database/migrations/2024_01_20_110439_create_projects_table.php @@ -26,7 +26,7 @@ public function up(): void $table->uuid('organization_id'); $table->foreign('organization_id') ->references('id') - ->on('teams') + ->on('organizations') ->cascadeOnUpdate() ->restrictOnDelete(); $table->timestamps(); diff --git a/database/migrations/2024_01_20_110444_create_tasks_table.php b/database/migrations/2024_01_20_110444_create_tasks_table.php index cda838cd..876508f2 100644 --- a/database/migrations/2024_01_20_110444_create_tasks_table.php +++ b/database/migrations/2024_01_20_110444_create_tasks_table.php @@ -25,7 +25,7 @@ public function up(): void $table->uuid('organization_id'); $table->foreign('organization_id') ->references('id') - ->on('teams') + ->on('organizations') ->cascadeOnUpdate() ->restrictOnDelete(); $table->timestamps(); diff --git a/database/migrations/2024_01_20_110452_create_tags_table.php b/database/migrations/2024_01_20_110452_create_tags_table.php index c0dde2b5..a1edb629 100644 --- a/database/migrations/2024_01_20_110452_create_tags_table.php +++ b/database/migrations/2024_01_20_110452_create_tags_table.php @@ -19,7 +19,7 @@ public function up(): void $table->uuid('organization_id'); $table->foreign('organization_id') ->references('id') - ->on('teams') + ->on('organizations') ->cascadeOnUpdate() ->restrictOnDelete(); $table->timestamps(); diff --git a/database/migrations/2024_01_20_110837_create_time_entries_table.php b/database/migrations/2024_01_20_110837_create_time_entries_table.php index ecaa5c54..91c5dcda 100644 --- a/database/migrations/2024_01_20_110837_create_time_entries_table.php +++ b/database/migrations/2024_01_20_110837_create_time_entries_table.php @@ -28,7 +28,7 @@ public function up(): void $table->uuid('organization_id'); $table->foreign('organization_id') ->references('id') - ->on('teams') + ->on('organizations') ->cascadeOnUpdate() ->restrictOnDelete(); $table->uuid('project_id')->nullable(); diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index e9b52ac6..eabb5750 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -6,9 +6,9 @@ // use Illuminate\Database\Console\Seeds\WithoutModelEvents; use App\Models\Client; +use App\Models\Organization; use App\Models\Project; use App\Models\Task; -use App\Models\Team; use App\Models\TimeEntry; use App\Models\User; use Illuminate\Database\Seeder; @@ -22,14 +22,23 @@ class DatabaseSeeder extends Seeder public function run(): void { $this->deleteAll(); - $organization = Team::factory()->create([ + $organization = Organization::factory()->create([ 'name' => 'ACME Corp', ]); - $user1 = User::factory()->withPersonalTeam()->create([ + $user1 = User::factory()->withPersonalOrganization()->create([ 'name' => 'Test User', 'email' => 'test@example.com', ]); - $user1->teams()->attach($organization); + $userAcmeAdmin = User::factory()->create([ + 'name' => 'ACME Admin', + 'email' => 'admin@acme.test', + ]); + $user1->organizations()->attach($organization, [ + 'role' => 'editor', + ]); + $userAcmeAdmin->organizations()->attach($organization, [ + 'role' => 'admin', + ]); $client = Client::factory()->create([ 'name' => 'Big Company', ]); @@ -50,6 +59,6 @@ private function deleteAll(): void DB::table((new Project())->getTable())->delete(); DB::table((new Client())->getTable())->delete(); DB::table((new User())->getTable())->delete(); - DB::table((new Team())->getTable())->delete(); + DB::table((new Organization())->getTable())->delete(); } } diff --git a/resources/js/Layouts/AppLayout.vue b/resources/js/Layouts/AppLayout.vue index 654a6f79..6b0d2c8f 100644 --- a/resources/js/Layouts/AppLayout.vue +++ b/resources/js/Layouts/AppLayout.vue @@ -72,12 +72,12 @@ const logout = () => {