diff --git a/app/Http/Controllers/TeamController.php b/app/Http/Controllers/TeamController.php index 17c2e6c..3a88786 100644 --- a/app/Http/Controllers/TeamController.php +++ b/app/Http/Controllers/TeamController.php @@ -3,9 +3,9 @@ namespace App\Http\Controllers; use App\Enums\TeamMemberRole; -use App\Http\Requests\AddTeamMemberRequest; use App\Http\Requests\StoreTeamRequest; use App\Http\Requests\UpdateTeamRequest; +use App\Http\Resources\TeamInviteResource; use App\Http\Resources\TeamResource; use App\Models\Team; use Illuminate\Http\Request; @@ -28,10 +28,14 @@ class TeamController extends Controller public function show(Request $request, Team $team) { + $teamManager = auth()->user()->can('manage', $team); + return Inertia::render('team/Show', [ 'team' => TeamResource::make($team)->resolve($request), - 'canManageTeam' => auth()->user()->can('manage', $team), - ]); + 'canManageTeam' => $teamManager, + ] + ($teamManager ? [ + 'teamInvites' => TeamInviteResource::collection($team->invites)->resolve($request), + ] : [])); } /** @@ -75,18 +79,6 @@ class TeamController extends Controller ]); } - /** - * Show the form for editing the specified resource. - */ - public function addMember(AddTeamMemberRequest $request, Team $team) - { - $validated = $request->validated(); - - $email = Arr::get($validated, 'email'); - - return response()->json(['message' => __('')]); - } - /** * Update the specified resource in storage. */ @@ -104,15 +96,8 @@ class TeamController extends Controller Inertia::flash('error', 'Team could not be updated.'); DB::rollBack(); } - return to_route('teams.show', $team); - } - /** - * Remove the specified resource from storage. - */ - public function destroy(Team $team) - { - // + return to_route('teams.show', $team); } public function nameToSlug(Request $request) @@ -132,4 +117,42 @@ class TeamController extends Controller 'taken' => $slug !== $ignoredSlug && Team::whereSlug($slug)->exists(), ]); } + + public function invite(Request $request, Team $team) + { + $validated = $request->validate([ + 'email' => 'required|email|max:255', + ]); + + abort_unless(auth()->user()->can('manage', $team), 403); + + $team->invites()->create([ + 'invited_email' => Arr::get($validated, 'email'), + 'invited_by_user_id' => auth()->user()->id, + ]); + + Inertia::flash('success', 'User invited to your team.'); + } + + public function revokeInvite(Request $request, Team $team) + { + $validated = $request->validate([ + 'email' => 'required|email|max:255|exists:team_invites,invited_email', + ]); + + abort_unless(auth()->user()->can('manage', $team), 403); + + $teamInvite = $team->invites()->whereInvitedEmail(Arr::get($validated, 'email'))->firstOrFail(); + + try { + DB::transaction(function () use ($teamInvite) { + $teamInvite->delete(); + }); + Inertia::flash('success', 'successfully deleted user invite.'); + } catch (\Throwable $e) { + Inertia::flash('error', 'could not delete user invite.'); + DB::rollBack(); + } + + } } diff --git a/app/Http/Requests/AddTeamMemberRequest.php b/app/Http/Requests/AddTeamMemberRequest.php deleted file mode 100644 index a57f977..0000000 --- a/app/Http/Requests/AddTeamMemberRequest.php +++ /dev/null @@ -1,21 +0,0 @@ -|string> - */ - public function rules(): array - { - return [ - 'email' => 'required|string|email|max:255', - ]; - } -} diff --git a/app/Http/Resources/TeamInviteResource.php b/app/Http/Resources/TeamInviteResource.php new file mode 100644 index 0000000..e0b18ff --- /dev/null +++ b/app/Http/Resources/TeamInviteResource.php @@ -0,0 +1,28 @@ + + */ + public function toArray(Request $request): array + { + return [ + 'invitedEmail' => $this->invited_email, + 'team' => TeamResource::make($this->team)->resolve($request), + 'invitedBy' => UserResource::make($this->invitedBy)->resolve($request), + 'invitedAt' => $this->created_at->toDateTimeString(), + ]; + } +} diff --git a/app/Http/Resources/TeamMemberResource.php b/app/Http/Resources/TeamMemberResource.php index c960e9e..596bc87 100644 --- a/app/Http/Resources/TeamMemberResource.php +++ b/app/Http/Resources/TeamMemberResource.php @@ -4,19 +4,15 @@ namespace App\Http\Resources; use App\Models\User; use Illuminate\Http\Request; -use Illuminate\Http\Resources\Json\JsonResource; /** * @mixin User */ -class TeamMemberResource extends JsonResource +class TeamMemberResource extends UserResource { public function toArray(Request $request): array { - return [ - 'name' => $this->name, - 'email' => $this->email, - 'has_multi_factor' => $this->hasVerifiedEmail() || $this->hasEnabledTwoFactorAuthentication(), + return parent::toArray($request) + [ 'role' => $this->pivot->role, 'joined_at' => $this->pivot->created_at->toDateTimeString(), ]; diff --git a/app/Http/Resources/UserResource.php b/app/Http/Resources/UserResource.php new file mode 100644 index 0000000..2f40320 --- /dev/null +++ b/app/Http/Resources/UserResource.php @@ -0,0 +1,27 @@ + + */ + public function toArray(Request $request): array + { + return [ + 'name' => $this->name, + 'email' => $this->email, + 'has_multi_factor' => $this->hasVerifiedEmail() || $this->hasEnabledTwoFactorAuthentication(), + ]; + } +} diff --git a/app/Models/Team.php b/app/Models/Team.php index ce44553..621530a 100644 --- a/app/Models/Team.php +++ b/app/Models/Team.php @@ -3,9 +3,11 @@ namespace App\Models; use Illuminate\Database\Eloquent\Attributes\Fillable; +use Illuminate\Database\Eloquent\Attributes\Scope; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsToMany; +use Illuminate\Database\Eloquent\Relations\HasMany; /** * @property int $id @@ -34,13 +36,6 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany; #[Fillable(['name', 'slug', 'require_2fa'])] class Team extends Model { - protected function casts() - { - return [ - 'require_2fa' => 'boolean', - ]; - } - public function members(): BelongsToMany { return $this->belongsToMany(User::class, 'team_members') @@ -48,11 +43,23 @@ class Team extends Model ->withPivot('role') ->withTimestamps(); } + public function invites(): HasMany + { + return $this->hasMany(TeamInvite::class); + } + #[Scope] public function scopeVisibleTo(Builder $query, User $user): Builder { return $query->whereHas('members', function (Builder $query) use ($user): void { $query->whereKey($user->getKey()); }); } + + protected function casts() + { + return [ + 'require_2fa' => 'boolean', + ]; + } } diff --git a/resources/js/pages/team/Show.vue b/resources/js/pages/team/Show.vue index 9818c3a..0e27f59 100644 --- a/resources/js/pages/team/Show.vue +++ b/resources/js/pages/team/Show.vue @@ -1,15 +1,16 @@ diff --git a/resources/js/types/models.ts b/resources/js/types/models.ts index 454fb70..1d68a08 100644 --- a/resources/js/types/models.ts +++ b/resources/js/types/models.ts @@ -8,10 +8,20 @@ export interface Team { export type TeamMemberRole = 'admin' | 'member'; -export interface TeamMember { +export interface User { name: string; email: string; has_multi_factor: boolean; +} + +export interface TeamMember extends User { role: TeamMemberRole; - joined_at: string + joined_at: string; +} + +export interface TeamInvite { + invitedEmail: string; + team: Team; + invitedBy: User; + invitedAt: string; } diff --git a/routes/web.php b/routes/web.php index cd9685b..adb9362 100644 --- a/routes/web.php +++ b/routes/web.php @@ -10,9 +10,9 @@ Route::middleware(['auth'])->group(function () { Route::inertia('dashboard', 'Dashboard')->name('dashboard'); Route::controller(TeamController::class)->prefix('teams')->name('teams.')->group(function () { - Route::controller(TeamInviteAcceptController::class)->prefix('invite')->name('invite.')->group(function () { - Route::get('/accept', 'create')->name('create'); - Route::post('/accept', 'store')->name('store'); + Route::prefix('invite')->name('invite.')->group(function () { + Route::get('/accept', [TeamInviteAcceptController::class, 'create'])->name('create'); + Route::post('/accept', [TeamInviteAcceptController::class, 'store'])->name('store'); }); Route::get('/', 'index')->name('index'); @@ -22,6 +22,8 @@ Route::middleware(['auth'])->group(function () { Route::patch('/{team:slug}', 'update')->name('update'); Route::post('/name-to-slug', 'nameToSlug')->name('nameToSlug'); Route::get('/{team:slug}', 'show')->name('show'); + Route::post('{team:slug}/invite', [TeamController::class, 'invite'])->name('invite'); + Route::post('{team:slug}/invite/revoke', [TeamController::class, 'revokeInvite'])->name('invite.revoke'); });