Merge pull request #1092 from cachethq/invitation-system

Invite system for users
This commit is contained in:
James Brooks
2015-11-09 18:38:40 +00:00
37 changed files with 887 additions and 6 deletions

View File

@@ -0,0 +1,34 @@
<?php
/*
* This file is part of Cachet.
*
* (c) Alt Three Services Limited
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace CachetHQ\Cachet\Commands\Invite;
final class ClaimInviteCommand
{
/**
* The invte to mark as claimed.
*
* @var \CachetHQ\Cachet\Model\Invite
*/
public $invite;
/**
* Create a new claim invite command instance.
*
* @param \CachetHQ\Cachet\Model\Invite $invite
*
* @return void
*/
public function __construct($invite)
{
$this->invite = $invite;
}
}

View File

@@ -0,0 +1,43 @@
<?php
/*
* This file is part of Cachet.
*
* (c) Alt Three Services Limited
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace CachetHQ\Cachet\Commands\User;
final class InviteTeamMemberCommand
{
/**
* The invte emails.
*
* @var string
*/
public $email;
/**
* The validation rules.
*
* @var string[]
*/
public $rules = [
'emails' => 'required|array|email',
];
/**
* Create a new invite team member command instance.
*
* @param array $email
*
* @return void
*/
public function __construct($emails)
{
$this->emails = $emails;
}
}

View File

@@ -0,0 +1,73 @@
<?php
/*
* This file is part of Cachet.
*
* (c) Alt Three Services Limited
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace CachetHQ\Cachet\Commands\User;
final class SignupUserCommand
{
/**
* The user username.
*
* @var string
*/
public $username;
/**
* The user password.
*
* @var string
*/
public $password;
/**
* The user email.
*
* @var string
*/
public $email;
/**
* The user level.
*
* @var int
*/
public $level;
/**
* The validation rules.
*
* @var string[]
*/
public $rules = [
'username' => 'required|string',
'password' => 'string',
'email' => 'required|string|email',
'level' => 'int',
];
/**
* Create a new signup user command instance.
*
* @param string $username
* @param string $password
* @param string $email
* @param int $level
*
* @return void
*/
public function __construct($username, $password, $email, $level)
{
$this->username = $username;
$this->password = $password;
$this->email = $email;
$this->level = $level;
}
}

View File

@@ -0,0 +1,34 @@
<?php
/*
* This file is part of Cachet.
*
* (c) Alt Three Services Limited
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace CachetHQ\Cachet\Events\Invite;
use CachetHQ\Cachet\Models\Invite;
final class InviteWasClaimed
{
/**
* The invite that has been claimed.
*
* @var \CachetHQ\Cachet\Models\Invite
*/
public $invite;
/**
* Create a new invite was claimed event instance.
*
* @return void
*/
public function __construct(Invite $invite)
{
$this->invite = $invite;
}
}

View File

@@ -0,0 +1,34 @@
<?php
/*
* This file is part of Cachet.
*
* (c) Alt Three Services Limited
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace CachetHQ\Cachet\Events\User;
use CachetHQ\Cachet\Models\Invite;
final class UserWasInvitedEvent
{
/**
* The invite that has been added.
*
* @var \CachetHQ\Cachet\Models\Invite
*/
public $invite;
/**
* Create a new user was invite event instance.
*
* @return void
*/
public function __construct(Invite $invite)
{
$this->invite = $invite;
}
}

View File

@@ -0,0 +1,36 @@
<?php
/*
* This file is part of Cachet.
*
* (c) Alt Three Services Limited
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace CachetHQ\Cachet\Handlers\Commands\Invite;
use CachetHQ\Cachet\Commands\Invite\ClaimInviteCommand;
use CachetHQ\Cachet\Events\Invite\InviteWasClaimed;
use Carbon\Carbon;
class ClaimInviteCommandHandler
{
/**
* Handle the claim invite command.
*
* @param \CachetHQ\Cachet\Commands\User\ClaimInviteCommand $command
*
* @return void
*/
public function handle(ClaimInviteCommand $command)
{
$invite = $command->invite;
$invite->claimed_at = Carbon::now();
$invite->save();
event(new InviteWasClaimed($invite));
}
}

View File

@@ -0,0 +1,37 @@
<?php
/*
* This file is part of Cachet.
*
* (c) Alt Three Services Limited
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace CachetHQ\Cachet\Handlers\Commands\User;
use CachetHQ\Cachet\Commands\User\InviteTeamMemberCommand;
use CachetHQ\Cachet\Events\User\UserWasInvitedEvent;
use CachetHQ\Cachet\Models\Invite;
class InviteTeamMemberCommandHandler
{
/**
* Handle the invite team member command.
*
* @param \CachetHQ\Cachet\Commands\User\InviteTeamMemberCommand $command
*
* @return void
*/
public function handle(InviteTeamMemberCommand $command)
{
foreach ($command->emails as $email) {
$invite = Invite::create([
'email' => $email,
]);
event(new UserWasInvitedEvent($invite));
}
}
}

View File

@@ -0,0 +1,40 @@
<?php
/*
* This file is part of Cachet.
*
* (c) Alt Three Services Limited
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace CachetHQ\Cachet\Handlers\Commands\User;
use CachetHQ\Cachet\Commands\User\SignupUserCommand;
use CachetHQ\Cachet\Events\User\UserWasAddedEvent;
use CachetHQ\Cachet\Models\User;
class SignupUserCommandHandler
{
/**
* Handle the signup user command.
*
* @param \CachetHQ\Cachet\Commands\User\SignupUserCommand $command
*
* @return \CachetHQ\Cachet\Models\User
*/
public function handle(SignupUserCommand $command)
{
$user = User::create([
'username' => $command->username,
'password' => $command->password,
'email' => $command->email,
'level' => 2,
]);
event(new UserWasAddedEvent($user));
return $user;
}
}

View File

@@ -0,0 +1,62 @@
<?php
/*
* This file is part of Cachet.
*
* (c) Alt Three Services Limited
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace CachetHQ\Cachet\Handlers\Events\User;
use CachetHQ\Cachet\Events\User\UserWasInvitedEvent;
use Illuminate\Contracts\Mail\MailQueue;
use Illuminate\Mail\Message;
class SendInviteUserEmailHandler
{
/**
* The mailer instance.
*
* @var \Illuminate\Contracts\Mail\MailQueue
*/
protected $mailer;
/**
* Create a new send invite user email handler.
*
* @param \Illuminate\Contracts\Mail\Mailer $mailer
*
* @return void
*/
public function __construct(MailQueue $mailer)
{
$this->mailer = $mailer;
}
/**
* Handle the event.
*
* @param \CachetHQ\Cachet\Events\UserWasInvitedEvent $event
*
* @return void
*/
public function handle(UserWasInvitedEvent $event)
{
$mail = [
'email' => $event->invite->email,
'subject' => 'You have been invited.',
'link' => route('signup.invite', ['code' => $event->invite->code]),
'app_url' => env('APP_URL'),
];
$this->mailer->queue([
'html' => 'emails.users.invite-html',
'text' => 'emails.users.invite-text',
], $mail, function (Message $message) use ($mail) {
$message->to($mail['email'])->subject($mail['subject']);
});
}
}

View File

@@ -13,6 +13,7 @@ namespace CachetHQ\Cachet\Http\Controllers\Dashboard;
use AltThree\Validator\ValidationException;
use CachetHQ\Cachet\Commands\User\AddTeamMemberCommand;
use CachetHQ\Cachet\Commands\User\InviteTeamMemberCommand;
use CachetHQ\Cachet\Commands\User\RemoveUserCommand;
use CachetHQ\Cachet\Models\User;
use GrahamCampbell\Binput\Facades\Binput;
@@ -62,6 +63,17 @@ class TeamController extends Controller
->withPageTitle(trans('dashboard.team.add.title').' - '.trans('dashboard.dashboard'));
}
/**
* Shows the invite team member view.
*
* @return \Illuminate\View\View
*/
public function showInviteTeamMemberView()
{
return View::make('dashboard.team.invite')
->withPageTitle(trans('dashboard.team.invite.title').' - '.trans('dashboard.dashboard'));
}
/**
* Creates a new team member.
*
@@ -111,6 +123,28 @@ class TeamController extends Controller
->withSuccess(sprintf('%s %s', trans('dashboard.notifications.awesome'), trans('dashboard.team.edit.success')));
}
/**
* Creates a new team member.
*
* @return \Illuminate\Http\RedirectResponse
*/
public function postInviteUser()
{
try {
$this->dispatch(new InviteTeamMemberCommand(
array_unique(array_filter((array) Binput::get('emails')))
));
} catch (ValidationException $e) {
return Redirect::route('dashboard.team.invite')
->withInput(Binput::except('password'))
->withTitle(sprintf('%s %s', trans('dashboard.notifications.whoops'), trans('dashboard.team.invite.failure')))
->withErrors($e->getMessageBag());
}
return Redirect::route('dashboard.team.invite')
->withSuccess(sprintf('%s %s', trans('dashboard.notifications.awesome'), trans('dashboard.team.invite.success')));
}
/**
* Delete a user.
*

View File

@@ -0,0 +1,97 @@
<?php
/*
* This file is part of Cachet.
*
* (c) Alt Three Services Limited
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace CachetHQ\Cachet\Http\Controllers;
use AltThree\Validator\ValidationException;
use CachetHQ\Cachet\Commands\Invite\ClaimInviteCommand;
use CachetHQ\Cachet\Commands\User\SignupUserCommand;
use CachetHQ\Cachet\Facades\Setting;
use CachetHQ\Cachet\Models\Invite;
use GrahamCampbell\Binput\Facades\Binput;
use GrahamCampbell\Markdown\Facades\Markdown;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Redirect;
use Illuminate\Support\Facades\View;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class SignupController extends Controller
{
use DispatchesJobs;
/**
* Handle the signup with invite.
*
* @param string|null $code
*
* @return \Illuminate\View\View
*/
public function getSignup($code = null)
{
if (is_null($code)) {
throw new NotFoundHttpException();
}
$invite = Invite::where('code', '=', $code)->first();
if (!$invite || $invite->claimed()) {
throw new BadRequestHttpException();
}
return View::make('signup')
->withPageTitle(Setting::get('app_name'))
->withAboutApp(Markdown::convertToHtml(Setting::get('app_about')))
->withCode($invite->code)
->withUsername(Binput::old('username'))
->withEmail(Binput::old('emai', $invite->email));
}
/**
* Handle the unsubscribe.
*
* @param string|null $code
*
* @return \Illuminate\View\View
*/
public function postSignup($code = null)
{
if (is_null($code)) {
throw new NotFoundHttpException();
}
$invite = Invite::where('code', '=', $code)->first();
if (!$invite || $invite->claimed()) {
throw new BadRequestHttpException();
}
try {
$this->dispatch(new SignupUserCommand(
Binput::get('username'),
Binput::get('password'),
Binput::get('email'),
2
));
} catch (ValidationException $e) {
return Redirect::route('signup.invite', ['code' => $invite->code])
->withInput(Binput::except('password'))
->withTitle(sprintf('%s %s', trans('dashboard.notifications.whoops'), trans('cachet.signup.failure')))
->withErrors($e->getMessageBag());
}
$this->dispatch(new ClaimInviteCommand($invite));
return Redirect::route('status-page')
->withSuccess(sprintf('<strong>%s</strong> %s', trans('dashboard.notifications.awesome'), trans('cachet.signup.success')));
}
}

View File

@@ -202,8 +202,13 @@ class DashboardRoutes
'as' => 'add',
'uses' => 'TeamController@showAddTeamMemberView',
]);
$router->get('invite', [
'as' => 'invite',
'uses' => 'TeamController@showInviteTeamMemberView',
]);
$router->get('{user}', 'TeamController@showTeamMemberView');
$router->post('add', 'TeamController@postAddUser');
$router->post('invite', 'TeamController@postInviteUser');
$router->post('{user}', 'TeamController@postUpdateUser');
$router->delete('{user}/delete', 'TeamController@deleteUser');
});

View File

@@ -0,0 +1,45 @@
<?php
/*
* This file is part of Cachet.
*
* (c) Alt Three Services Limited
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace CachetHQ\Cachet\Http\Routes;
use Illuminate\Contracts\Routing\Registrar;
/**
* This is the signup routes class.
*
* @author Joseph Cohen <joe@alt-three.com>
*/
class SignupRoutes
{
/**
* Define the signup routes.
*
* @param \Illuminate\Contracts\Routing\Registrar $router
*/
public function map(Registrar $router)
{
$router->group([
'middleware' => ['app.hasSetting', 'guest'],
'setting' => 'app_name',
'as' => 'signup.',
], function ($router) {
$router->get('signup/invite/{code}', [
'as' => 'invite',
'uses' => 'SignupController@getSignup',
]);
$router->post('signup/invite/{code}', [
'uses' => 'SignupController@postSignup',
]);
});
}
}

67
app/Models/Invite.php Normal file
View File

@@ -0,0 +1,67 @@
<?php
/*
* This file is part of Cachet.
*
* (c) Alt Three Services Limited
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace CachetHQ\Cachet\Models;
use Illuminate\Database\Eloquent\Model;
class Invite extends Model
{
/**
* The attributes that should be casted to native types.
*
* @var string[]
*/
protected $casts = [
'email' => 'string',
];
/**
* The fillable properties.
*
* @var string[]
*/
protected $fillable = ['email'];
/**
* Overrides the models boot method.
*/
public static function boot()
{
parent::boot();
self::creating(function ($invite) {
if (!$invite->code) {
$invite->code = self::generateInviteCode();
}
});
}
/**
* Returns an invite code.
*
* @return string
*/
public static function generateInviteCode()
{
return str_random(20);
}
/**
* Determines if the invite was claimed.
*
* @return bool
*/
public function claimed()
{
return !is_null($this->claimed_at);
}
}

View File

@@ -33,8 +33,8 @@ class ComposerServiceProvider extends ServiceProvider
$factory->composer('*', AppComposer::class);
$factory->composer('*', CurrentUserComposer::class);
$factory->composer(['index'], MetricsComposer::class);
$factory->composer(['index', 'incident', 'subscribe'], StatusPageComposer::class);
$factory->composer(['index', 'incident', 'subscribe', 'dashboard.settings.theme'], ThemeComposer::class);
$factory->composer(['index', 'incident', 'subscribe', 'signup'], StatusPageComposer::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

@@ -33,5 +33,8 @@ class EventServiceProvider extends ServiceProvider
'CachetHQ\Cachet\Events\User\UserWasAddedEvent' => [
//
],
'CachetHQ\Cachet\Events\User\UserWasInvitedEvent' => [
'CachetHQ\Cachet\Handlers\Events\User\SendInviteUserEmailHandler',
],
];
}

View File

@@ -0,0 +1,44 @@
<?php
/*
* This file is part of Cachet.
*
* (c) Alt Three Services Limited
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
class CreateInvitesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('invites', function (Blueprint $table) {
$table->engine = 'InnoDB';
$table->increments('id');
$table->string('code')->unique();
$table->string('email');
$table->timestamp('claimed_at')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::drop('invites');
}
}

View File

@@ -153,6 +153,10 @@ return [
'2fa' => [
'help' => 'Brug Two-Factor login for større sikkerhed på din konto. Du skal så nok installere <a href="https://support.google.com/accounts/answer/1066447?hl=en">Google Authenticator</a> eller lignende på din mobile enked for at kunne logge ind med nøgler fra appen.',
],
'team' => [
'description' => 'Invite your team members by entering their email addresses here.',
'email' => 'Email #:id',
],
],
// Buttons

View File

@@ -138,6 +138,10 @@ return [
'2fa' => [
'help' => 'Die Zwei-Faktor-Authentifizierung erhöht die Sicherheit Ihres Kontos. Sie benötigen <a href="https://support.google.com/accounts/answer/1066447?hl=en">Google Authenticator</a> oder eine ähnliche App auf Ihrem Mobilgerät. Beim Anmelden werden sie aufgefordert, einen Token einzugeben, der von der App generiert wird.',
],
'team' => [
'description' => 'Invite your team members by entering their email addresses here.',
'email' => 'Email #:id',
],
],
// Buttons

View File

@@ -87,6 +87,25 @@ return [
],
],
'users' => [
'email' => [
'invite' => [
'text' => "You have been invited to the team :app_name status page, to sign up follow the next link.\n:link\nThank you, :app_name",
'html-preheader' => 'You have been invited to the team :app_name.',
'html' => '<p>You have been invited to the team :app_name status page, to sign up follow the next link.</p><p><a href=":link">:link</a></p><p>Thank you, :app_name</p>',
],
],
],
'signup' => [
'title' => 'Sign Up',
'username' => 'Username',
'email' => 'Email',
'password' => 'Password',
'success' => 'Your account has been created.',
'failure' => 'Something went wrong with the signup.',
],
// Other
'powered_by' => ':app Status Page is powered by <a href="https://cachethq.io" class="links">Cachet</a>.',
'about_this_site' => 'About This Site',

View File

@@ -142,7 +142,7 @@ return [
'add' => [
'title' => 'Add a New Team Member',
'success' => 'Team member added.',
'failure' => 'Something went wrong with the component.',
'failure' => 'Something went wrong with the user.',
],
'edit' => [
'title' => 'Update Profile',
@@ -153,6 +153,11 @@ return [
'success' => 'User deleted.',
'failure' => 'Something went wrong when deleting this user.',
],
'invite' => [
'title' => 'Invite a New Team Member',
'success' => 'The users invited.',
'failure' => 'Something went wrong with the invite.',
],
],
// Settings

View File

@@ -153,6 +153,10 @@ return [
'2fa' => [
'help' => 'Enabling two factor authentication increases security of your account. You will need to download <a href="https://support.google.com/accounts/answer/1066447?hl=en">Google Authenticator</a> or a similar app on to your mobile device. When you login you will be asked to provide a token generated by the app.',
],
'team' => [
'description' => 'Invite your team members by entering their email addresses here.',
'email' => 'Email #:id',
],
],
// Buttons
@@ -165,6 +169,8 @@ return [
'submit' => 'Submit',
'cancel' => 'Cancel',
'remove' => 'Remove',
'invite' => 'Invite',
'signup' => 'Sign Up',
// Other
'optional' => '* Optional',

View File

@@ -130,6 +130,10 @@ return [
'2fa' => [
'help' => 'Habilitar autenticación de dos pasos aumenta la seguridad de tu cuenta. Necesitarás descargar <a href="https://support.google.com/accounts/answer/1066447?hl=en"> Google Authenticator</a> o una aplicación similar en tu dispositivo móvil. Al iniciar sesión te pedirá proporcionar un token generado por la aplicación.',
],
'team' => [
'description' => 'Invite your team members by entering their email addresses here.',
'email' => 'Email #:id',
],
],
// Buttons

View File

@@ -138,6 +138,10 @@ return [
'2fa' => [
'help' => 'Habilitante authentification à deux facteurs augmente la sécurité de votre compte. Vous aurez besoin de télécharger <a href="https://support.google.com/accounts/answer/1066447?hl=en"> Google Authenticator</a> ou une application similaire sur votre appareil mobile. Lorsque vous vous connectez vous sera demandé de fournir un jeton généré par l\'application.',
],
'team' => [
'description' => 'Invite your team members by entering their email addresses here.',
'email' => 'Email #:id',
],
],
// Buttons

View File

@@ -138,6 +138,10 @@ return [
'2fa' => [
'help' => 'Mengaktifkan otentikasi dua faktor akan memperkuat keamanan akun anda. Anda perlu mengunduh <a href="https://support.google.com/accounts/answer/1066447?hl=en">Google Authenticator</a> atau app sejenis di gadget anda. Saat login anda akan ditanyakan untuk mengisi token yang dibuat oleh app tersebut.',
],
'team' => [
'description' => 'Invite your team members by entering their email addresses here.',
'email' => 'Email #:id',
],
],
// Buttons

View File

@@ -144,6 +144,10 @@ return [
'2fa' => [
'help' => 'L\'abilitazione della verifica in 2 passaggi aumenta la sicurezza del tuo account. Sarà necessario scaricare <a href="https://support.google.com/accounts/answer/1066447?hl=it">Google Authenticator</a> o un\'applicazione simile sul tuo dispositivo mobile. Quando accederai, ti verrà chiesto di fornire un token generato dall\'app.',
],
'team' => [
'description' => 'Invite your team members by entering their email addresses here.',
'email' => 'Email #:id',
],
],
// Buttons

View File

@@ -139,6 +139,10 @@ return [
'2fa' => [
'help' => '2단계 인증을 활성화하면 계정 보안이 강화됩니다. <a href="https://support.google.com/accounts/answer/1066447?hl=en">Google Authenticator</a> 또는 유사한 앱을 모바일 기기에 다운로드 받아야 합니다. 로그인 할 때, 해당 앱에서 생성된 토큰을 입력해야합니다.',
],
'team' => [
'description' => 'Invite your team members by entering their email addresses here.',
'email' => 'Email #:id',
],
],
// Buttons

View File

@@ -138,6 +138,10 @@ return [
'2fa' => [
'help' => 'Het inschakelen van two-factor authenticatie verhoogt de veiligheid van uw account. U zult een applicatie zoals <a href="https://support.google.com/accounts/answer/1066447?hl=en">Google Authenticator</a> of een vergelijkbare applicatie moeten downloaden op uw mobiele apparaat. Wanneer u inlogt wordt u gevraagd om een token in te voeren welke door de applicatie wordt gegenereerd.',
],
'team' => [
'description' => 'Invite your team members by entering their email addresses here.',
'email' => 'Email #:id',
],
],
// Buttons

View File

@@ -132,6 +132,10 @@ return [
'2fa' => [
'help' => 'Aktywacja dwuetapowej autentykacji zwiększą bezpieczeństwo twojego konta. Musisz ściągnąć <a href="https://support.google.com/accounts/answer/1066447?hl=en">Google Authenticator</a> lub podobną aplikację na swój telefon. Przy logowaniu będziesz proszony o podanie kodu wygenerowanego przez tą aplikację.',
],
'team' => [
'description' => 'Invite your team members by entering their email addresses here.',
'email' => 'Email #:id',
],
],
// Buttons

View File

@@ -153,6 +153,10 @@ return [
'2fa' => [
'help' => 'Ativar a autenticação de dois fatores aumenta a segurança de sua conta. Você vai precisar baixar <a href="https://support.google.com/accounts/answer/1066447?hl=en"> Google Authenticator</a> ou um app similar em seu dispositivo móvel. Quando você entrar, será solicitado um token gerado pelo app.',
],
'team' => [
'description' => 'Invite your team members by entering their email addresses here.',
'email' => 'Email #:id',
],
],
// Buttons

View File

@@ -139,6 +139,10 @@ return [
'2fa' => [
'help' => 'Включение двухфакторной аутентификации увеличивает безопасность вашей учетной записи. Вам понадобится скачать <a href="https://support.google.com/accounts/answer/1066447?hl=ru">Google Authenticator</a> или аналогичное приложение на свой смартфон. Когда в следующий раз вы войдете в панель управления, вам понадобится токен, выданный этим приложением.',
],
'team' => [
'description' => 'Invite your team members by entering their email addresses here.',
'email' => 'Email #:id',
],
],
// Buttons

View File

@@ -139,6 +139,10 @@ return [
'2fa' => [
'help' => '启用双因素身份验证会增加您的帐户安全。您将需要下载 <a href="https://support.google.com/accounts/answer/1066447?hl=en">Google Authenticator</a> 或类似的应用到您的移动设备。当您登录时将会要求您提供由应用程序生成的一个短码。',
],
'team' => [
'description' => 'Invite your team members by entering their email addresses here.',
'email' => 'Email #:id',
],
],
// Buttons

View File

@@ -9,9 +9,14 @@
<i class="icon icon ion-android-alert"></i> {{ trans('dashboard.team.team') }}
</span>
@if($current_user->isAdmin)
<a class="btn btn-sm btn-success pull-right" href="{{ route('dashboard.team.add') }}">
{{ trans('dashboard.team.add.title') }}
</a>
<div class="button-group pull-right">
<a class="btn btn-sm btn-success" href="{{ route('dashboard.team.invite') }}">
{{ trans('dashboard.team.invite.title') }}
</a>
<a class="btn btn-sm btn-success" href="{{ route('dashboard.team.add') }}">
{{ trans('dashboard.team.add.title') }}
</a>
</div>
@endif
<div class="clearfix"></div>
</div>

View File

@@ -0,0 +1,44 @@
@extends('layout.dashboard')
@section('content')
<div class="header">
<div class="sidebar-toggler visible-xs">
<i class="icon ion-navicon"></i>
</div>
<span class="uppercase">
<i class="icon ion-person"></i> {{ trans('dashboard.team.team') }}
</span>
</div>
<div class="content-wrapper">
<div class="row">
<div class="col-sm-12">
@include('dashboard.partials.errors')
<form name="UserForm" class="form-vertical" role="form" action="/dashboard/team/invite" method="POST">
<input type="hidden" name="_token" value="{{ csrf_token() }}">
<fieldset>
<div class="form-group">
<label>{{ trans('forms.user.team.description') }}</label>
<input type="email" class="form-control" name="emails[]" placeholder="{{ trans('forms.user.team.email', ['id' => 1]) }}" required>
</div>
<div class="form-group">
<input type="email" class="form-control" name="emails[]" placeholder="{{ trans('forms.user.team.email', ['id' => 2]) }}">
</div>
<div class="form-group">
<input type="email" class="form-control" name="emails[]" placeholder="{{ trans('forms.user.team.email', ['id' => 3]) }}">
</div>
<div class="form-group">
<input type="email" class="form-control" name="emails[]" placeholder="{{ trans('forms.user.team.email', ['id' => 4]) }}">
</div>
<div class="form-group">
<input type="email" class="form-control" name="emails[]" placeholder="{{ trans('forms.user.team.email', ['id' => 5]) }}">
</div>
</fieldset>
<div class="form-group">
<button type="submit" class="btn btn-success">{{ trans('forms.invite') }}</button>
</div>
</form>
</div>
</div>
</div>
@stop

View File

@@ -0,0 +1,13 @@
@extends('layout.emails')
@section('preheader')
{!! trans('cachet.users.email.invite.html-preheader', ['app_name' => Setting::get('app_name')]) !!}
@stop
@section('content')
{!! trans('cachet.users.email.invite.html', ['app_name' => Setting::get('app_name'), 'link' => $link]) !!}
@if(Setting::get('show_support'))
<p>{!! trans('cachet.powered_by', ['app' => Setting::get('app_name')]) !!}</p>
@endif
@stop

View File

@@ -0,0 +1,5 @@
{{ trans('cachet.users.email.invite.text', ['app_name' => Setting::get('app_name'), 'link' => $link]) }}
@if(Setting::get('show_support'))
{!! trans('cachet.powered_by', ['app' => Setting::get('app_name')]) !!}
@endif

View File

@@ -0,0 +1,48 @@
@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($bannerImage = Setting::get('app_banner'))
<div class="row app-banner">
<div class="col-md-12 text-center">
<?php $bannerType = Setting::get('app_banner_type') ?>
@if($app_url = Setting::get('app_domain'))
<a href="{{ $app_url }}"><img src="data:{{ $bannerType }};base64, {{ $bannerImage}}" class="banner-image img-responsive"></a>
@else
<img src="data:{{ $bannerType }};base64, {{ $bannerImage}}" 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