Merge pull request #1424 from pixelfed/frontend-ui-refactor

Add Contact Form
This commit is contained in:
daniel 2019-06-18 21:06:54 -06:00 committed by GitHub
commit 07182a5797
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 238 additions and 15 deletions

View file

@ -13,6 +13,6 @@ class Contact extends Model
public function adminUrl()
{
return url('/i/admin/contact/show/' . $this->id);
return url('/i/admin/messages/show/' . $this->id);
}
}

View file

@ -3,6 +3,7 @@
namespace App\Http\Controllers;
use App\{
Contact,
FailedJob,
Hashtag,
Instance,
@ -47,6 +48,10 @@ class AdminController extends Controller
$data = Cache::remember('admin:dashboard:home:data', now()->addMinutes(15), function() {
$day = config('database.default') == 'pgsql' ? 'DATE_PART(\'day\',' : 'day(';
return [
'contact' => [
'count' => PrettyNumber::convert(Contact::whereNull('read_at')->count()),
'graph' => Contact::selectRaw('count(*) as count, '.$day.'created_at) as day')->whereNull('read_at')->whereBetween('created_at',[now()->subDays(14), now()])->groupBy('day')->orderBy('day')->pluck('count')
],
'failedjobs' => [
'count' => PrettyNumber::convert(FailedJob::where('failed_at', '>=', \Carbon\Carbon::now()->subDay())->count()),
'graph' => FailedJob::selectRaw('count(*) as count, '.$day.'failed_at) as d')->groupBy('d')->whereBetween('failed_at',[now()->subDays(24), now()])->orderBy('d')->pluck('count')
@ -248,4 +253,30 @@ class AdminController extends Controller
return view('admin.hashtags.home', compact('hashtags'));
}
public function messagesHome(Request $request)
{
$messages = Contact::orderByDesc('id')->paginate(10);
return view('admin.messages.home', compact('messages'));
}
public function messagesShow(Request $request, $id)
{
$message = Contact::findOrFail($id);
return view('admin.messages.show', compact('message'));
}
public function messagesMarkRead(Request $request)
{
$this->validate($request, [
'id' => 'required|integer|min:1'
]);
$id = $request->input('id');
$message = Contact::findOrFail($id);
if($message->read_at) {
return;
}
$message->read_at = now();
$message->save();
return;
}
}

View file

@ -5,16 +5,19 @@ namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Auth;
use App\Contact;
use App\Jobs\ContactPipeline\ContactPipeline;
class ContactController extends Controller
{
public function show(Request $request)
{
abort_if(!config('instance.email') && !config('instance.contact.enabled'), 404);
return view('site.contact');
}
public function store(Request $request)
{
abort_if(!config('instance.contact.enabled'), 404);
abort_if(!Auth::check(), 403);
$this->validate($request, [
@ -26,11 +29,12 @@ class ContactController extends Controller
$request_response = $request->input('request_response') == 'on' ? true : false;
$user = Auth::user();
$max = config('instance.contact.max_per_day');
$contact = Contact::whereUserId($user->id)
->whereDate('created_at', '>', now()->subDays(1))
->whereDate('created_at', '>', now()->subDays($max))
->count();
if($contact >= 2) {
if($contact >= $max) {
return redirect()->back()->with('error', 'You have recently sent a message. Please try again later.');
}
@ -40,6 +44,8 @@ class ContactController extends Controller
$contact->message = $message;
$contact->save();
ContactPipeline::dispatchNow($contact);
return redirect()->back()->with('status', 'Success - Your message has been sent to admins.');
}
}

View file

@ -0,0 +1,44 @@
<?php
namespace App\Jobs\ContactPipeline;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use App\Contact;
use App\Mail\ContactAdmin;
use Mail;
class ContactPipeline implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $contact;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Contact $contact)
{
$this->contact = $contact;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$contact = $this->contact;
$email = config('instance.email');
if(config('instance.contact.enabled') == false || $contact->read_at !== null || filter_var($email, FILTER_VALIDATE_EMAIL) == false) {
return;
}
Mail::to($email)->send(new ContactAdmin($contact));
}
}

View file

@ -2,4 +2,9 @@
return [
'email' => env('INSTANCE_CONTACT_EMAIL'),
'contact' => [
'enabled' => env('INSTANCE_CONTACT_FORM', false),
'max_per_day' => env('INSTANCE_CONTACT_MAX_PER_DAY', 1),
],
];

View file

@ -12,5 +12,7 @@ return [
'l10nWip' => 'Were still working on localization support',
'currentLocale' => 'Current locale',
'selectLocale' => 'Select one of the supported languages',
'contact' => 'Contact',
'contact-us' => 'Contact Us',
];

View file

@ -17,7 +17,8 @@
</div>
<hr>
<table class="table table-responsive">
<div class="table-responsive">
<table class="table">
<thead class="bg-light">
<tr>
<th scope="col">#</th>
@ -45,4 +46,5 @@
@endforeach
</tbody>
</table>
</div>
@endsection

View file

@ -11,10 +11,10 @@
<div class="col-md-4">
<div class="card" style="min-height:125px">
<div class="card-body">
<p class="small text-uppercase font-weight-bold text-muted">Alerts</p>
<p class="h2 mb-0">0</p>
<p class="small text-uppercase font-weight-bold text-muted">New Messages</p>
<p class="h2 mb-0">{{$data['contact']['count']}}</p>
</div>
<canvas width="100" height="10" class="sparkline mb-1" data-chart_values="[0,0]"></canvas>
<canvas width="100" height="10" class="sparkline mb-1" data-chart_values="{{$data['contact']['graph']}}"></canvas>
</div>
</div>

View file

@ -0,0 +1,36 @@
@extends('admin.partial.template-full')
@section('section')
<div class="title">
<h3 class="font-weight-bold d-inline-block">Messages</h3>
</div>
<hr>
<div class="table-responsive">
<table class="table">
<thead class="bg-light">
<tr>
<th scope="col">#</th>
<th scope="col">User</th>
<th scope="col">Message</th>
<th scope="col">Created</th>
</tr>
</thead>
<tbody>
@foreach($messages as $msg)
<tr>
<td>
<a href="/i/admin/messages/show/{{$msg->id}}" class="btn btn-sm btn-outline-primary">
{{$msg->id}}
</a>
</td>
<td class="font-weight-bold"><a href="{{$msg->user->url()}}">{{$msg->user->username}}</a></td>
<td class="font-weight-bold">{{str_limit($msg->message, 40)}}</td>
<td class="font-weight-bold">{{$msg->created_at->diffForHumans()}}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
{{$messages->links()}}
@endsection

View file

@ -0,0 +1,68 @@
@extends('admin.partial.template-full')
@section('section')
<div class="title">
<div class="d-flex justify-content-between align-items-center">
<div class="font-weight-bold"># {{$message->id}}</div>
<div class="font-weight-bold h3">Message</div>
<div>
@if($message->read_at)
<span class="btn btn-outline-secondary btn-sm disabled" disabled>Read</span>
@else
<button type="button" class="btn btn-outline-primary btn-sm" id="markRead">Mark Read</button>
@endif
</div>
</div>
</div>
<hr>
<div class="row">
<div class="col-12 col-md-3 text-md-right">
@if($message->response_requested)
<p class="text-dark font-weight-bold">Response Requested</p>
@endif
<p class="text-dark">Sent {{$message->created_at->diffForHumans()}}</p>
</div>
<div class="col-12 col-md-6">
<div class="card shadow-none border">
<div class="card-header bg-white">
<div class="media">
<img src="{{$message->user->profile->avatarUrl()}}" class="mr-3 rounded-circle" width="40px" height="40px">
<div class="media-body">
<h5 class="my-0">&commat;{{$message->user->username}}</h5>
<span class="text-muted">{{$message->user->email}}</span>
</div>
</div>
</div>
<div class="card-body">
<p class="mb-0">{{$message->message}}</p>
</div>
</div>
</div>
<div class="col-12 col-md-3">
{{-- @if($message->responded_at == null)
<button class="btn btn-primary font-weight-bold btn-block">Send Response</button>
<hr>
@endif
<button class="btn btn-outline-danger font-weight-bold btn-block">Delete</button> --}}
</div>
</div>
@endsection
@push('scripts')
<script type="text/javascript">
$('#markRead').on('click', function(e) {
e.preventDefault();
axios.post('/i/admin/messages/mark-read', {
id: '{{$message->id}}',
}).then(res => {
window.location.href = window.location.href;
})
})
</script>
@endpush

View file

@ -8,8 +8,8 @@
<li class="nav-item mx-2 {{request()->is('*admin/dashboard')?'active':''}}">
<a class="nav-link" href="{{route('admin.home')}}">Dashboard</a>
</li>
<li class="nav-item mx-2 {{request()->is('*apps*')?'active':''}}">
<a class="nav-link font-weight-lighter text-muted" href="{{route('admin.apps')}}">Apps</a>
<li class="nav-item mx-2 {{request()->is('*messages*')?'active':''}}">
<a class="nav-link font-weight-lighter text-muted" href="{{route('admin.messages')}}">Messages</a>
</li>
<li class="nav-item mx-2 {{request()->is('*hashtags*')?'active':''}}">
<a class="nav-link font-weight-lighter text-muted" href="{{route('admin.hashtags')}}">Hashtags</a>
@ -37,6 +37,7 @@
More
</a>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
<a class="dropdown-item font-weight-bold {{request()->is('*apps*')?'active':''}}" href="{{route('admin.apps')}}">Apps</a>
<a class="dropdown-item font-weight-bold {{request()->is('*discover*')?'active':''}}" href="{{route('admin.discover')}}">Discover</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item font-weight-bold" href="/horizon">Horizon</a>

View file

@ -2,6 +2,9 @@
<div class="container py-5">
<p class="mb-0 text-uppercase font-weight-bold small text-justify">
<a href="{{route('site.about')}}" class="text-primary pr-3">{{__('site.about')}}</a>
@if(config('instance.contact.enabled') || config('instance.email'))
<a href="{{route('site.contact')}}" class="text-primary pr-3">{{__('site.contact-us')}}</a>
@endif
<a href="{{route('site.help')}}" class="text-primary pr-3">{{__('site.help')}}</a>
<a href="{{route('site.opensource')}}" class="text-primary pr-3">{{__('site.opensource')}}</a>
<a href="{{route('site.terms')}}" class="text-primary pr-3">{{__('site.terms')}}</a>

View file

@ -3,11 +3,23 @@
@section('section')
<div class="title">
<h3 class="font-weight-bold">Contact</h3>
<h3 class="font-weight-bold">{{__('site.contact-us')}}</h3>
</div>
<hr>
<section>
@auth
<p class="lead">
@if(config('instance.email') && config('instance.contact.enabled'))
You can contact the admins by sending an email to <span class="font-weight-bold">{{config('instance.email')}}</span> or by using the form below.
@elseif(config('instance.email') && !config('instance.contact.enabled'))
You can contact the admins by sending an email to <span class="font-weight-bold">{{config('instance.email')}}</span>.
@elseif(!config('instance.email') && config('instance.contact.enabled'))
You can contact the admins by using the form below.
@else
The admins have not set a contact email address.
@endif
</p>
@if(config('instance.contact.enabled'))
<form method="POST">
@csrf
<div class="form-group">
@ -21,12 +33,15 @@
</div>
<button type="submit" class="btn btn-primary font-weight-bold py-0">Submit</button>
</form>
@endif
@else
<p class="lead">
@if(filter_var(config('instance.email'), FILTER_VALIDATE_EMAIL) == true)
You can contact the admins by sending an email to {{config('instance.email')}}.
@else
The admins have not listed any public email. Please log in to send a message.
@if(config('instance.email') && config('instance.contact.enabled'))
You can contact the admins by sending an email to <span class="font-weight-bold">{{config('instance.email')}}</span> or log in to send a message.
@elseif (!config('instance.email') && config('instance.contact.enabled'))
The admins have not set a contact email address. Please log in to send a message.
@elseif (config('instance.email') && !config('instance.contact.enabled'))
You can contact the admins by sending an email to <span class="font-weight-bold">{{config('instance.email')}}</span>.
@endif
</p>
@endauth

View file

@ -28,6 +28,11 @@
<li class="nav-item">
<hr>
</li>
@if(config('instance.contact.enabled') || config('instance.email'))
<li class="nav-item pl-3 {{request()->is('site/contact')?'active':''}}">
<a class="nav-link font-weight-light text-muted" href="{{route('site.contact')}}">{{__('site.contact-us')}}</a>
</li>
@endif
<li class="nav-item pl-3 {{request()->is('site/terms')?'active':''}}">
<a class="nav-link font-weight-light text-muted" href="{{route('site.terms')}}">{{__('site.terms')}}</a>
</li>

View file

@ -44,6 +44,10 @@ Route::domain(config('pixelfed.domain.admin'))->prefix('i/admin')->group(functio
Route::get('discover/category/edit/{id}', 'AdminController@discoverCategoryEdit');
Route::post('discover/category/edit/{id}', 'AdminController@discoverCategoryUpdate');
Route::post('discover/category/hashtag/create', 'AdminController@discoveryCategoryTagStore')->name('admin.discover.create-hashtag');
Route::get('messages/home', 'AdminController@messagesHome')->name('admin.messages');
Route::get('messages/show/{id}', 'AdminController@messagesShow');
Route::post('messages/mark-read', 'AdminController@messagesMarkRead');
});
Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofactor', 'localization'])->group(function () {
@ -264,7 +268,8 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
Route::view('privacy', 'site.privacy')->name('site.privacy');
Route::view('platform', 'site.platform')->name('site.platform');
Route::view('language', 'site.language')->name('site.language');
Route::get('contact', 'ContactController@show')->name('site.contact');
Route::post('contact', 'ContactController@store');
Route::group(['prefix'=>'kb'], function() {
Route::view('getting-started', 'site.help.getting-started')->name('help.getting-started');
Route::view('sharing-media', 'site.help.sharing-media')->name('help.sharing-media');