Skip to content

Commit

Permalink
Added user invitation email
Browse files Browse the repository at this point in the history
  • Loading branch information
ok200paul committed Aug 20, 2024
1 parent ebe2887 commit bfb1427
Show file tree
Hide file tree
Showing 6 changed files with 218 additions and 15 deletions.
59 changes: 57 additions & 2 deletions app/Http/Controllers/Api/V1/Admin/ApiAdminTeamUsersController.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use App\Exceptions\DisallowedApiFieldException;
use App\Http\Controllers\Api\HandlesAPIRequests;
use App\Http\Controllers\Controller;
use App\Jobs\TeamUsers\SendTeamUserInvitationEmail;
use App\Models\TeamUser;
use Exception;
use Illuminate\Http\JsonResponse;
Expand Down Expand Up @@ -132,8 +133,62 @@ public function show(string $id)
*/
public function update(string $id)
{
$this->responseCode = 403;
$this->message = ApiResponse::RESPONSE_METHOD_NOT_ALLOWED->value;
$validationArray = [
'send_invite_email' => [
'sometimes',
'boolean',
],
];

$validator = Validator::make($this->request->all(), $validationArray);

if ($validator->fails()) {

$this->responseCode = 400;
$this->message = $validator->errors()
->first();

}
else {

try {

$model = TeamUser::find($id);

if (!$model) {

$this->responseCode = 404;
$this->message = ApiResponse::RESPONSE_NOT_FOUND->value;

}
else{

if($this->request->has('send_invite_email'))
{
$sendInviteEmail = $this->request->get('send_invite_email');

if($sendInviteEmail)
{
$model->invitation_sent_at = now();
$model->save();

dispatch(new SendTeamUserInvitationEmail($model));
}
}

$this->message = ApiResponse::RESPONSE_UPDATED->value;
$this->data = $model;
}


}
catch (Exception $e) {

$this->responseCode = 500;
$this->message = ApiResponse::RESPONSE_ERROR->value . ': "' . $e->getMessage() . '".';

}
}

return $this->respond();
}
Expand Down
43 changes: 43 additions & 0 deletions app/Jobs/TeamUsers/SendTeamUserInvitationEmail.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

namespace App\Jobs\TeamUsers;

use App\Models\TeamUser;
use App\Models\User;
use App\Notifications\Mail\TeamUsers\SendTeamUserInvitationEmailNotification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;

class SendTeamUserInvitationEmail implements ShouldQueue
{
use Queueable;

/**
* Create a new job instance.
*/
public function __construct(public TeamUser $teamUser)
{
//
}

/**
* Execute the job.
*/
public function handle(): void
{
Log::info('SendTeamUserInvitationEmail');
$userToNotify = User::find($this->teamUser->user_id);

if($userToNotify)
{
$userToNotify->current_team_id = $this->teamUser->team_id;
$userToNotify->saveQuietly();

$userToNotify->notify(new SendTeamUserInvitationEmailNotification($this->teamUser));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

namespace App\Notifications\Mail\TeamUsers;

use App\Models\Team;
use App\Models\TeamUser;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;

class SendTeamUserInvitationEmailNotification extends Notification implements ShouldQueue
{
use Queueable;

/**
* Create a new notification instance.
*/
public function __construct(public TeamUser $teamUser)
{
//
}

/**
* Get the notification's delivery channels.
*
* @return array<int, string>
*/
public function via(object $notifiable): array
{
return ['mail'];
}

/**
* Get the mail representation of the notification.
*/
public function toMail(object $notifiable): MailMessage
{
$team = Team::find($this->teamUser->team_id);

return (new MailMessage)
->subject('You have been invited to join ' . $team->name. ' - '.config('app.name'))
->line('You have been invited to join team "' . $team->name. '" on '.config('app.name').'.')
->line('An account has been created for you on the team, but if you have never logged in to use the system, you may need to reset your password in order to log in.')
->line('Please follow the button below to reset your password and log in.')
->action('Reset your password & log in', url('/forgot-password'))
->line('Thank you for using our application!');
}

/**
* Get the array representation of the notification.
*
* @return array<string, mixed>
*/
public function toArray(object $notifiable): array
{
return [
//
];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public function up(): void
$table->id();
$table->unsignedBigInteger('team_id')->index('tu_ti');
$table->unsignedBigInteger('user_id')->index('tu_ui');
$table->dateTime('invitation_sent_at')->nullable();
$table->timestamps();
$table->softDeletes();
});
Expand Down
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

63 changes: 53 additions & 10 deletions resources/js/Pages/Admin/Teams/Team.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ import AdminTeamMerchantTeamsComponent from "@/Components/Admin/TeamMerchantTeam
import AdminTeamDetailsComponent from "@/Components/Admin/AdminTeamDetailsComponent.vue";
import AdminTeamServiceTeamsComponent from "@/Components/Admin/TeamServiceTeams/AdminTeamServiceTeamsComponent.vue";
import AdminUserSelectComponent from "@/Components/Admin/AdminUserSelectComponent.vue";
import SecondaryButton from "@/Components/SecondaryButton.vue";
import AjaxLoadingIndicator from "@/Components/AjaxLoadingIndicator.vue";
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime";
dayjs.extend(relativeTime);
const $props = defineProps({
id: {
Expand All @@ -20,6 +25,7 @@ const $props = defineProps({
const limit = ref(5)
const team = ref({})
const teamUsers = ref({})
const invitingTeamUser = ref(false)
onMounted(() => {
getTeam()
Expand All @@ -37,7 +43,7 @@ function createNewTeamUser(addingUserId) {
title: 'Success!',
icon: 'success',
timer: 1000
}).then(()=> {
}).then(() => {
getTeamUsers()
})
}).catch(error => {
Expand All @@ -61,6 +67,21 @@ function getTeamUsers(page = 1) {
})
}
function sendInvite(teamUser){
invitingTeamUser.value = true;
let payload = {
send_invite_email: true
};
axios.put('/admin/team-users/'+teamUser.id, payload).then(response => {
getTeamUsers();
invitingTeamUser.value = false;
}).catch(error => {
console.log(error);
invitingTeamUser.value = false;
})
}
function setDataPage(page) {
getTeamUsers(page);
}
Expand Down Expand Up @@ -92,18 +113,40 @@ function setDataPage(page) {
</div>

<div class="card">
<AjaxLoadingIndicator :loading="invitingTeamUser"></AjaxLoadingIndicator>
<div class="card-header">
Team members
</div>

<div v-if="teamUsers.data && teamUsers.data.length > 0">
<Link :href="route('admin.user', teamUser.user_id)" v-for="teamUser in teamUsers.data" class="hover:no-underline hover:opacity-75">
<div :class="{'border-b p-2': teamUsers.data.length > 1}">
<div v-if="teamUser.user" class="flex items-center">
<div>{{ teamUser.user.name }}</div>
<div v-for="teamUser in teamUsers.data" class="flex hover:opacity-75">
<Link :href="route('admin.user', teamUser.user_id)"
class="border-b p-2 mr-2 flex-grow flex justify-between items-center hover:no-underline">


<div>{{ teamUser.user?.name }}</div>
<div class="flex justify-end items-center">

<div v-if="teamUser.invitation_sent_at" class="pr-2 text-xs">
Invited: {{dayjs(teamUser.invitation_sent_at).fromNow()}}
</div>

<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" />
</svg>
</div>

</Link>
<SecondaryButton @click="sendInvite(teamUser)">
<div>
<div v-if="teamUser.invitation_sent_at">Resend Invite</div>
<div v-else-if="invitingTeamUser" class="px-2">Sending..</div>
<div v-else class="px-2">Send Invite</div>
</div>
</div>
</Link>
</SecondaryButton>
</div>


</div>

<div class="flex justify-end items-center mt-4">
Expand All @@ -120,23 +163,23 @@ function setDataPage(page) {
Add user to team
</div>

<AdminUserSelectComponent :teamId="$props.id" @createNewTeamUser="createNewTeamUser" />
<AdminUserSelectComponent :teamId="$props.id" @createNewTeamUser="createNewTeamUser"/>
</div>

<div class="card">
<div class="card-header">
Merchant teams
</div>

<AdminTeamMerchantTeamsComponent :teamId="$props.id" :teamName="team.name" />
<AdminTeamMerchantTeamsComponent :teamId="$props.id" :teamName="team.name"/>
</div>

<div class="card">
<div class="card-header">
Service teams
</div>

<AdminTeamServiceTeamsComponent :teamId="$props.id" :teamName="team.name" />
<AdminTeamServiceTeamsComponent :teamId="$props.id" :teamName="team.name"/>
</div>

<div class="card">
Expand Down

0 comments on commit bfb1427

Please sign in to comment.