diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index 4e0e80f..ef010a0 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -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([ - 'email' => $newEmail, - 'token' => $token + $token = $user->tokens()->create([ + 'type' => 'email-update', + 'data' => [ + 'email' => $newEmail, + ], + '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'); diff --git a/app/Http/Controllers/AuthController.php b/app/Http/Controllers/AuthController.php index d5d09df..c555413 100644 --- a/app/Http/Controllers/AuthController.php +++ b/app/Http/Controllers/AuthController.php @@ -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(); + dispatch(new SendEmail($user->email, new UserLogin($token->id, $user->getName(), $user->email)))->onQueue('mail'); + return view('auth.login-link'); + } - if ($user) { - Auth::login($user); + session()->flash('status', 'not-found'); + return view('auth.login'); + } - $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'); + /** + * 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($intended_url) { - return redirect($intended_url); - } - - return redirect()->action([HomeController::class, 'index']); + 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); + } + + return redirect()->action([HomeController::class, 'index']); } - public function logout() { + /** + * 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'); - session()->flash('message', 'Your email has been updated'); - session()->flash('message-title', 'Email updated'); - session()->flash('message-type', 'success'); - return redirect()->route('index'); + $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'); } } diff --git a/app/Mail/UserEmailUpdateConfirm.php b/app/Mail/UserEmailUpdateConfirm.php new file mode 100644 index 0000000..429e7c6 --- /dev/null +++ b/app/Mail/UserEmailUpdateConfirm.php @@ -0,0 +1,30 @@ +email = $email; + } + + public function build() + { + return $this + ->subject('Your STEMMechanics account has been updated 👍') + ->markdown('emails.email-update-confirm') + ->with([ + 'email' => $this->email, + ]); + } +} diff --git a/app/Mail/EmailUpdateLink.php b/app/Mail/UserEmailUpdateRequest.php similarity index 61% rename from app/Mail/EmailUpdateLink.php rename to app/Mail/UserEmailUpdateRequest.php index 6f41744..ecb66c1 100644 --- a/app/Mail/EmailUpdateLink.php +++ b/app/Mail/UserEmailUpdateRequest.php @@ -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, ]); diff --git a/app/Mail/LoginLink.php b/app/Mail/UserLogin.php similarity index 75% rename from app/Mail/LoginLink.php rename to app/Mail/UserLogin.php index 38517c8..e8bf2b1 100644 --- a/app/Mail/LoginLink.php +++ b/app/Mail/UserLogin.php @@ -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, ]); diff --git a/app/Mail/RegisterLink.php b/app/Mail/UserRegister.php similarity index 57% rename from app/Mail/RegisterLink.php rename to app/Mail/UserRegister.php index 646f4b5..9b72edc 100644 --- a/app/Mail/RegisterLink.php +++ b/app/Mail/UserRegister.php @@ -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, ]); } diff --git a/app/Mail/UserWelcome.php b/app/Mail/UserWelcome.php new file mode 100644 index 0000000..0bd413d --- /dev/null +++ b/app/Mail/UserWelcome.php @@ -0,0 +1,30 @@ +email = $email; + } + + public function build() + { + return $this + ->subject('Welcome to STEMMechanics 🌟') + ->markdown('emails.welcome') + ->with([ + 'email' => $this->email, + ]); + } +} diff --git a/app/Models/EmailUpdate.php b/app/Models/EmailUpdate.php deleted file mode 100644 index 3f2501f..0000000 --- a/app/Models/EmailUpdate.php +++ /dev/null @@ -1,30 +0,0 @@ -belongsTo(User::class); - } -} diff --git a/app/Models/Token.php b/app/Models/Token.php new file mode 100644 index 0000000..85f3740 --- /dev/null +++ b/app/Models/Token.php @@ -0,0 +1,87 @@ + + */ + 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); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index 66430bb..be0781b 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -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 diff --git a/database/migrations/0001_01_01_000000_create_users_table.php b/database/migrations/0001_01_01_000000_create_users_table.php index 577d658..56d67dd 100644 --- a/database/migrations/0001_01_01_000000_create_users_table.php +++ b/database/migrations/0001_01_01_000000_create_users_table.php @@ -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'); } diff --git a/database/migrations/2024_05_06_155300_merge_tokens_table.php b/database/migrations/2024_05_06_155300_merge_tokens_table.php new file mode 100644 index 0000000..069e2ca --- /dev/null +++ b/database/migrations/2024_05_06_155300_merge_tokens_table.php @@ -0,0 +1,56 @@ +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')); + }); + } +}; diff --git a/resources/views/account.blade.php b/resources/views/account.blade.php index 4b6c675..caeef42 100644 --- a/resources/views/account.blade.php +++ b/resources/views/account.blade.php @@ -25,7 +25,7 @@ $billing_same_home = $user->home_address === $user->billing_address
Check your email for the registration link we just sent. Click the link to finish setting up your account.
A request was made to change your email address at STEMMechanics to {{ $newEmail }}.
-For your security, this link can only be used once and expires after 10 minutes.
-- @component('mail::button', ['url' => route('update.email', ['token' => $token])]) - Update Email - @endcomponent -
-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.
-@endcomponent diff --git a/resources/views/emails/email-update-confirm.blade.php b/resources/views/emails/email-update-confirm.blade.php new file mode 100644 index 0000000..a775a96 --- /dev/null +++ b/resources/views/emails/email-update-confirm.blade.php @@ -0,0 +1,6 @@ +@component('mail::message', ['email' => $email]) +Hey there!
+Just a quick line to confirm that your email address has now been updated at STEMMechanics!
+Warm regards,
+—James 😁
+@endcomponent diff --git a/resources/views/emails/email-update-request.blade.php b/resources/views/emails/email-update-request.blade.php new file mode 100644 index 0000000..2f910df --- /dev/null +++ b/resources/views/emails/email-update-request.blade.php @@ -0,0 +1,16 @@ +@component('mail::message', ['email' => $email]) +Hey there!
+You requested to update your email address at STEMMechanics to {{ $newEmail }}. Click the link below to confirm this change:
++ @component('mail::button', ['url' => $update_url]) + Update Email + @endcomponent +
+Remember, the link expires in 30 minutes.
+Warm regards,
+—James 😁
+ @slot('subcopy') +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.
+ @endslot +@endcomponent diff --git a/resources/views/emails/login-link.blade.php b/resources/views/emails/login-link.blade.php deleted file mode 100644 index 808d88e..0000000 --- a/resources/views/emails/login-link.blade.php +++ /dev/null @@ -1,13 +0,0 @@ -@component('mail::message', ['username' => $username, 'email' => $email]) -For your security, this link can only be used once and expires after 10 minutes.
-- @component('mail::button', ['url' => route('login', ['token' => $token])]) - Log in - @endcomponent -
-Someone asked for a login link to log in to STEMMechanics with this email.
-If this wasn't you, you can ignore this email.
-@endcomponent diff --git a/resources/views/emails/login.blade.php b/resources/views/emails/login.blade.php new file mode 100644 index 0000000..be1e1f0 --- /dev/null +++ b/resources/views/emails/login.blade.php @@ -0,0 +1,16 @@ +@component('mail::message', ['email' => $email]) +Hey there!
+You requested a link to log in to STEMMechanics, and here it is!
++ @component('mail::button', ['url' => $login_url]) + Log in + @endcomponent +
+Remember, the link expires in 10 minutes and can only be used once.
+Warm regards,
+—James 😁
+ @slot('subcopy') +Someone asked for a link to log in to STEMMechanics with this email address. If this wasn't you, you can ignore this email.
+ @endslot +@endcomponent diff --git a/resources/views/emails/register-link.blade.php b/resources/views/emails/register-link.blade.php deleted file mode 100644 index 02a1748..0000000 --- a/resources/views/emails/register-link.blade.php +++ /dev/null @@ -1,13 +0,0 @@ -@component('mail::message', ['username' => $username, 'email' => $email]) -For your security, this link can only be used once and expires after 10 minutes.
-- @component('mail::button', ['url' => route('login', ['token' => $token])]) - Register - @endcomponent -
-Someone asked to register an account at STEMMechanics with this email.
-If this wasn't you, you can ignore this email.
-@endcomponent diff --git a/resources/views/emails/register.blade.php b/resources/views/emails/register.blade.php new file mode 100644 index 0000000..de5dbb9 --- /dev/null +++ b/resources/views/emails/register.blade.php @@ -0,0 +1,17 @@ +@component('mail::message', ['email' => $email]) +Hey there!
+We're thrilled to have you join us. To complete your registration and officially become part of the community, just click link below:
++@component('mail::button', ['url' => $register_url]) +Register +@endcomponent +
+Remember, the link expires in 10 minutes and can only be used once, so act fast!
+Warm regards,
+—James 😁
+ +@slot('subcopy') +Someone asked to register at STEMMechanics with this email address. If this wasn't you, you can ignore this email.
+@endslot +@endcomponent diff --git a/resources/views/emails/subscribe.blade.php b/resources/views/emails/subscribe.blade.php new file mode 100644 index 0000000..183305e --- /dev/null +++ b/resources/views/emails/subscribe.blade.php @@ -0,0 +1,7 @@ +@component('mail::message', ['email' => $email, 'unsubscribe' => $unsubscribe]) + @include('emails.welcome') +Someone asked to subscribe to our mailing list at STEMMechanics with this email address.
+If this wasn't you, you can unsubscribe.
+@endcomponent diff --git a/resources/views/emails/welcome.blade.php b/resources/views/emails/welcome.blade.php new file mode 100644 index 0000000..0e6096a --- /dev/null +++ b/resources/views/emails/welcome.blade.php @@ -0,0 +1,12 @@ +@component('mail::message', ['email' => $email]) +Welcome to the community!
+Really glad to have you here and can't wait to see you at one of our workshops.
+You'll get information about upcoming workshops as it comes out.
+Even though this is (of course) an automated email, just wanted to say thanks for registering and intro myself.
+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.
+You know a bit about me but I don't know really anything about you...
+If you're up for it, reply to this email and tell me a bit about yourself and also let me know what workshops you are interested in?
+I read and reply to every one 😁
+Talk soon
+—James
+@endcomponent diff --git a/resources/views/vendor/mail/html/footer.blade.php b/resources/views/vendor/mail/html/footer.blade.php index 3ff41f8..773cecc 100644 --- a/resources/views/vendor/mail/html/footer.blade.php +++ b/resources/views/vendor/mail/html/footer.blade.php @@ -1,11 +1 @@ -| {{ Illuminate\Mail\Markdown::parse($slot) }} - | -
+
+
+ {{ Illuminate\Mail\Markdown::parse($slot) }}
+ |
+
+ |
+ + {{ $subcopy ?? '' }} + |
+ ||
+
|
+