Merge pull request #1470 from CachetHQ/view-subscriptions

Subscription changes
This commit is contained in:
Graham Campbell 2016-02-21 15:55:17 +00:00
commit 3487194415
24 changed files with 249 additions and 143 deletions

View File

@ -59,6 +59,7 @@ class SendComponentUpdateEmailNotificationHandler
foreach (Subscription::isVerifiedForComponent($component->id)->with('subscriber')->get() as $subscription) {
$subscriber = $subscription->subscriber;
$mail['email'] = $subscriber->email;
$mail['manage_link'] = route('subscribe.manage', ['code' => $subscriber->verify_code]);
$mail['unsubscribe_link'] = route('subscribe.unsubscribe', ['code' => $subscriber->verify_code, 'subscription' => $subscription->id]);
$this->mailer->queue([

View File

@ -75,6 +75,7 @@ class SendIncidentEmailNotificationHandler
'html_content' => $incident->formattedMessage,
'text_content' => $incident->message,
'token' => $subscriber->token,
'manage_link' => route('subscribe.manage', ['code' => $subscriber->verify_code]),
'unsubscribe_link' => route('subscribe.unsubscribe', ['code' => $subscriber->verify_code]),
];

View File

@ -71,6 +71,7 @@ class SendMaintenanceEmailNotificationHandler
'text_content' => $data->message,
'scheduled_at' => $data->scheduled_at_formatted,
'token' => $subscriber->token,
'manage_link' => route('subscribe.manage', ['code' => $subscriber->verify_code]),
'unsubscribe_link' => route('subscribe.unsubscribe', ['code' => $subscriber->verify_code]),
];

View File

@ -13,6 +13,7 @@ namespace CachetHQ\Cachet\Composers;
use CachetHQ\Cachet\Models\Component;
use CachetHQ\Cachet\Models\Incident;
use CachetHQ\Cachet\Models\Subscriber;
use Illuminate\Contracts\View\View;
class DashboardComposer
@ -28,5 +29,6 @@ class DashboardComposer
{
$view->withIncidentCount(Incident::notScheduled()->count());
$view->withComponentCount(Component::all()->count());
$view->withSubscriberCount(Subscriber::isVerified()->count());
}
}

View File

@ -34,7 +34,7 @@ class ComposerServiceProvider extends ServiceProvider
$factory->composer('*', CurrentUserComposer::class);
$factory->composer(['index'], MetricsComposer::class);
$factory->composer(['index', 'incident', 'subscribe', 'signup'], StatusPageComposer::class);
$factory->composer(['index', 'incident', 'subscribe', 'signup', 'dashboard.settings.theme'], ThemeComposer::class);
$factory->composer(['index', 'incident', 'subscribe.*', 'signup', 'dashboard.settings.theme'], ThemeComposer::class);
$factory->composer('dashboard.*', DashboardComposer::class);
$factory->composer(['setup', 'dashboard.settings.localization'], TimezoneLocaleComposer::class);
}

View File

@ -42,7 +42,7 @@ class SubscribeController extends Controller
*/
public function showSubscribe()
{
return View::make('subscribe')
return View::make('subscribe.subscribe')
->withAboutApp(Markdown::convertToHtml(Config::get('setting.app_about')));
}
@ -127,4 +127,26 @@ class SubscribeController extends Controller
return Redirect::route('status-page')
->withSuccess(sprintf('<strong>%s</strong> %s', trans('dashboard.notifications.awesome'), trans('cachet.subscriber.email.unsubscribed')));
}
/**
* Shows the subscription manager page.
*
* @param string|null $code
*
* @return \Illuminate\View\View
*/
public function showManage($code = null)
{
if ($code === null) {
throw new NotFoundHttpException();
}
$subscriber = Subscriber::where('verify_code', '=', $code)->first();
if (!$subscriber || !$subscriber->is_verified) {
throw new BadRequestHttpException();
}
return View::make('subscribe.manage')->withSubscriber($subscriber);
}
}

View File

@ -29,17 +29,20 @@ class SubscribeRoutes
*/
public function map(Registrar $router)
{
$router->group(['middleware' => ['web', 'ready', 'localize'], 'as' => 'subscribe.'], function ($router) {
$router->group(['middleware' => ['subscribers']], function ($router) {
$router->get('subscribe', [
'as' => 'subscribe',
'uses' => 'SubscribeController@showSubscribe',
]);
$router->group(['middleware' => ['web', 'ready', 'localize', 'subscribers'], 'as' => 'subscribe.'], function ($router) {
$router->get('subscribe', [
'as' => 'subscribe',
'uses' => 'SubscribeController@showSubscribe',
]);
$router->post('subscribe', [
'uses' => 'SubscribeController@postSubscribe',
]);
});
$router->post('subscribe', [
'uses' => 'SubscribeController@postSubscribe',
]);
$router->get('subscribe/manage/{code}', [
'as' => 'manage',
'uses' => 'SubscribeController@showManage',
]);
$router->get('subscribe/verify/{code}', [
'as' => 'verify',

View File

@ -48,6 +48,13 @@ class Subscriber extends Model implements HasPresenter
'email' => 'required|email',
];
/**
* The relations to eager load on every query.
*
* @var string[]
*/
protected $with = ['subscriptions'];
/**
* Overrides the models boot method.
*/

View File

@ -14,6 +14,7 @@ namespace CachetHQ\Cachet\Presenters;
use CachetHQ\Cachet\Dates\DateFactory;
use CachetHQ\Cachet\Presenters\Traits\TimestampsTrait;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Support\Facades\Config;
use McCool\LaravelAutoPresenter\BasePresenter;
class SubscriberPresenter extends BasePresenter implements Arrayable
@ -30,6 +31,16 @@ class SubscriberPresenter extends BasePresenter implements Arrayable
return app(DateFactory::class)->make($this->wrappedObject->verified_at)->toDateTimeString();
}
/**
* Present formatted subscribed date.
*
* @return string
*/
public function subscribed_at()
{
return ucfirst(app(DateFactory::class)->make($this->wrappedObject->subscribed_at)->format(Config::get('setting.incident_date_format', 'l jS F Y H:i:s')));
}
/**
* Convert the presenter instance to an array.
*

View File

@ -63,10 +63,15 @@ return [
'subscriber' => [
'subscribe' => 'Subscribe to get the most recent updates',
'button' => 'Subscribe',
'manage' => [
'no_subscriptions' => 'You\'re currently subscribed to all updates.',
'my_subscriptions' => 'You\'re currently subscribed to the following updates.',
],
'email' => [
'subscribe' => 'Subscribe to email updates.',
'subscribed' => 'You\'ve been subscribed to email notifications, please check your email to confirm your subscription.',
'verified' => 'Your email subscription has been confirmed. Thank you!',
'manage' => 'Manage your subscription.',
'unsubscribe' => 'Unsubscribe from email updates.',
'unsubscribed' => 'Your email subscription has been cancelled.',
'failure' => 'Something went wrong with the subscription.',

View File

@ -140,11 +140,13 @@ return [
],
// Subscribers
'subscribers' => [
'subscribers' => 'Subscribers',
'description' => 'Subscribers will receive email updates when incidents are created.',
'verified' => 'Verified',
'not_verified' => 'Not verified',
'add' => [
'subscribers' => 'Subscribers',
'description' => 'Subscribers will receive email updates when incidents are created or components are updated.',
'verified' => 'Verified',
'not_verified' => 'Not verified',
'subscriber' => ':email, subscribed :date',
'no_subscriptions' => 'Subscribed to all updates',
'add' => [
'title' => 'Add a new subscriber',
'success' => 'Subscriber has been added!',
'failure' => 'Something went wrong with the component.',

View File

@ -60,6 +60,7 @@
<a href="{{ route('dashboard.subscribers.index') }}">
<i class="icons ion-email"></i>
<span>{{ trans('dashboard.subscribers.subscribers') }}</span>
<span class="label label-info">{{ $subscriber_count }}</span>
</a>
</li>
<li {!! set_active('dashboard/team*') !!}>

View File

@ -18,30 +18,36 @@
<div class="content-wrapper header-fixed">
<div class="row">
<div class="col-sm-12">
<p class="lead">{{ trans('dashboard.subscribers.description') }}</p>
<p class="lead">{{ trans('dashboard.subscribers.description') }}</p>
<div class="striped-list">
@foreach($subscribers as $subscriber)
<div class="row striped-list-item">
<div class="col-xs-3">
<p>{{ $subscriber->email }}</p>
<div class="striped-list">
@foreach($subscribers as $subscriber)
<div class="row striped-list-item">
<div class="col-xs-3">
<p>{{ trans('dashboard.subscribers.subscriber', ['email' => $subscriber->email, 'date' => $subscriber->subscribed_at]) }}</p>
</div>
<div class="col-xs-3">
@if(is_null($subscriber->getOriginal('verified_at')))
<b class="text-danger">{{ trans('dashboard.subscribers.not_verified') }}</b>
@else
<b class="text-success">{{ trans('dashboard.subscribers.verified') }}</b>
@endif
</div>
<div class="col-xs-3">
@if($subscriber->subscriptions->count() > 0)
{!! $subscriber->subscriptions->map(function ($subscription) {
return '<span class="label label-primary">'.$subscription->component->name.'</span>';
})->implode(' ') !!}
@else
<p>{{ trans('dashboard.subscribers.no_subscriptions') }}</p>
@endif
</div>
<div class="col-xs-3 text-right">
<a href="/dashboard/subscribers/{{ $subscriber->id }}/delete" class="btn btn-danger confirm-action" data-method='DELETE'>{{ trans('forms.delete') }}</a>
</div>
</div>
@endforeach
</div>
<div class="col-xs-3">
<p>{{ $subscriber->created_at }}</p>
</div>
<div class="col-xs-3">
@if(is_null($subscriber->getOriginal('verified_at')))
<b class="text-danger">{{ trans('dashboard.subscribers.not_verified') }}</b>
@else
<b class="text-success">{{ trans('dashboard.subscribers.verified') }}</b>
@endif
</div>
<div class="col-xs-3 text-right">
<a href="/dashboard/subscribers/{{ $subscriber->id }}/delete" class="btn btn-danger confirm-action" data-method='DELETE'>{{ trans('forms.delete') }}</a>
</div>
</div>
@endforeach
</div>
</div>
</div>
</div>

View File

@ -10,6 +10,9 @@
@if($show_support)
<p>{!! trans('cachet.powered_by', ['app' => $app_name]) !!}</p>
@endif
<p>
<small><a href="{{ $manage_link }}">{!! trans('cachet.subscriber.email.manage') !!}</a></small>
</p>
<p>
<small><a href="{{ $unsubscribe_link }}">{!! trans('cachet.subscriber.email.unsubscribe') !!}</a></small>
</p>

View File

@ -4,4 +4,6 @@
{!! trans('cachet.powered_by', ['app' => $app_name]) !!}
@endif
{!! trans('cachet.subscriber.email.manage') !!} {{ $manage_link }}
{!! trans('cachet.subscriber.email.unsubscribe') !!} {{ $unsubscribe_link }}

View File

@ -5,22 +5,25 @@
@stop
@section('content')
{!! trans('cachet.subscriber.email.maintenance.html', ['app_name' => $app_name]) !!}
{!! trans('cachet.subscriber.email.maintenance.html', ['app_name' => $app_name]) !!}
<p>{{ $scheduled_at }}</p>
<p>{{ $scheduled_at }}</p>
<p>
{!! $status !!}
</p>
<p>
{!! $status !!}
</p>
<p>
{!! $html_content !!}
</p>
<p>
{!! $html_content !!}
</p>
@if($show_support)
<p>{!! trans('cachet.powered_by', ['app' => $app_name]) !!}</p>
@endif
<p>
<small><a href="{{ $unsubscribe_link }}">{!! trans('cachet.subscriber.email.unsubscribe') !!}</a></small>
</p>
@if($show_support)
<p>{!! trans('cachet.powered_by', ['app' => $app_name]) !!}</p>
@endif
<p>
<small><a href="{{ $manage_link }}">{!! trans('cachet.subscriber.email.manage') !!}</a></small>
</p>
<p>
<small><a href="{{ $unsubscribe_link }}">{!! trans('cachet.subscriber.email.unsubscribe') !!}</a></small>
</p>
@stop

View File

@ -10,4 +10,6 @@
{!! trans('cachet.powered_by', ['app' => $app_name]) !!}
@endif
{!! trans('cachet.subscriber.email.manage') !!} {{ $manage_link }}
{!! trans('cachet.subscriber.email.unsubscribe') !!} {{ $unsubscribe_link }}

View File

@ -5,23 +5,26 @@
@stop
@section('content')
{!! trans('cachet.subscriber.email.incident.html-preheader', ['app_name' => $app_name]) !!}
{!! trans('cachet.subscriber.email.incident.html-preheader', ['app_name' => $app_name]) !!}
<p>
{!! $status !!}
@if($has_component)
({{ $component_name }})
@endif
</p>
<p>
{!! $html_content !!}
</p>
@if($show_support)
<p>{!! trans('cachet.powered_by', ['app' => $app_name]) !!}</p>
<p>
{!! $status !!}
@if($has_component)
({{ $component_name }})
@endif
<p>
<small><a href="{{ $unsubscribe_link }}">{!! trans('cachet.subscriber.email.unsubscribe') !!}</a></small>
</p>
</p>
<p>
{!! $html_content !!}
</p>
@if($show_support)
<p>{!! trans('cachet.powered_by', ['app' => $app_name]) !!}</p>
@endif
<p>
<small><a href="{{ $manage_link }}">{!! trans('cachet.subscriber.email.manage') !!}</a></small>
</p>
<p>
<small><a href="{{ $unsubscribe_link }}">{!! trans('cachet.subscriber.email.unsubscribe') !!}</a></small>
</p>
@stop

View File

@ -11,4 +11,6 @@
{!! trans('cachet.powered_by', ['app' => $app_name]) !!}
@endif
{!! trans('cachet.subscriber.email.manage') !!} {{ $manage_link }}
{!! trans('cachet.subscriber.email.unsuscribe') !!} {{ $unsubscribe_link }}

View File

@ -1,47 +0,0 @@
@extends('layout.master')
@section('content')
<div class="pull-right">
<p><a class="btn btn-success btn-outline" href="/"><i class="ion-home"></i></a></p>
</div>
<div class="clearfix"></div>
@if($app_banner)
<div class="row app-banner">
<div class="col-md-12 text-center">
@if($app_domain)
<a href="{{ $app_domain }}"><img src="data:{{ $app_banner_type }};base64, {{ $app_banner }}" class="banner-image img-responsive"></a>
@else
<img src="data:{{ $app_banner_type }};base64, {{ $app_banner }}" class="banner-image img-responsive">
@endif
</div>
</div>
@endif
@include('dashboard.partials.errors')
<div class="panel panel-meassage">
<div class="panel-heading">
<strong>{{ trans('cachet.signup.title') }}</strong>
</div>
<div class="panel-body">
<form action="{{ route('signup.invite', ['code' => $code]) }}" method="post" class="form">
<input type="hidden" name="_token" value="{{ csrf_token() }}">
<div class="form-group">
<label for="username">{{ trans('cachet.signup.username') }}</label>
<input class="form-control" type="text" name="username" value="{{ $username }}">
</div>
<div class="form-group">
<label for="email">{{ trans('cachet.signup.email') }}</label>
<input class="form-control" type="email" name="email" value="{{ $email }}">
</div>
<div class="form-group">
<label for="password">{{ trans('cachet.signup.password') }}</label>
<input class="form-control" type="password" name="password">
</div>
<button type="submit" class="btn btn-success">{{ trans('forms.signup') }}</button>
</form>
</div>
</div>
@stop

View File

@ -1,26 +0,0 @@
@extends('layout.master')
@section('content')
<div class="pull-right">
<p><a class="btn btn-success btn-outline" href="/"><i class="ion-home"></i></a></p>
</div>
<div class="clearfix"></div>
@include('dashboard.partials.errors')
<div class="panel panel-meassage">
<div class="panel-heading">
<strong>{{ trans('cachet.subscriber.subscribe') }}</strong>
</div>
<div class="panel-body">
<form action="{{ route('subscribe.subscribe', [], false) }}" method="post" class="form">
<input type="hidden" name="_token" value="{{ csrf_token() }}">
<div class="form-group">
<input class="form-control" type="email" name="email">
</div>
<button type="submit" class="btn btn-success">{{ trans('cachet.subscriber.button') }}</button>
</form>
</div>
</div>
@stop

View File

@ -0,0 +1,29 @@
@extends('layout.master')
@section('content')
<div class="pull-right">
<p><a class="btn btn-success btn-outline" href="/"><i class="ion-home"></i></a></p>
</div>
<div class="clearfix"></div>
@include('dashboard.partials.errors')
<div class="panel panel-default">
<div class="panel-heading"><strong>{{ $subscriber->email }}</strong></div>
@if($subscriber->subscriptions->count() > 0)
<div class="panel-body">
<p>{{ trans('cachet.subscriber.manage.my_subscriptions') }}</p>
</div>
<div class="list-group">
@foreach($subscriber->subscriptions as $subscription)
<div class="list-group-item">{{ $subscription->component->name }}</div>
@endforeach
</div>
@else
<div class="panel-body">
<p>{{ trans('cachet.subscriber.manage.no_subscriptions') }}</p>
</div>
@endif
</div>
@stop

View File

@ -0,0 +1,47 @@
@extends('layout.master')
@section('content')
<div class="pull-right">
<p><a class="btn btn-success btn-outline" href="/"><i class="ion-home"></i></a></p>
</div>
<div class="clearfix"></div>
@if($app_banner)
<div class="row app-banner">
<div class="col-md-12 text-center">
@if($app_domain)
<a href="{{ $app_domain }}"><img src="data:{{ $app_banner_type }};base64, {{ $app_banner }}" class="banner-image img-responsive"></a>
@else
<img src="data:{{ $app_banner_type }};base64, {{ $app_banner }}" class="banner-image img-responsive">
@endif
</div>
</div>
@endif
@include('dashboard.partials.errors')
<div class="panel panel-meassage">
<div class="panel-heading">
<strong>{{ trans('cachet.signup.title') }}</strong>
</div>
<div class="panel-body">
<form action="{{ route('signup.invite', ['code' => $code]) }}" method="post" class="form">
<input type="hidden" name="_token" value="{{ csrf_token() }}">
<div class="form-group">
<label for="username">{{ trans('cachet.signup.username') }}</label>
<input class="form-control" type="text" name="username" value="{{ $username }}">
</div>
<div class="form-group">
<label for="email">{{ trans('cachet.signup.email') }}</label>
<input class="form-control" type="email" name="email" value="{{ $email }}">
</div>
<div class="form-group">
<label for="password">{{ trans('cachet.signup.password') }}</label>
<input class="form-control" type="password" name="password">
</div>
<button type="submit" class="btn btn-success">{{ trans('forms.signup') }}</button>
</form>
</div>
</div>
@stop

View File

@ -0,0 +1,26 @@
@extends('layout.master')
@section('content')
<div class="pull-right">
<p><a class="btn btn-success btn-outline" href="/"><i class="ion-home"></i></a></p>
</div>
<div class="clearfix"></div>
@include('dashboard.partials.errors')
<div class="panel panel-meassage">
<div class="panel-heading">
<strong>{{ trans('cachet.subscriber.subscribe') }}</strong>
</div>
<div class="panel-body">
<form action="{{ route('subscribe.subscribe', [], false) }}" method="post" class="form">
<input type="hidden" name="_token" value="{{ csrf_token() }}">
<div class="form-group">
<input class="form-control" type="email" name="email">
</div>
<button type="submit" class="btn btn-success">{{ trans('cachet.subscriber.button') }}</button>
</form>
</div>
</div>
@stop