pixelfed/resources/views/admin/curated-register/partials/activity-log.blade.php
2024-02-26 20:41:27 -07:00

515 lines
20 KiB
PHP

<template v-if="loaded">
@if($email_verified_at === null)
<div class="alert alert-danger mb-3">
<p class="mb-0 font-weight-bold">Applicant has not verified their email address yet, action can not be taken at this time.</p>
</div>
@elseif($is_closed != true)
<div class="d-flex justify-content-between flex-column flex-md-row mb-4" style="gap:1rem">
<button
class="btn btn-success bg-gradient-success rounded-pill"
v-on:click.prevent="handleAction('approve', $event)">
Approve
</button>
<button
class="btn btn-danger bg-gradient-danger rounded-pill flex-grow-1"
v-on:click.prevent="handleAction('reject', $event)">
Reject
</button>
<button
class="btn rounded-pill px-md-5"
:class="[ composeFormOpen ? 'btn-dark bg-gradient-dark' : 'btn-outline-dark' ]"
v-on:click.prevent="handleAction('request', $event)">
Request details
</button>
<button
class="btn rounded-pill px-md-5"
:class="[ messageFormOpen ? 'btn-dark bg-gradient-dark' : 'btn-outline-dark' ]"
v-on:click.prevent="handleAction('message', $event)">
Message
</button>
</div>
@else
@if($is_rejected == true)
<p>Application was <span class="font-weight-bold text-danger">rejected</span> on {{ $action_taken_at }}</p>
@elseif($is_approved == true)
<p>Application was <span class="font-weight-bold text-success">approved</span> on {{ $action_taken_at }}</p>
@else
<p>Application was closed on {{ $action_taken_at }}</p>
@endif
@endif
<transition name="fade">
<div v-show="composeFormOpen">
<div class="card">
<div class="card-body pt-0">
<p class="lead font-weight-bold text-center">Request Additional Details</p>
<p class="text-muted">Use this form to request additional details. Once you press Send, we'll send the potential user an email with a special link they can visit with a form that they can provide additional details with. You can also Preview the email before it's sent.</p>
<div v-if="responseTemplates && responseTemplates.length" class="my-3">
<p class="small font-weight-bold mb-1">Template Responses</p>
<div class="d-grid">
<template v-for="tmpl in responseTemplates">
<button
class="btn btn-lighter btn-sm py-1 font-weight-bold rounded-lg text-dark border border-muted px-3"
style="font-size: 13px;"
@click="useTemplate(tmpl)">
<i class="far fa-plus mr-1 text-muted"></i> @{{ tmpl.name.slice(0, 25) }}
</button>
</template>
</div>
</div>
<div class="request-form">
<div class="form-group">
<label for="requestDetailsMessageInput" class="small text-muted">Your Message:</label>
<textarea
class="form-control text-dark"
id="requestDetailsMessageInput"
rows="5"
v-model="composeMessage"
style="white-space: pre-wrap;"
placeholder="Enter your additional detail message here...">
</textarea>
<p class="help-text small text-right">
<span>@{{ composeMessage && composeMessage.length ? composeMessage.length : 0 }}</span>
<span>/</span>
<span>2000</span>
</p>
</div>
<div class="d-flex">
<button
type="button"
class="btn btn-primary rounded-pill btn-sm px-4"
v-on:click.prevent="handleSend()">
Send
</button>
<a
class="btn btn-dark rounded-pill btn-sm px-4"
:href="previewDetailsMessageUrl"
target="_blank">
Preview
</a>
<a
v-if="composeMessage && composeMessage.length"
class="btn btn-outline-danger text-danger rounded-pill btn-sm px-4"
@click="composeMessage = null">
Clear
</a>
</div>
</div>
</div>
</div>
</div>
</transition>
<transition name="fade">
<div v-show="messageFormOpen">
<div class="card">
<div class="card-body pt-0">
<p class="lead font-weight-bold text-center">Send Message</p>
<p class="text-muted">Use this form to send a message to the applicant. Once you press Send, we'll send the potential user an email with your message. You can also Preview the email before it's sent.</p>
<div v-if="responseTemplates && responseTemplates.length" class="my-3">
<p class="small font-weight-bold mb-1">Template Responses</p>
<div class="d-grid">
<template v-for="tmpl in responseTemplates">
<button
class="btn btn-lighter btn-sm py-1 font-weight-bold rounded-lg text-dark border border-muted px-3"
style="font-size: 13px;"
@click="useTemplateMessage(tmpl)">
<i class="far fa-plus mr-1 text-muted"></i> @{{ tmpl.name.slice(0, 25) }}
</button>
</template>
</div>
</div>
<div class="request-form">
<div class="form-group">
<label for="sendMessageInput" class="small text-muted">Your Message:</label>
<textarea
class="form-control text-dark"
id="sendMessageInput"
rows="5"
v-model="messageBody"
style="white-space: pre-wrap;"
placeholder="Enter your message here...">
</textarea>
<p class="help-text small text-right">
<span>@{{ messageBody && messageBody.length ? messageBody.length : 0 }}</span>
<span>/</span>
<span>500</span>
</p>
</div>
<div class="d-flex">
<button
type="button"
class="btn btn-primary rounded-pill btn-sm px-4"
v-on:click.prevent="handleMessageSend()">
Send
</button>
<a
class="btn btn-dark rounded-pill btn-sm px-4"
:href="previewMessageUrl"
target="_blank">
Preview
</a>
</div>
</div>
</div>
</div>
</div>
</transition>
<div class="card border">
<div class="card-body">
<p class="text-center font-weight-bold">Activity Log</p>
<div class="activity-log">
<div v-if="!loaded" class="d-flex justify-content-center align-items-center py-5 my-5">
<b-spinner />
</div>
<template v-else>
<div
v-for="activity in activities"
class="activity-log-item"
:key="activity.timestamp">
<div v-if="activity.action === 'approved'" class="activity-log-item-icon bg-success">
<i class="far fa-check fa-lg text-white"></i>
</div>
<div v-else-if="activity.action === 'rejected'" class="activity-log-item-icon bg-danger">
<i class="far fa-times fa-lg text-white"></i>
</div>
<div v-else-if="activity.action === 'request_details'" class="activity-log-item-icon bg-lighter">
<i class="fas fa-exclamation fa-lg text-dark"></i>
</div>
<div v-else class="activity-log-item-icon">
<i class="fas fa-circle"></i>
</div>
<div class="activity-log-item-date">@{{ parseDate(activity.timestamp) }}<span>@{{ parseTime(activity.timestamp) }}</span></div>
<div class="activity-log-item-content"><span class="activity-log-item-content-title">@{{ activity.title }}</span><span class="activity-log-item-content-message" v-if="activity.message">@{{ strLimit(activity.message) }}</span></div>
<div class="d-flex" style="gap: 1rem;">
<div v-if="activity.link">
<a href="#" class="activity-log-item-content-link text-muted" @click.prevent="openModal(activity)">Details</a>
</div>
<div v-if="activity.user_response">
<a href="#" class="activity-log-item-content-link" @click.prevent="openUserResponse(activity)">View User Response</a>
</div>
</div>
</div>
</template>
</div>
</div>
</div>
</template>
<template v-else>
<div class="card card-body d-flex justify-content-center align-items-center py-5">
<b-spinner />
</div>
</template>
@push('scripts')
<script type="text/javascript">
let app = new Vue({
el: '#panel',
data() {
return {
loaded: false,
activities: [],
composeFormOpen: false,
messageFormOpen: false,
composeMessage: null,
messageBody: null,
responseTemplates: [],
}
},
mounted() {
setTimeout(() => {
this.fetchResponseTemplates();
this.fetchActivities();
}, 1000)
},
computed: {
previewDetailsMessageUrl() {
return `/i/admin/curated-onboarding/show/{{$id}}/preview-details-message?message=${encodeURIComponent(this.composeMessage)}`;
},
previewMessageUrl() {
return `/i/admin/curated-onboarding/show/{{$id}}/preview-message?message=${encodeURIComponent(this.messageBody)}`;
}
},
methods: {
parseDate(timestamp) {
const date = new Date(timestamp);
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric'
});
},
parseTime(timestamp) {
const date = new Date(timestamp);
return date.toLocaleTimeString('en-US', {
hour: '2-digit',
minute: '2-digit',
hour12: true
});
},
strLimit(str, len = 15) {
if(str && str.length) {
return str.slice(0, len) + (str.length > 15 ? '...' : '');
}
return str;
},
fetchResponseTemplates() {
axios.get('/i/admin/api/curated-onboarding/templates/get')
.then(res => {
this.responseTemplates = res.data;
})
},
fetchActivities() {
axios.get('/i/admin/api/curated-onboarding/show/{{$id}}/activity-log')
.then(res => {
this.activities = res.data;
})
.finally(() => {
this.loaded = true;
})
},
handleAction(action, $event) {
$event.currentTarget?.blur();
switch(action) {
case 'approve':
this.handleApprove();
break;
case 'reject':
this.handleReject();
break;
case 'request':
this.messageFormOpen = false;
this.composeFormOpen = !this.composeFormOpen;
break;
case 'message':
this.composeFormOpen = false;
this.messageFormOpen = !this.messageFormOpen;
break;
}
},
handleApprove() {
swal({
title: "Approve Request?",
text: "The user application request will be approved.",
icon: "warning",
buttons: true,
dangerMode: true,
}).then((willApprove) => {
if(willApprove) {
this.handleApproveAction();
} else {
swal("Approval Cancelled!", "The application approval has been cancelled. If you change your mind, you can easily approve or reject this application in the future.", "success");
}
})
},
handleReject() {
swal({
title: "Are you sure?",
text: "The user application request will be rejected.",
icon: "warning",
buttons: true,
dangerMode: true,
})
.then((willReject) => {
if (willReject) {
swal({
title: "Choose Action",
text: "You can provide a rejection email, or simply silently reject",
icon: "warning",
buttons: {
cancel: "Cancel",
reject: {
text: "Reject with email",
value: "reject-email"
},
silent: {
text: "Silently Reject",
value: "reject-silent"
}
},
dangerMode: true,
})
.then(res => {
if(!res) {
swal("Rejection Cancelled!", "The application rejection has been cancelled. If you change your mind, you can easily reject this application in the future.", "success");
} else {
this.handleRejectAction(res);
}
})
} else {
swal("Rejection Cancelled!", "The application rejection has been cancelled. If you change your mind, you can easily reject this application in the future.", "success");
}
});
},
handleRejectAction(action) {
axios.post('/i/admin/api/curated-onboarding/show/{{$id}}/reject', {
action: action
}).then(res => {
window.location.href = '/i/admin/curated-onboarding/home?a=rj';
console.log(res);
})
},
handleApproveAction() {
axios.post('/i/admin/api/curated-onboarding/show/{{$id}}/approve')
.then(res => {
window.location.href = '/i/admin/curated-onboarding/home?a=aj';
})
},
handlePreview() {
axios.post('/i/admin/api/curated-onboarding/show/{{$id}}/message/preview', {
message: this.composeMessage
})
.then(res => {
console.log(res.data);
})
},
handleSend() {
swal({
title: "Confirm",
text: "Are you sure you want to send this request to this user?",
icon: "warning",
buttons: true,
dangerMode: true,
}).then((hasConfirmed) => {
if(hasConfirmed) {
this.composeFormOpen = false;
this.loaded = false;
axios.post('/i/admin/api/curated-onboarding/show/{{$id}}/message/send', {
message: this.composeMessage
})
.then(res => {
this.composeMessage = null;
swal('Successfully sent!','', 'success');
this.fetchActivities();
})
}
})
},
openModal(activity) {
swal(activity.title, activity.message)
},
openUserResponse(activity) {
swal('User Response', activity.user_response.message)
},
useTemplate(tmpl) {
this.composeMessage = tmpl.content;
},
useTemplateMessage(tmpl) {
this.messageBody = tmpl.content;
},
}
});
</script>
@endpush
@push('styles')
<style type="text/css">
.activity-log-item {
border-left: 1px solid #e5e5e5;
position: relative;
padding: 2rem 1.5rem .5rem 2.5rem;
font-size: .9rem;
margin-left: 3rem;
min-height: 5rem
}
.activity-log-item:last-child {
padding-bottom: 4rem
}
.activity-log-item .activity-log-item-date {
margin-bottom: .5rem;
font-weight: bold;
color: var(--primary);
}
.activity-log-item .activity-log-item-date span {
color: #888;
font-size: 85%;
padding-left: .4rem;
font-weight: 300;
}
.activity-log-item .activity-log-item-content {
padding: .5rem .8rem;
background-color: #f4f4f4;
border-radius: .5rem;
}
.activity-log-item .activity-log-item-content span {
display:block;
color:#666;
}
.activity-log-item .activity-log-item-icon {
line-height:2.6rem;
position:absolute;
left:-1.3rem;
width:2.6rem;
height:2.6rem;
text-align:center;
border-radius:50%;
font-size:1.1rem;
background-color:#fff;
color:#fff
}
.activity-log-item .activity-log-item-icon {
color:#e5e5e5;
border:1px solid #e5e5e5;
font-size:.6rem
}
.activity-log-item-content-title {
font-weight: 500;
font-size: 15px;
color: #000000 !important;
}
.activity-log-item-content-message {
font-weight: 400;
font-size: 13px;
}
@media(min-width:992px) {
.activity-log-item {
margin-left:10rem
}
.activity-log-item .activity-log-item-date {
position:absolute;
left:-10rem;
width:7.5rem;
text-align:right
}
.activity-log-item .activity-log-item-date span {
display:block
}
}
</style>
@endpush