updated tokens and emails
This commit is contained in:
@@ -4,11 +4,9 @@ namespace App\Http\Controllers;
|
||||
|
||||
use App\Helpers;
|
||||
use App\Jobs\SendEmail;
|
||||
use App\Mail\EmailUpdateLink;
|
||||
use App\Mail\RegisterLink;
|
||||
use App\Mail\UserEmailUpdateRequest;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rule;
|
||||
@@ -49,7 +47,7 @@ class AccountController extends Controller
|
||||
$validator = Validator::make($request->all(), [
|
||||
'firstname' => 'required',
|
||||
'surname' => 'required',
|
||||
'email' => ['required', 'email', Rule::unique('users')->ignore($user->id)],
|
||||
'email' => ['required', 'email', 'unique:users,email,' . $user->id],
|
||||
'phone' => 'required',
|
||||
|
||||
'home_address' => 'required_with:home_city,home_postcode,home_country,home_state',
|
||||
@@ -92,20 +90,18 @@ class AccountController extends Controller
|
||||
$newEmail = $userData['email'];
|
||||
unset($userData['email']);
|
||||
|
||||
if ($user->email !== $newEmail) {
|
||||
if(User::where('email', $request->get('email'))->exists()) {
|
||||
$validator->errors()->add('email', __('validation.custom_messages.email_exists'));
|
||||
return redirect()->back()->withErrors($validator)->withInput();
|
||||
}
|
||||
if (strtolower($user->email) !== strtolower($newEmail)) {
|
||||
$user->tokens()->where('type', 'email-update')->delete();
|
||||
|
||||
$token = Str::random(60);
|
||||
$user->emailUpdate()->delete();
|
||||
$emailUpdate = $user->emailUpdate()->create([
|
||||
$token = $user->tokens()->create([
|
||||
'type' => 'email-update',
|
||||
'data' => [
|
||||
'email' => $newEmail,
|
||||
'token' => $token
|
||||
],
|
||||
'expires_at' => now()->addMinutes(30),
|
||||
]);
|
||||
|
||||
dispatch(new SendEmail($user->email, new EmailUpdateLink($token, $user->getName(), $user->email, $newEmail)))->onQueue('mail');
|
||||
dispatch(new SendEmail($user->email, new UserEmailUpdateRequest($token->id, $user->email, $newEmail)))->onQueue('mail');
|
||||
}
|
||||
|
||||
$userData['subscribed'] = ($request->get('subscribed', false) === 'on');
|
||||
|
||||
@@ -3,57 +3,89 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Jobs\SendEmail;
|
||||
use App\Mail\LoginLink;
|
||||
use App\Mail\RegisterLink;
|
||||
use App\Models\EmailSubscriptions;
|
||||
use App\Models\EmailUpdate;
|
||||
use App\Mail\UserEmailUpdateConfirm;
|
||||
use App\Mail\UserLogin;
|
||||
use App\Mail\UserRegister;
|
||||
use App\Mail\UserWelcome;
|
||||
use App\Models\Token;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class AuthController extends Controller
|
||||
{
|
||||
public function showLogin(Request $request) {
|
||||
/**
|
||||
* Show the login form or if token present, process the login
|
||||
*
|
||||
* @param Request $request
|
||||
* @return View|RedirectResponse
|
||||
*/
|
||||
public function showLogin(Request $request): View|RedirectResponse
|
||||
{
|
||||
if (auth()->check()) {
|
||||
// return redirect()->route('dashboard');
|
||||
return redirect()->action([HomeController::class, 'index']);
|
||||
}
|
||||
|
||||
$token = $request->query('token');
|
||||
if ($token) {
|
||||
return $this->tokenLogin($token);
|
||||
return $this->LoginByToken($token);
|
||||
}
|
||||
|
||||
return view('auth.login');
|
||||
}
|
||||
|
||||
public function tokenLogin($token)
|
||||
/**
|
||||
* Process the login form
|
||||
*
|
||||
* @param Request $request
|
||||
* @return View|RedirectResponse
|
||||
*/
|
||||
public function postLogin(Request $request): View|RedirectResponse
|
||||
{
|
||||
$loginToken = DB::table('login_tokens')->where('token', $token)->first();
|
||||
$request->validate([
|
||||
'email' => 'required|email',
|
||||
], [
|
||||
'email.required' => __('validation.custom_messages.email_required'),
|
||||
'email.email' => __('validation.custom_messages.email_invalid'),
|
||||
]);
|
||||
|
||||
if ($loginToken) {
|
||||
$user = User::where('email', $loginToken->email)->first();
|
||||
$intended_url = $loginToken->intended_url;
|
||||
$user = User::where('email', $request->email)->whereNotNull('email_verified_at')->first();
|
||||
if($user) {
|
||||
$token = $user->tokens()->create([
|
||||
'type' => 'login',
|
||||
'data' => ['url' => session()->pull('url.intended', null)],
|
||||
]);
|
||||
|
||||
DB::table('login_tokens')->where('token', $token)->delete();
|
||||
|
||||
if ($user) {
|
||||
Auth::login($user);
|
||||
|
||||
$user->markEmailAsVerified();
|
||||
DB::table('login_tokens')->where('token', $token)->delete();
|
||||
|
||||
session()->flash('message', 'You have been logged in');
|
||||
session()->flash('message-title', 'Logged in');
|
||||
session()->flash('message-type', 'success');
|
||||
|
||||
if($intended_url) {
|
||||
return redirect($intended_url);
|
||||
dispatch(new SendEmail($user->email, new UserLogin($token->id, $user->getName(), $user->email)))->onQueue('mail');
|
||||
return view('auth.login-link');
|
||||
}
|
||||
|
||||
return redirect()->action([HomeController::class, 'index']);
|
||||
session()->flash('status', 'not-found');
|
||||
return view('auth.login');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Process the login by token
|
||||
*
|
||||
* @param string $tokenStr
|
||||
* @return View|RedirectResponse
|
||||
*/
|
||||
public function loginByToken(string $tokenStr): View|RedirectResponse
|
||||
{
|
||||
$token = Token::where('id', $tokenStr)
|
||||
->where('type', 'login')
|
||||
->where('expires_at', '>', now())
|
||||
->first();
|
||||
|
||||
if ($token) {
|
||||
$user = $token->user;
|
||||
if($user) {
|
||||
$token->delete();
|
||||
return $this->loginByUser($user, $token->data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,27 +95,40 @@ class AuthController extends Controller
|
||||
return view('auth.login');
|
||||
}
|
||||
|
||||
public function postLogin(Request $request) {
|
||||
$request->validate([
|
||||
'email' => 'required|email',
|
||||
], [
|
||||
'email.required' => __('validation.custom_messages.email_required'),
|
||||
'email.email' => __('validation.custom_messages.email_invalid'),
|
||||
]);
|
||||
|
||||
$user = User::where('email', $request->email)->first();
|
||||
if($user) {
|
||||
$token = $user->createLoginToken(session()->pull('url.intended', null));
|
||||
dispatch(new SendEmail($user->email, new LoginLink($token, $user->getName(), $user->email)))->onQueue('mail');
|
||||
|
||||
return view('auth.login-link');
|
||||
/**
|
||||
* Process the login by user
|
||||
*
|
||||
* @param User $user
|
||||
* @param array $data
|
||||
* @return RedirectResponse
|
||||
*/
|
||||
public function loginByUser(User $user, array $data = [])
|
||||
{
|
||||
$url = null;
|
||||
if($data && isset($data->url) && $data->url) {
|
||||
$url = $data->url;
|
||||
}
|
||||
|
||||
session()->flash('status', 'not-found');
|
||||
return view('auth.login');
|
||||
Auth::login($user);
|
||||
|
||||
session()->flash('message', 'You have been logged in');
|
||||
session()->flash('message-title', 'Logged in');
|
||||
session()->flash('message-type', 'success');
|
||||
|
||||
if($url) {
|
||||
return redirect($url);
|
||||
}
|
||||
|
||||
public function logout() {
|
||||
return redirect()->action([HomeController::class, 'index']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the user logout
|
||||
*
|
||||
* @return RedirectResponse
|
||||
*/
|
||||
public function logout(): RedirectResponse
|
||||
{
|
||||
auth()->logout();
|
||||
|
||||
session()->flash('message', 'You have been logged out');
|
||||
@@ -92,15 +137,57 @@ class AuthController extends Controller
|
||||
return redirect()->route('index');
|
||||
}
|
||||
|
||||
public function showRegister(Request $request) {
|
||||
/**
|
||||
* Show the registration form or if token present, process the registration
|
||||
*
|
||||
* @param Request $request
|
||||
* @return View|RedirectResponse
|
||||
*/
|
||||
public function showRegister(Request $request): View|RedirectResponse
|
||||
{
|
||||
if (auth()->check()) {
|
||||
return redirect()->route('index');
|
||||
}
|
||||
|
||||
$tokenStr = $request->query('token');
|
||||
if ($tokenStr) {
|
||||
$token = Token::where('id', $tokenStr)
|
||||
->where('type', 'register')
|
||||
->where('expires_at', '>', now())
|
||||
->first();
|
||||
|
||||
if ($token) {
|
||||
$user = $token->user;
|
||||
if ($user) {
|
||||
$user->email_verified_at = now();
|
||||
$user->save();
|
||||
|
||||
$user->tokens()->where('type', 'register')->delete();
|
||||
|
||||
dispatch(new SendEmail($user->email, new UserWelcome($user->email)))->onQueue('mail');
|
||||
|
||||
$this->loginByUser($user);
|
||||
return redirect()->route('index');
|
||||
}
|
||||
}
|
||||
|
||||
session()->flash('message', 'That token has expired or is invalid');
|
||||
session()->flash('message-title', 'Registration failed');
|
||||
session()->flash('message-type', 'danger');
|
||||
}
|
||||
|
||||
|
||||
return view('auth.register');
|
||||
}
|
||||
|
||||
public function postRegister(Request $request) {
|
||||
/**
|
||||
* Process the registration form
|
||||
*
|
||||
* @param Request $request
|
||||
* @return View|RedirectResponse
|
||||
*/
|
||||
public function postRegister(Request $request): View|RedirectResponse
|
||||
{
|
||||
$request->validate([
|
||||
'email' => 'required|email',
|
||||
], [
|
||||
@@ -119,46 +206,65 @@ class AuthController extends Controller
|
||||
]);
|
||||
}
|
||||
} else if($passHoneypot) {
|
||||
$firstname = explode('@', $request->email)[0];
|
||||
|
||||
$user = User::create([
|
||||
'firstname' => $firstname,
|
||||
'email' => $request->email,
|
||||
]);
|
||||
|
||||
EmailUpdate::where('email', $request->email)->delete();
|
||||
}
|
||||
|
||||
if($passHoneypot) {
|
||||
Log::channel('honeypot')->info('Valid key used for registration using email: ' . $request->email . ', ip address: ' . $request->ip() . ', user agent: ' . $request->userAgent());
|
||||
$token = $user->createLoginToken(session()->pull('url.intended', null));
|
||||
dispatch(new SendEmail($user->email, new RegisterLink($token, $user->getName(), $user->email)))->onQueue('mail');
|
||||
$user->tokens()->where('type', 'register')->delete();
|
||||
$token = $user->tokens()->create([
|
||||
'type' => 'register',
|
||||
'data' => ['url' => session()->pull('url.intended', null)],
|
||||
]);
|
||||
|
||||
dispatch(new SendEmail($user->email, new UserRegister($token->id, $user->email)))->onQueue('mail');
|
||||
} else {
|
||||
Log::channel('honeypot')->info('Invalid key used for registration using email: ' . $request->email . ', ip address: ' . $request->ip() . ', user agent: ' . $request->userAgent() . ', key: ' . $key);
|
||||
}
|
||||
|
||||
return view('auth.login-link');
|
||||
return view('auth.register-link');
|
||||
}
|
||||
|
||||
public function updateEmail(Request $request)
|
||||
/**
|
||||
* Confirm the user email update.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return RedirectResponse
|
||||
*/
|
||||
public function updateEmail(Request $request): RedirectResponse
|
||||
{
|
||||
$token = $request->query('token');
|
||||
$emailUpdate = EmailUpdate::where('token', $token)->first();
|
||||
if($emailUpdate && $emailUpdate->user) {
|
||||
$emailUpdate->user->email = $emailUpdate->email;
|
||||
$emailUpdate->user->email_verified_at = now();
|
||||
$emailUpdate->user->save();
|
||||
$emailUpdate->delete();
|
||||
$tokenStr = $request->query('token');
|
||||
|
||||
$token = Token::where('id', $tokenStr)
|
||||
->where('type', 'email-update')
|
||||
->where('expires_at', '>', now())
|
||||
->first();
|
||||
|
||||
if($token && $token->user) {
|
||||
if($token->data && isset($token->data['email'])) {
|
||||
$user = $token->user;
|
||||
$user->email = $token->data['email'];
|
||||
$user->email_verified_at = now();
|
||||
$user->save();
|
||||
|
||||
$user->tokens()->where('type', 'email-update')->delete();
|
||||
|
||||
session()->flash('message', 'Your email has been updated');
|
||||
session()->flash('message-title', 'Email updated');
|
||||
session()->flash('message-type', 'success');
|
||||
|
||||
dispatch(new SendEmail($user->email, new UserEmailUpdateConfirm($user->email)))->onQueue('mail');
|
||||
|
||||
return redirect()->route('index');
|
||||
}
|
||||
}
|
||||
|
||||
session()->flash('message', 'That token has expired or is invalid');
|
||||
session()->flash('message-title', 'Email update failed');
|
||||
session()->flash('message-type', 'danger');
|
||||
|
||||
return redirect()->route('index');
|
||||
}
|
||||
}
|
||||
|
||||
30
app/Mail/UserEmailUpdateConfirm.php
Normal file
30
app/Mail/UserEmailUpdateConfirm.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class UserEmailUpdateConfirm extends Mailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
public $email;
|
||||
|
||||
public function __construct($email)
|
||||
{
|
||||
$this->email = $email;
|
||||
}
|
||||
|
||||
public function build()
|
||||
{
|
||||
return $this
|
||||
->subject('Your STEMMechanics account has been updated 👍')
|
||||
->markdown('emails.email-update-confirm')
|
||||
->with([
|
||||
'email' => $this->email,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -7,19 +7,17 @@ use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class EmailUpdateLink extends Mailable
|
||||
class UserEmailUpdateRequest extends Mailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
public $token;
|
||||
public $username;
|
||||
public $email;
|
||||
public $newEmail;
|
||||
|
||||
public function __construct($token, $username, $email, $newEmail)
|
||||
public function __construct($token, $email, $newEmail)
|
||||
{
|
||||
$this->token = $token;
|
||||
$this->username = $username;
|
||||
$this->email = $email;
|
||||
$this->newEmail = $newEmail;
|
||||
}
|
||||
@@ -27,11 +25,10 @@ class EmailUpdateLink extends Mailable
|
||||
public function build()
|
||||
{
|
||||
return $this
|
||||
->subject('Confirm new email address')
|
||||
->markdown('emails.change-email-link')
|
||||
->subject('Almost There! Confirm Your New Email Address 👍')
|
||||
->markdown('emails.email-update-request')
|
||||
->with([
|
||||
'token' => $this->token,
|
||||
'username' => $this->username,
|
||||
'update_url' => route('update.email', ['token' => $this->token]),
|
||||
'email' => $this->email,
|
||||
'newEmail' => $this->newEmail,
|
||||
]);
|
||||
@@ -7,7 +7,7 @@ use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class LoginLink extends Mailable
|
||||
class UserLogin extends Mailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
@@ -25,10 +25,10 @@ class LoginLink extends Mailable
|
||||
public function build()
|
||||
{
|
||||
return $this
|
||||
->subject('Here\'s your login link')
|
||||
->markdown('emails.login-link')
|
||||
->subject('Here\'s your login link 🤫')
|
||||
->markdown('emails.login')
|
||||
->with([
|
||||
'token' => $this->token,
|
||||
'login_url' => route('login', ['token' => $this->token]),
|
||||
'username' => $this->username,
|
||||
'email' => $this->email,
|
||||
]);
|
||||
@@ -7,29 +7,26 @@ use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class RegisterLink extends Mailable
|
||||
class UserRegister extends Mailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
public $token;
|
||||
public $username;
|
||||
public $email;
|
||||
|
||||
public function __construct($token, $username, $email)
|
||||
public function __construct($token, $email)
|
||||
{
|
||||
$this->token = $token;
|
||||
$this->username = $username;
|
||||
$this->email = $email;
|
||||
}
|
||||
|
||||
public function build()
|
||||
{
|
||||
return $this
|
||||
->subject('Here\'s your registration link')
|
||||
->markdown('emails.register-link')
|
||||
->subject('Almost There! Just One More Step to Join Us 🚀')
|
||||
->markdown('emails.register')
|
||||
->with([
|
||||
'token' => $this->token,
|
||||
'username' => $this->username,
|
||||
'register_url' => route('register', ['token' => $this->token]),
|
||||
'email' => $this->email,
|
||||
]);
|
||||
}
|
||||
30
app/Mail/UserWelcome.php
Normal file
30
app/Mail/UserWelcome.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class UserWelcome extends Mailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
public $email;
|
||||
|
||||
public function __construct($email)
|
||||
{
|
||||
$this->email = $email;
|
||||
}
|
||||
|
||||
public function build()
|
||||
{
|
||||
return $this
|
||||
->subject('Welcome to STEMMechanics 🌟')
|
||||
->markdown('emails.welcome')
|
||||
->with([
|
||||
'email' => $this->email,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class EmailUpdate extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'email',
|
||||
'token'
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the user that owns the email update.
|
||||
*/
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
87
app/Models/Token.php
Normal file
87
app/Models/Token.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class Token extends Model
|
||||
{
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'type',
|
||||
'data',
|
||||
'expires_at',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $casts = [
|
||||
'expires_at' => 'datetime',
|
||||
'data' => 'array',
|
||||
];
|
||||
|
||||
/**
|
||||
* Indicates if the model should be timestamped.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $timestamps = false;
|
||||
|
||||
/**
|
||||
* The primary key for the model is incrementing.
|
||||
*
|
||||
* @var bool $incrementing
|
||||
*/
|
||||
public $incrementing = false;
|
||||
|
||||
/**
|
||||
* The primary key type for the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $keyType = 'string';
|
||||
|
||||
/**
|
||||
* The "booted" method of the model.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function boot()
|
||||
{
|
||||
parent::boot();
|
||||
|
||||
static::creating(function ($model) {
|
||||
if (empty($model->{$model->getKeyName()}) === true) {
|
||||
do {
|
||||
$newToken = Str::random(48);
|
||||
} while (self::where($model->getKeyName(), $newToken)->exists());
|
||||
|
||||
$model->{$model->getKeyName()} = $newToken;
|
||||
}
|
||||
|
||||
if (empty($model->expires_at) === true) {
|
||||
$model->expires_at = now()->addMinutes(10);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user that the token belongs to.
|
||||
*
|
||||
* @return BelongsTo
|
||||
*/
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
@@ -2,17 +2,12 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Mail\LoginLink;
|
||||
use App\Traits\UUID;
|
||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Str;
|
||||
use PharIo\Manifest\Email;
|
||||
|
||||
class User extends Authenticatable implements MustVerifyEmail
|
||||
{
|
||||
@@ -110,34 +105,21 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
});
|
||||
}
|
||||
|
||||
public function createLoginToken($redirect = null)
|
||||
/**
|
||||
* Get the tokens for the user.
|
||||
*
|
||||
* @return HasMany
|
||||
*/
|
||||
public function tokens(): HasMany
|
||||
{
|
||||
// Generate a unique token
|
||||
$token = Str::random(60);
|
||||
|
||||
// Store the token in the database
|
||||
DB::table('login_tokens')->insert([
|
||||
'email' => $this->email,
|
||||
'token' => $token,
|
||||
'intended_url' => $redirect,
|
||||
]);
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
public function softDelete()
|
||||
{
|
||||
foreach ($this->fillable as $field) {
|
||||
if ($field === 'email_verified_at') {
|
||||
$this->email_verified_at = null;
|
||||
} else if ($field !== 'email') {
|
||||
$this->{$field} = '';
|
||||
}
|
||||
}
|
||||
|
||||
$this->save();
|
||||
return $this->hasMany(Token::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the calculated name of the user.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
$name = '';
|
||||
@@ -183,14 +165,11 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
}
|
||||
}
|
||||
|
||||
public function emailUpdate()
|
||||
{
|
||||
return $this->hasOne(EmailUpdate::class);
|
||||
}
|
||||
|
||||
public function getEmailUpdatePendingAttribute()
|
||||
{
|
||||
return $this->emailUpdate()->exists();
|
||||
$emailUpdate = $this->tokens()->where('type', 'email-update')->where('expires_at', '>', now())->first();
|
||||
return $emailUpdate ? $emailUpdate->data['email'] : null;
|
||||
}
|
||||
|
||||
public function isAdmin(): bool
|
||||
|
||||
@@ -69,6 +69,7 @@ return new class extends Migration
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('users');
|
||||
Schema::dropIfExists('login_tokens');
|
||||
Schema::dropIfExists('password_reset_tokens');
|
||||
Schema::dropIfExists('sessions');
|
||||
}
|
||||
|
||||
56
database/migrations/2024_05_06_155300_merge_tokens_table.php
Normal file
56
database/migrations/2024_05_06_155300_merge_tokens_table.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::dropIfExists('password_reset_tokens');
|
||||
Schema::dropIfExists('login_tokens');
|
||||
Schema::dropIfExists('email_updates');
|
||||
|
||||
Schema::create('tokens', function (Blueprint $table) {
|
||||
$table->string('id')->primary();
|
||||
$table->foreignUuid('user_id')->constrained()->onDelete('cascade');
|
||||
$table->string('type');
|
||||
$table->json('data')->nullable();
|
||||
$table->timestamp('expires_at')->nullable();
|
||||
$table->timestamp('created_at')->default(DB::raw('CURRENT_TIMESTAMP'));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::create('email_updates', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignUuid('user_id')->constrained()->onDelete('cascade');
|
||||
$table->string('email');
|
||||
$table->string('token')->unique();
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('password_reset_tokens', function (Blueprint $table) {
|
||||
$table->string('email')->primary();
|
||||
$table->string('token');
|
||||
$table->timestamp('created_at')->default(DB::raw('CURRENT_TIMESTAMP'));
|
||||
});
|
||||
|
||||
Schema::create('login_tokens', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('email');
|
||||
$table->string('token')->unique();
|
||||
$table->string('intended_url')->nullable();
|
||||
$table->timestamp('created_at')->default(DB::raw('CURRENT_TIMESTAMP'));
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -25,7 +25,7 @@ $billing_same_home = $user->home_address === $user->billing_address
|
||||
</div>
|
||||
<div class="flex gap-8">
|
||||
<div class="flex-1">
|
||||
<x-ui.input type="email" label="Email" name="email" value="{{ $user->email }}" label-notice="{{ $user->email_update_pending ? 'There is a pending request to change the email address of this account' : '' }}"/>
|
||||
<x-ui.input type="email" label="Email" name="email" value="{{ $user->email }}" info="{{ $user->email_update_pending ? 'Pending request to change to ' . $user->email_update_pending : '' }}"/>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<x-ui.input label="Phone" name="phone" value="{{ $user->phone }}" />
|
||||
|
||||
7
resources/views/auth/register-link.blade.php
Normal file
7
resources/views/auth/register-link.blade.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<x-layout :bodyClass="'image-background'">
|
||||
<x-dialog>
|
||||
<x-slot:title>Check your inbox</x-slot:title>
|
||||
<x-slot:header><p class="text-center">Check your email for the registration link we just sent. Click the link to finish setting up your account.</p></x-slot:header>
|
||||
<x-slot:footer class="mt-8"><x-ui.button href="{{ route('index') }}">Home</x-ui.button></x-slot:footer>
|
||||
</x-dialog>
|
||||
</x-layout>
|
||||
@@ -1,14 +0,0 @@
|
||||
@component('mail::message', ['username' => $username, 'email' => $email])
|
||||
<h2 class="center narrow">Confirm your new email address</h2>
|
||||
<p class="center narrow">A request was made to change your email address at STEMMechanics to {{ $newEmail }}.</p>
|
||||
<p class="center narrow">For your security, this link <strong>can only be used once</strong> and <strong>expires after 10 minutes.</strong></p>
|
||||
<p class="center narrow">
|
||||
@component('mail::button', ['url' => route('update.email', ['token' => $token])])
|
||||
Update Email
|
||||
@endcomponent
|
||||
</p>
|
||||
<hr />
|
||||
<h3>Why did I get this link?</h3>
|
||||
<p class="sub">Someone asked to change the email address associated with an account at STEMMechanics with this email.</p>
|
||||
<p class="sub">If this wasn't you, you can ignore this email.</p>
|
||||
@endcomponent
|
||||
6
resources/views/emails/email-update-confirm.blade.php
Normal file
6
resources/views/emails/email-update-confirm.blade.php
Normal file
@@ -0,0 +1,6 @@
|
||||
@component('mail::message', ['email' => $email])
|
||||
<p>Hey there!</p>
|
||||
<p>Just a quick line to confirm that your email address has now been updated at STEMMechanics!</p>
|
||||
<p>Warm regards,</p>
|
||||
<p>—James 😁</p>
|
||||
@endcomponent
|
||||
16
resources/views/emails/email-update-request.blade.php
Normal file
16
resources/views/emails/email-update-request.blade.php
Normal file
@@ -0,0 +1,16 @@
|
||||
@component('mail::message', ['email' => $email])
|
||||
<p>Hey there!</p>
|
||||
<p>You requested to update your email address at STEMMechanics to {{ $newEmail }}. Click the link below to confirm this change:</p>
|
||||
<p class="tall center">
|
||||
@component('mail::button', ['url' => $update_url])
|
||||
Update Email
|
||||
@endcomponent
|
||||
</p>
|
||||
<p>Remember, the link expires in 30 minutes.</p>
|
||||
<p>Warm regards,</p>
|
||||
<p>—James 😁</p>
|
||||
@slot('subcopy')
|
||||
<h4>Why did I get this email?</h4>
|
||||
<p class="sub">Someone asked to change the email address associated with an account at STEMMechanics with this email. If this wasn't you, you can ignore this email.</p>
|
||||
@endslot
|
||||
@endcomponent
|
||||
@@ -1,13 +0,0 @@
|
||||
@component('mail::message', ['username' => $username, 'email' => $email])
|
||||
<h2 class="center narrow">Follow this link to log in to your account.</h2>
|
||||
<p class="center narrow">For your security, this link <strong>can only be used once</strong> and <strong>expires after 10 minutes.</strong></p>
|
||||
<p class="center narrow">
|
||||
@component('mail::button', ['url' => route('login', ['token' => $token])])
|
||||
Log in
|
||||
@endcomponent
|
||||
</p>
|
||||
<hr />
|
||||
<h3>Why did I get this link?</h3>
|
||||
<p class="sub">Someone asked for a login link to log in to STEMMechanics with this email.</p>
|
||||
<p class="sub">If this wasn't you, you can ignore this email.</p>
|
||||
@endcomponent
|
||||
16
resources/views/emails/login.blade.php
Normal file
16
resources/views/emails/login.blade.php
Normal file
@@ -0,0 +1,16 @@
|
||||
@component('mail::message', ['email' => $email])
|
||||
<p>Hey there!</p>
|
||||
<p>You requested a link to log in to STEMMechanics, and here it is!</p>
|
||||
<p class="tall center">
|
||||
@component('mail::button', ['url' => $login_url])
|
||||
Log in
|
||||
@endcomponent
|
||||
</p>
|
||||
<p>Remember, the link expires in 10 minutes and can only be used once.</p>
|
||||
<p>Warm regards,</p>
|
||||
<p>—James 😁</p>
|
||||
@slot('subcopy')
|
||||
<h4>Why did I get this email?</h4>
|
||||
<p class="sub">Someone asked for a link to log in to STEMMechanics with this email address. If this wasn't you, you can ignore this email.</p>
|
||||
@endslot
|
||||
@endcomponent
|
||||
@@ -1,13 +0,0 @@
|
||||
@component('mail::message', ['username' => $username, 'email' => $email])
|
||||
<h2 class="center narrow">Follow this link to register your account.</h2>
|
||||
<p class="center narrow">For your security, this link <strong>can only be used once</strong> and <strong>expires after 10 minutes.</strong></p>
|
||||
<p class="center narrow">
|
||||
@component('mail::button', ['url' => route('login', ['token' => $token])])
|
||||
Register
|
||||
@endcomponent
|
||||
</p>
|
||||
<hr />
|
||||
<h3>Why did I get this link?</h3>
|
||||
<p class="sub">Someone asked to register an account at STEMMechanics with this email.</p>
|
||||
<p class="sub">If this wasn't you, you can ignore this email.</p>
|
||||
@endcomponent
|
||||
17
resources/views/emails/register.blade.php
Normal file
17
resources/views/emails/register.blade.php
Normal file
@@ -0,0 +1,17 @@
|
||||
@component('mail::message', ['email' => $email])
|
||||
<p>Hey there!</p>
|
||||
<p>We're thrilled to have you join us. To complete your registration and officially become part of the community, just click link below:</p>
|
||||
<p class="tall center">
|
||||
@component('mail::button', ['url' => $register_url])
|
||||
Register
|
||||
@endcomponent
|
||||
</p>
|
||||
<p>Remember, the link expires in 10 minutes and can only be used once, so act fast!</p>
|
||||
<p>Warm regards,</p>
|
||||
<p>—James 😁</p>
|
||||
|
||||
@slot('subcopy')
|
||||
<h4>Why did I get this email?</h4>
|
||||
<p class="sub">Someone asked to register at STEMMechanics with this email address. If this wasn't you, you can ignore this email.</p>
|
||||
@endslot
|
||||
@endcomponent
|
||||
7
resources/views/emails/subscribe.blade.php
Normal file
7
resources/views/emails/subscribe.blade.php
Normal file
@@ -0,0 +1,7 @@
|
||||
@component('mail::message', ['email' => $email, 'unsubscribe' => $unsubscribe])
|
||||
@include('emails.welcome')
|
||||
<hr />
|
||||
<h3>Why did I get this email?</h3>
|
||||
<p>Someone asked to subscribe to our mailing list at STEMMechanics with this email address.</p>
|
||||
<p>If this wasn't you, you can <a href="{{$unsubscribe}}">unsubscribe</a>.</p>
|
||||
@endcomponent
|
||||
12
resources/views/emails/welcome.blade.php
Normal file
12
resources/views/emails/welcome.blade.php
Normal file
@@ -0,0 +1,12 @@
|
||||
@component('mail::message', ['email' => $email])
|
||||
<p>Welcome to the community!</p>
|
||||
<p>Really glad to have you here and can't wait to see you at one of our workshops.</p>
|
||||
<p>You'll get information about upcoming workshops as it comes out.</p>
|
||||
<p>Even though this is (of course) an automated email, just wanted to say thanks for registering and intro myself.</p>
|
||||
<p>If you didn't know, I'm James and I'm the founder of STEMMechanics. I promise not to spam you, sell your data, or send you anything I don't think is absolutely necessary.</p>
|
||||
<p>You know a bit about me but I don't know really anything about you...</p>
|
||||
<p><strong>If you're up for it</strong>, reply to this email and tell me a bit about yourself and also let me know what workshops you are interested in?</p>
|
||||
<p>I read and reply to every one 😁</p>
|
||||
<p>Talk soon</p>
|
||||
<p>—James</p>
|
||||
@endcomponent
|
||||
@@ -1,11 +1 @@
|
||||
<tr>
|
||||
<td>
|
||||
<table class="footer" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tr>
|
||||
<td class="content-cell" align="center">
|
||||
{{ Illuminate\Mail\Markdown::parse($slot) }}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
@props(['url', 'username'])
|
||||
<tr>
|
||||
<td class="header">
|
||||
<a href="{{ $url }}" style="display: inline-block;">
|
||||
@props(['url'])
|
||||
<a href="{{ $url }}">
|
||||
<img
|
||||
alt="STEMMechanics Logo"
|
||||
src="https://www.stemmechanics.com.au/logo.svg"
|
||||
@@ -9,6 +7,3 @@
|
||||
height="31"
|
||||
/>
|
||||
</a>
|
||||
<h1>Hello, {{ $username }}</h1>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -25,33 +25,46 @@ width: 100% !important;
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<table class="wrapper" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<!-- Email Body -->
|
||||
<table class="inner-body" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<!-- Body header -->
|
||||
<tr>
|
||||
<td class="header" align="center">
|
||||
{{ $header ?? '' }}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<table class="wrapper" width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<table class="content" width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
||||
{{ $header ?? '' }}
|
||||
|
||||
<!-- Email Body -->
|
||||
<tr>
|
||||
<td class="body" width="100%" cellpadding="0" cellspacing="0" style="border: hidden !important;">
|
||||
<table class="inner-body" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<!-- Body content -->
|
||||
<tr>
|
||||
<td class="content-cell">
|
||||
{{ Illuminate\Mail\Markdown::parse($slot) }}
|
||||
|
||||
{{ $subcopy ?? '' }}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{{ $footer ?? '' }}
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!-- Body content -->
|
||||
<tr>
|
||||
<td class="content-cell">
|
||||
{{ Illuminate\Mail\Markdown::parse($slot) }}
|
||||
</td>
|
||||
</tr>
|
||||
@isset($subcopy)
|
||||
<tr>
|
||||
<td class="content-cell">
|
||||
<hr />
|
||||
{{ $subcopy ?? '' }}
|
||||
</td>
|
||||
</tr>
|
||||
@endisset
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<table class="footer" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tr>
|
||||
<td class="content-cell" align="center">
|
||||
{{ $footer ?? '' }}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<x-mail::layout>
|
||||
{{-- Header --}}
|
||||
<x-slot:header>
|
||||
<x-mail::header :url="config('app.url')" username="{{ $username }}" />
|
||||
<x-mail::header :url="config('app.url')"/>
|
||||
</x-slot:header>
|
||||
|
||||
{{-- Body --}}
|
||||
@@ -10,9 +10,7 @@
|
||||
{{-- Subcopy --}}
|
||||
@isset($subcopy)
|
||||
<x-slot:subcopy>
|
||||
<x-mail::subcopy>
|
||||
{{ $subcopy }}
|
||||
</x-mail::subcopy>
|
||||
{{ Illuminate\Mail\Markdown::parse($subcopy) }}
|
||||
</x-slot:subcopy>
|
||||
@endisset
|
||||
|
||||
@@ -20,9 +18,9 @@
|
||||
<x-slot:footer>
|
||||
<x-mail::footer>
|
||||
<p>This email was sent to <a href="mailto:{{ $email }}">{{ $email }}</a><br />
|
||||
<a href="{{ config('app.url') }}">{{ config('app.name') }}</a> | 1/4 Jordan Street | Edmonton, QLD 4869 Australia<br />
|
||||
<a href="{{ route('index') }}">{{ config('app.name') }}</a> | 1/4 Jordan Street | Edmonton, QLD 4869 Australia<br />
|
||||
© {{ date('Y') }} {{ config('app.name') }}. {{ __('All rights reserved.') }}<br />
|
||||
<a href="{{ config('app.url') }}/privacy">Privacy Policy</a>
|
||||
<a href="{{ route('privacy') }}">Privacy Policy</a> | <a href="{{ route('terms-conditions') }}">Terms & Conditions</a> @isset($unsubscribe) | <a href="{{ $unsubscribe }}">Unsubscribe</a>@endisset
|
||||
</p>
|
||||
</x-mail::footer>
|
||||
</x-slot:footer>
|
||||
|
||||
@@ -1,7 +1 @@
|
||||
<table class="subcopy" width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
||||
<tr>
|
||||
<td>
|
||||
{{ Illuminate\Mail\Markdown::parse($slot) }}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -10,8 +10,8 @@ body *:not(html):not(style):not(br):not(tr):not(code) {
|
||||
|
||||
body {
|
||||
-webkit-text-size-adjust: none;
|
||||
background-color: #ffffff;
|
||||
color: #718096;
|
||||
background-color: #eee;
|
||||
color: #444;
|
||||
height: 100%;
|
||||
line-height: 1.4;
|
||||
margin: 0;
|
||||
@@ -31,7 +31,7 @@ a {
|
||||
color: #3869d4;
|
||||
}
|
||||
|
||||
a img {
|
||||
img {
|
||||
border: none;
|
||||
}
|
||||
|
||||
@@ -39,10 +39,11 @@ a img {
|
||||
|
||||
h1 {
|
||||
color: #3d4852;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
margin-top: 14px;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: inline-block;
|
||||
border: 1px solid red;
|
||||
}
|
||||
|
||||
h2 {
|
||||
@@ -59,9 +60,17 @@ h3 {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 16px;
|
||||
line-height: 1.5em;
|
||||
font-size: 14px;
|
||||
line-height: 1.2em;
|
||||
margin-top: 0;
|
||||
text-align: left;
|
||||
}
|
||||
@@ -88,9 +97,9 @@ hr {
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
-premailer-width: 100%;
|
||||
background-color: #edf2f7;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #eee;
|
||||
margin: 30px auto;
|
||||
padding: 12px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -107,23 +116,21 @@ hr {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.narrow {
|
||||
padding-left: 32px;
|
||||
padding-right: 32px;
|
||||
.tall {
|
||||
padding-top: 16px;
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
|
||||
.header {
|
||||
padding: 25px 0;
|
||||
text-align: center;
|
||||
padding-top: 28px;
|
||||
padding-bottom: 32px;
|
||||
}
|
||||
|
||||
.header a {
|
||||
color: #3d4852;
|
||||
font-size: 19px;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Logo */
|
||||
@@ -154,24 +161,13 @@ hr {
|
||||
-premailer-width: 570px;
|
||||
background-color: #ffffff;
|
||||
border-color: #e8e5ef;
|
||||
border-radius: 2px;
|
||||
border-radius: 8px;
|
||||
border-width: 1px;
|
||||
box-shadow: 0 2px 0 rgba(0, 0, 150, 0.025), 2px 4px 0 rgba(0, 0, 150, 0.015);
|
||||
box-shadow: 4px 4px 12px rgba(0, 0, 0, 0.05);
|
||||
margin: 0 auto;
|
||||
padding: 0;
|
||||
padding: 0 0 32px;
|
||||
width: 570px;
|
||||
}
|
||||
|
||||
/* Subcopy */
|
||||
|
||||
.subcopy {
|
||||
border-top: 1px solid #e8e5ef;
|
||||
margin-top: 25px;
|
||||
padding-top: 25px;
|
||||
}
|
||||
|
||||
.subcopy p {
|
||||
font-size: 14px;
|
||||
max-width: 570px;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
@@ -181,7 +177,7 @@ hr {
|
||||
-premailer-cellspacing: 0;
|
||||
-premailer-width: 570px;
|
||||
margin: 0 auto;
|
||||
padding: 0;
|
||||
padding: 32px 0 0;
|
||||
text-align: center;
|
||||
width: 570px;
|
||||
}
|
||||
@@ -204,7 +200,7 @@ hr {
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
-premailer-width: 100%;
|
||||
margin: 30px auto;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -223,8 +219,9 @@ hr {
|
||||
}
|
||||
|
||||
.content-cell {
|
||||
max-width: 100vw;
|
||||
padding: 32px;
|
||||
max-width: 570px;
|
||||
padding-left: 32px;
|
||||
padding-right: 32px;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
@@ -246,10 +243,6 @@ hr {
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.button-blue,
|
||||
.button-primary {
|
||||
background-color: #2d3748;
|
||||
border-bottom: 8px solid #2d3748;
|
||||
border-left: 18px solid #2d3748;
|
||||
@@ -257,6 +250,15 @@ hr {
|
||||
border-top: 8px solid #2d3748;
|
||||
}
|
||||
|
||||
.button-blue,
|
||||
.button-primary {
|
||||
background-color: #0284C7;
|
||||
border-bottom: 8px solid #0284C7;
|
||||
border-left: 18px solid #0284C7;
|
||||
border-right: 18px solid #0284C7;
|
||||
border-top: 8px solid #0284C7;
|
||||
}
|
||||
|
||||
.button-green,
|
||||
.button-success {
|
||||
background-color: #48bb78;
|
||||
|
||||
@@ -1 +1 @@
|
||||
{{ $slot }}: {{ $url }}
|
||||
{{ $url }}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{{ $slot }}: {{ $url }}
|
||||
{{ $slot }}
|
||||
|
||||
@@ -1,9 +1,21 @@
|
||||
{!! strip_tags($header ?? '') !!}
|
||||
|
||||
{!! strip_tags($slot) !!}
|
||||
@isset($subcopy)
|
||||
@php
|
||||
$slot = str_replace([' ', "\t"], '', $slot);
|
||||
$slot = str_replace('</p>', "\r\n", $slot);
|
||||
$slot = strip_tags($slot);
|
||||
@endphp
|
||||
{!! $slot !!}
|
||||
|
||||
{!! strip_tags($subcopy) !!}
|
||||
@isset($subcopy)
|
||||
@php
|
||||
$subcopy = str_replace([' ', "\t"], '', $subcopy);
|
||||
$subcopy = str_replace("</h4>\n", " - ", $subcopy);
|
||||
$subcopy = str_replace(['<br>', '<br />', '</p>'], "\r\n", $subcopy);
|
||||
$subcopy = strip_tags($subcopy);
|
||||
@endphp
|
||||
{!! $subcopy !!}
|
||||
@endisset
|
||||
|
||||
------
|
||||
{!! strip_tags($footer ?? '') !!}
|
||||
|
||||
@@ -1,27 +1,24 @@
|
||||
<x-mail::layout>
|
||||
{{-- Header --}}
|
||||
<x-slot:header>
|
||||
<x-mail::header :url="config('app.url')">
|
||||
{{ config('app.name') }}
|
||||
</x-mail::header>
|
||||
</x-slot:header>
|
||||
{{-- Body --}}
|
||||
{{ $slot }}
|
||||
|
||||
{{-- Body --}}
|
||||
{{ $slot }}
|
||||
@isset($subcopy)
|
||||
<x-slot:subcopy>
|
||||
{{ $subcopy }}
|
||||
</x-slot:subcopy>
|
||||
@endisset
|
||||
|
||||
{{-- Subcopy --}}
|
||||
@isset($subcopy)
|
||||
<x-slot:subcopy>
|
||||
<x-mail::subcopy>
|
||||
{{ $subcopy }}
|
||||
</x-mail::subcopy>
|
||||
</x-slot:subcopy>
|
||||
@endisset
|
||||
|
||||
{{-- Footer --}}
|
||||
<x-slot:footer>
|
||||
<x-mail::footer>
|
||||
© {{ date('Y') }} {{ config('app.name') }}. @lang('All rights reserved.')
|
||||
</x-mail::footer>
|
||||
</x-slot:footer>
|
||||
{{-- Footer --}}
|
||||
<x-slot:footer>
|
||||
<x-mail::footer>
|
||||
|
||||
This email was sent to {{ $email }}
|
||||
|
||||
STEMMechanics | 1/4 Jordan Street | Edmonton, QLD 4869 Australia
|
||||
© {{ date('Y') }} {{ config('app.name') }}. {{ __('All rights reserved.') }}
|
||||
|
||||
@isset($unsubscribe) Unsubscribe: {{ $unsubscribe }}@endisset
|
||||
</x-mail::footer>
|
||||
</x-slot:footer>
|
||||
</x-mail::layout>
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
|
||||
{{ $slot }}
|
||||
|
||||
@@ -8,13 +8,8 @@ use Illuminate\Support\Facades\Storage;
|
||||
Artisan::command('cleanup', function() {
|
||||
|
||||
// Clean up expired tokens
|
||||
DB::table('login_tokens')
|
||||
->where('created_at', '<', now()->subMinutes(10))
|
||||
->delete();
|
||||
|
||||
// Clean up expired change email requests
|
||||
DB::table('email_updates')
|
||||
->where('created_at', '<', now()->subMinutes(10))
|
||||
DB::table('tokens')
|
||||
->where('expires_at', '<', now())
|
||||
->delete();
|
||||
|
||||
// Published scheduled posts
|
||||
|
||||
Reference in New Issue
Block a user