added email subscriptions
This commit is contained in:
44
app/Http/Controllers/UnsubscribeController.php
Normal file
44
app/Http/Controllers/UnsubscribeController.php
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\EmailSubscriptions;
|
||||||
|
use App\Models\SentEmail;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class UnsubscribeController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Display a listing of the resource.
|
||||||
|
*/
|
||||||
|
public function destroy($email)
|
||||||
|
{
|
||||||
|
$emailModel = SentEmail::where('id', $email)->first();
|
||||||
|
|
||||||
|
if (!$emailModel) {
|
||||||
|
// Email not found, redirect to home page with a message
|
||||||
|
return redirect()->route('index')->with([
|
||||||
|
'message' => 'The unsubscribe link is invalid or has expired.',
|
||||||
|
'message-title' => 'Invalid Unsubscribe Link',
|
||||||
|
'message-type' => 'warning'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Existing unsubscribe logic
|
||||||
|
$subscriptions = EmailSubscriptions::where('email', $emailModel->recipient)->get();
|
||||||
|
|
||||||
|
if ($subscriptions->isEmpty()) {
|
||||||
|
session()->flash('message', 'You are already unsubscribed.');
|
||||||
|
session()->flash('message-title', 'Already Unsubscribed');
|
||||||
|
session()->flash('message-type', 'info');
|
||||||
|
} else {
|
||||||
|
EmailSubscriptions::where('email', $emailModel->recipient)->delete();
|
||||||
|
|
||||||
|
session()->flash('message', 'You have been successfully unsubscribed.');
|
||||||
|
session()->flash('message-title', 'Unsubscribed');
|
||||||
|
session()->flash('message-type', 'success');
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->route('index');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Jobs;
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Models\SentEmail;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
@@ -48,6 +49,18 @@ class SendEmail implements ShouldQueue
|
|||||||
*/
|
*/
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
|
// Record sent email
|
||||||
|
$sentEmail = SentEmail::create([
|
||||||
|
'recipient' => $this->to,
|
||||||
|
'mailable_class' => get_class($this->mailable)
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Add unsubscribe link if mailable supports it
|
||||||
|
if (method_exists($this->mailable, 'withUnsubscribeLink')) {
|
||||||
|
$unsubscribeLink = route('unsubscribe', ['email' => $sentEmail->id]);
|
||||||
|
$this->mailable->withUnsubscribeLink($unsubscribeLink);
|
||||||
|
}
|
||||||
|
|
||||||
Mail::to($this->to)->send($this->mailable);
|
Mail::to($this->to)->send($this->mailable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
58
app/Mail/UpcomingWorkshops.php
Normal file
58
app/Mail/UpcomingWorkshops.php
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Mail;
|
||||||
|
|
||||||
|
use App\Models\Workshop;
|
||||||
|
use App\Traits\HasUnsubscribeLink;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Mail\Mailable;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
|
||||||
|
class UpcomingWorkshops extends Mailable
|
||||||
|
{
|
||||||
|
use Queueable, SerializesModels, HasUnsubscribeLink;
|
||||||
|
|
||||||
|
public $subject;
|
||||||
|
public $email;
|
||||||
|
public $workshops;
|
||||||
|
|
||||||
|
public function __construct($email, $subject = 'Upcoming Workshops 🌟')
|
||||||
|
{
|
||||||
|
$this->subject = $subject;
|
||||||
|
$this->email = $email;
|
||||||
|
$this->workshops = $this->getUpcomingWorkshops();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getUpcomingWorkshops()
|
||||||
|
{
|
||||||
|
$startDate = Carbon::now()->addDays(3);
|
||||||
|
$endDate = Carbon::now()->addDays(42);
|
||||||
|
|
||||||
|
return Workshop::select('workshops.*', 'locations.name as location_name')
|
||||||
|
->join('locations', 'workshops.location_id', '=', 'locations.id')
|
||||||
|
->whereIn('workshops.status', ['open','scheduled'])
|
||||||
|
->whereBetween('workshops.starts_at', [$startDate, $endDate])
|
||||||
|
->where('locations.name', 'not like', '%private%')
|
||||||
|
->orderBy('locations.name')
|
||||||
|
->orderBy('workshops.starts_at')
|
||||||
|
->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function build()
|
||||||
|
{
|
||||||
|
// Bail if there are no upcoming workshops
|
||||||
|
if ($this->workshops->isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this
|
||||||
|
->subject($this->subject)
|
||||||
|
->markdown('emails.upcoming-workshops')
|
||||||
|
->with([
|
||||||
|
'email' => $this->email,
|
||||||
|
'workshops' => $this->workshops,
|
||||||
|
'unsubscribeLink' => $this->unsubscribeLink
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,8 +7,6 @@ use Illuminate\Database\Eloquent\Model;
|
|||||||
|
|
||||||
class EmailSubscriptions extends Model
|
class EmailSubscriptions extends Model
|
||||||
{
|
{
|
||||||
use HasFactory;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The attributes that are mass assignable.
|
* The attributes that are mass assignable.
|
||||||
*
|
*
|
||||||
|
|||||||
14
app/Traits/HasUnsubscribeLink.php
Normal file
14
app/Traits/HasUnsubscribeLink.php
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Traits;
|
||||||
|
|
||||||
|
trait HasUnsubscribeLink
|
||||||
|
{
|
||||||
|
protected $unsubscribeLink;
|
||||||
|
|
||||||
|
public function withUnsubscribeLink($link)
|
||||||
|
{
|
||||||
|
$this->unsubscribeLink = $link;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,8 @@ use Illuminate\Support\Str;
|
|||||||
|
|
||||||
trait Slug
|
trait Slug
|
||||||
{
|
{
|
||||||
|
protected $appendsSlug = ['slug'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Boot function from Laravel.
|
* Boot function from Laravel.
|
||||||
*
|
*
|
||||||
@@ -20,6 +22,16 @@ trait Slug
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the trait.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function initializeSlug(): void
|
||||||
|
{
|
||||||
|
$this->appends = array_merge($this->appends ?? [], $this->appendsSlug);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the value indicating whether the IDs are incrementing.
|
* Get the value indicating whether the IDs are incrementing.
|
||||||
*
|
*
|
||||||
@@ -47,7 +59,7 @@ trait Slug
|
|||||||
*/
|
*/
|
||||||
public function getRouteKey()
|
public function getRouteKey()
|
||||||
{
|
{
|
||||||
return $this->slug();
|
return $this->slug;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -68,7 +80,7 @@ trait Slug
|
|||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function slug()
|
public function getSlugAttribute()
|
||||||
{
|
{
|
||||||
return Str::slug($this->title) . '-' . $this->id;
|
return Str::slug($this->title) . '-' . $this->id;
|
||||||
}
|
}
|
||||||
|
|||||||
29
resources/views/emails/upcoming-workshops.blade.php
Normal file
29
resources/views/emails/upcoming-workshops.blade.php
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
@component('mail::message', ['email' => $email])
|
||||||
|
<p>Hey there!</p>
|
||||||
|
<p>Check out our exciting workshops coming up in the next few weeks:</p>
|
||||||
|
<p class="center">
|
||||||
|
@php
|
||||||
|
$currentLocation = null;
|
||||||
|
@endphp
|
||||||
|
@foreach($workshops as $workshop)
|
||||||
|
@if($workshop->location->name !== $currentLocation)
|
||||||
|
<h2 style="margin-top: 32px; margin-bottom: 6px">{{ $workshop->location->name }}</h2>
|
||||||
|
@php
|
||||||
|
$currentLocation = $workshop->location->name;
|
||||||
|
@endphp
|
||||||
|
@endif
|
||||||
|
<p style="margin-bottom: 6px">{{ $workshop->starts_at->format('D, j M, g:i A') . ' - ' }}<a href="{{ route('workshop.show', $workshop->slug) }}">{{ $workshop->title }}</a> ({{ ($workshop->price && is_numeric($workshop->price) && $workshop->price != '0' ? '$' . number_format((float)$workshop->price, 2) : 'Free') . ( $workshop->status === 'scheduled' ? ' / Opens soon' : '') }})</p>
|
||||||
|
@endforeach
|
||||||
|
<p class="tall center" style="margin-top: 32px">
|
||||||
|
@component('mail::button', ['url' => 'https://stemmechanics.com.au/workshops'])
|
||||||
|
View All Workshops
|
||||||
|
@endcomponent
|
||||||
|
</p>
|
||||||
|
<p>We hope to see you at one of our upcoming workshops!</p>
|
||||||
|
<p>Warm regards,</p>
|
||||||
|
<p>—James 😁</p>
|
||||||
|
@slot('subcopy')
|
||||||
|
<h4>Why did I get this email?</h4>
|
||||||
|
<p class="sub">You received this email as you are subscribed to our upcoming workshop email list. If you wish no longer receive this email, you can <a href="{{ $unsubscribeLink }}">unsubscribe here</a>.</p>
|
||||||
|
@endslot
|
||||||
|
@endcomponent
|
||||||
@@ -43,7 +43,6 @@ h1 {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
border: 1px solid red;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
|
|||||||
@@ -1,10 +1,39 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use App\Jobs\SendEmail;
|
||||||
|
use App\Mail\UpcomingWorkshops;
|
||||||
|
use App\Mail\UserWelcome;
|
||||||
use App\Models\Media;
|
use App\Models\Media;
|
||||||
use Illuminate\Support\Facades\Artisan;
|
use Illuminate\Support\Facades\Artisan;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Mail;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
Artisan::command('email:send', function() {
|
||||||
|
$subjects = [
|
||||||
|
'🚀 Your STEM Adventure Awaits!',
|
||||||
|
'⚡ Spark Your STEM Skills in a Workshop',
|
||||||
|
'🔬 Unleash Your Curiosity in a Workshop',
|
||||||
|
'🧠 Boost Your Brain with STEM Workshops',
|
||||||
|
'🌟 Become a STEM Star: Join Our Workshops',
|
||||||
|
'🔧 Tinker, Create, Learn in a Workshop',
|
||||||
|
'🎨 Where Science Meets Creativity',
|
||||||
|
'🏆 Level Up Your STEM Skills',
|
||||||
|
'🌈 Discover the STEM Spectrum',
|
||||||
|
'🔮 Future Innovators: Workshops Unveiled',
|
||||||
|
];
|
||||||
|
|
||||||
|
$subject = $subjects[array_rand($subjects)];
|
||||||
|
|
||||||
|
$subscribers = DB::table('email_subscriptions')
|
||||||
|
->whereNotNull('confirmed')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
foreach ($subscribers as $subscriber) {
|
||||||
|
dispatch(new SendEmail($subscriber->email, new UpcomingWorkshops($subscriber->email, $subject)))->onQueue('mail');
|
||||||
|
}
|
||||||
|
})->purpose('Send newsletter to confirmed subscribers')->daily();
|
||||||
|
|
||||||
Artisan::command('cleanup', function() {
|
Artisan::command('cleanup', function() {
|
||||||
|
|
||||||
// Clean up expired tokens
|
// Clean up expired tokens
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use App\Http\Controllers\LocationController;
|
|||||||
use App\Http\Controllers\MediaController;
|
use App\Http\Controllers\MediaController;
|
||||||
use App\Http\Controllers\PostController;
|
use App\Http\Controllers\PostController;
|
||||||
use App\Http\Controllers\SearchController;
|
use App\Http\Controllers\SearchController;
|
||||||
|
use App\Http\Controllers\UnsubscribeController;
|
||||||
use App\Http\Controllers\UserController;
|
use App\Http\Controllers\UserController;
|
||||||
use App\Http\Controllers\WorkshopController;
|
use App\Http\Controllers\WorkshopController;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
@@ -20,6 +21,7 @@ Route::get('workshops/past', [WorkshopController::class, 'past_index'])->name('w
|
|||||||
Route::get('workshops/{workshop}', [WorkshopController::class, 'show'])->name('workshop.show');
|
Route::get('workshops/{workshop}', [WorkshopController::class, 'show'])->name('workshop.show');
|
||||||
|
|
||||||
Route::get('search', [SearchController::class, 'index'])->name('search.index');
|
Route::get('search', [SearchController::class, 'index'])->name('search.index');
|
||||||
|
Route::get('unsubscribe/{email}', [UnsubscribeController::class, 'destroy'])->name('unsubscribe');
|
||||||
|
|
||||||
Route::middleware('auth')->group(function () {
|
Route::middleware('auth')->group(function () {
|
||||||
Route::get('/account', [AccountController::class, 'show'])->name('account.show');
|
Route::get('/account', [AccountController::class, 'show'])->name('account.show');
|
||||||
|
|||||||
Reference in New Issue
Block a user