This is a viewer only at the moment see the article on how this works.
To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk
This is a preview from the server running through my markdig pipeline
Een Newsletter Subscription Service Pt. 1 | - Benodigheden en Subscriptionspagina
Saturday, 21 September 2024
Inleiding
Terwijl ik andere mensen's blogs bekeek, merkte ik dat velen van hen een abonnementsdienst hebben waarmee gebruikers zich kunnen inschrijven om weekelijk een e-mail te sturen met de posten uit die blog. Ik besloot mijn eigen versie van dit te implementeren en te delen hoe ik het deed.
Let op: ik verwacht niet dat iemand dit zal gebruiken. Ik heb veel tijd aan mijn handen na een contract dat mislukte, dus dit houdt me bezig.
Stappen
Dus om deze dienst te creëren, besloot ik de volgende voorwaarden te bepalen.
- Een flexibele abonnementspagina die gebruikers wat flexibiliteit mogelijk maakt.
- Gebruikers zetten alleen e-mail in.
- De mogelijkheid om taal van de e-mail te selecteren.
- De mogelijkheid om de frequentie van de e-mail te selecteren.
- Als maand de dag van de maand kies dan is het:'s gestuurd
- Als Weekly de dag van de week selecteert, dan is het:'s gestuurd
- Laat elke dag toe (dertijd van de dag is niet belangrijk).
- Laat auto-posting toe om een e-mail te sturen wanneer ik blog.
- Laat de gebruiker de categorieën selecteren die hem interesseren.
- Laat eenvoudige afteken toe
- Laat voorskou geven van de e-mail die ze zullen ontvangen
- Laat de gebruiker zijn voorkeuren op elk moment veranderen.
- Een aparte dienst die het verzenden van e-mails hanteert.
- Deze dienst wordt geroepen door de blogdienst wanneer er een nieuwe post is gemaakt.
- Deze dienst stuurt dan de e-mail naar alle abonenten.
- Het gebruikt Hangfire om de uitstuur van e-mails te plannen.
Het gevolg hiervan is dat ik veel werk te doen heb.
De Betalingspagina
Ik begon met het leuke deel: ,, een abonnementspagina schrijven, . Ik wilde dat dit goed werkte op de desktop en ook op mobiele browsers. SPA); van Mijn Umami-analyse. Ik kan zien dat een redelijk deel van de gebruikers toegang tot deze beweging heeft vanuit mobiele apparaten.

Ik wilde ook dat de abonnementspagina duidelijk was hoe je het moet gebruiken. Steve Krug 's "Don'doet me niet aan het denken. Philosophie waarbij een pagina duidelijk moet zijn om te gebruiken en niet het gebruiker moet vragen na te denken over hoe ze te gebruiken.
Dit betekent dat de verstek de meerderheid van de gebruikers zou willen gebruiken.. Ik besloot over de volgende verstek.
- Weekly e-mails
- Engelse taal
- Alle categorieën gekies
- E-mail gestuurd op een maandag
Ik kan dit later veranderen als het ongelijk blijkt..
Dit is de pagina die ik uiteindelijk bouwde.

De code voor de pagina
Zoals bij de rest van deze site wilde ik de code zo eenvoudig mogelijk maken.. Het gebruikt de volgende HTML.
Subscription Page
@using Mostlylucid.Shared
@using Mostlylucid.Shared.Helpers
@model Mostlylucid.EmailSubscription.Models.EmailSubscribeViewModel
<form x-data="{schedule :'@Model.SubscriptionType'}" x-init="$watch('schedule', value => console.log(value))" hx-boost="true" asp-action="Save" asp-controller="EmailSubscription"
hx-target="#contentcontainer" hx-swap="#outerHTML">
<div class="flex flex-col mb-4">
<div class="flex flex-wrap lg:flex-nowrap lg:space-x-4 space-y-4 lg:space-y-0 items-start">
<label class="input input-bordered flex items-center gap-2 mb-2 dark:bg-custom-dark-bg bg-white w-full lg:w-2/3">
<i class='bx bx-envelope'></i>
<input type="email" class="grow text-black dark:text-white bg-transparent border-0"
asp-for="Email" placeholder="Email (optional)"/>
</label>
<div class="grid grid-cols-2 sm:grid-cols-[repeat(auto-fit,minmax(100px,1fr))] w-full lg:w-1/3">
@{
var frequency = Enum.GetValues(typeof(SubscriptionType)).Cast<SubscriptionType>().ToList();
}
@foreach (var freq in frequency)
{
<div class="flex items-center w-auto h-full min-h-[30px] lg:mb-0 mb-3">
<input x-model="schedule" id="@freq" type="radio" value="@freq.ToString()" name="SubscriptionType" class="hidden peer">
<label for="@freq" class="ml-2 text-sm font-medium text-white
bg-blue-dark border-gray-light border rounded-xl px-1 py-2 w-full
peer-checked:text-blue-600 peer-checked:dark:bg-green text-center justify-center">
@freq.EnumDisplayName()
</label>
</div>
}
</div>
</div>
@{
var languages = LanguageConverter.LanguageMap;
}
<div class="grid grid-cols-[repeat(auto-fit,minmax(85px,1fr))] mt-4 gap-2 pl-6 large:pl-0 w-auto">
@foreach (var language in languages)
{
var [email protected] == language.Key ? "checked" : "";
<div class="tooltip lg:mb-0 mb-2" data-tip="@language.Value)" >
<div class="flex items-center justify-center w-[85px] h-full min-h-[70px]">
<input id="@language.Key" type="radio" value="@language.Key" @isChecked name="language" class="hidden peer">
<label for="@language.Key" class="flex flex-col items-center justify-center text-sm font-medium text-white bg-blue-dark opacity-50 peer-checked:opacity-100 w-full h-full">
<img src="/img/flags/@(language.Key).svg" asp-append-version="true" class="border-gray-light border rounded-l w-full h-full object-cover" alt="@language.Value">
</label>
</div>
</div>
}
</div>
<div class="mt-3 border-neutral-400 dark:border-neutral-600 border rounded-lg" x-data="{ hideCategories: false, showCategories: false }">
<h4
class="px-5 py-1 bg-neutral-500 bg-opacity-10 rounded-lg font-body text-primary dark:text-white w-full flex justify-between items-center cursor-pointer"
x-on:click="if(!hideCategories) { showCategories = !showCategories }">
<span class="flex flex-row items-center space-x-1">
Categories
<label class="label cursor-pointer ml-4" x-on:click.stop="">
all
</label>
<input type="checkbox" x-on:click.stop="" x-model="hideCategories" asp-for="AllCategories" class="toggle toggle-info toggle-sm" />
</span>
<span>
<i
class="bx text-2xl"
x-show="!hideCategories"
:class="showCategories ? 'bx-chevron-up' : 'bx-chevron-down'"></i>
</span>
</h4>
<div class="flex flex-wrap gap-2 pt-2 pl-5 pr-5 pb-2"
x-show="showCategories"
x-cloak
x-transition:enter="max-h-0 opacity-0"
x-transition:enter-end="max-h-screen opacity-100"
x-transition:leave="max-h-screen opacity-100"
x-transition:leave-end="max-h-0 opacity-0">
<div class="grid grid-cols-[repeat(auto-fit,minmax(150px,1fr))] mt-4 w-full">
@foreach (var category in Model.Categories)
{
var categoryKey = category.Replace(" ", "_").Replace(".", "_").Replace("-", "_");
<div class="flex items-center w-auto h-full min-h-[50px]">
<input id="@categoryKey" type="checkbox" value="@category" name="@nameof(Model.SelectedCategories)" class="hidden peer">
<label for="@categoryKey" class="ml-2 text-sm font-medium text-white
bg-blue-dark border-gray-light border rounded-xl px-1 py-2 w-full
peer-checked:text-blue-600 peer-checked:dark:bg-green text-center justify-center">
@category
</label>
</div>
}
</div>
</div></div>
<div :class="{ 'opacity-50 pointer-events-none': schedule !== 'Weekly' }" class=" mt-2 border-neutral-400 dark:border-neutral-600 border rounded-lg" >
<h4
class="px-5 py-1 bg-neutral-500 bg-opacity-10 rounded-lg font-body text-primary dark:text-white w-full flex justify-between items-center cursor-pointer"
>
<span class="flex flex-row items-center space-x-1 ">
Day of Week to Send On
</span>
</h4>
<div class="grid grid-cols-3 sm:grid-cols-[repeat(auto-fit,minmax(80px,1fr))] my-2 w-full lg:w-1/2" x-show="schedule === 'Weekly'">
@foreach (var day in Model.DaysOfWeek)
{
var checkedDay = day.ToString() == Model.Day ? "checked" : "";
<div class="flex items-center w-auto h-full min-h-[50px]">
<input id="@day" type="radio" value="@day" name="day" @checkedDay class="hidden peer">
<label for="@day" class="ml-2 text-sm font-medium text-white
bg-blue-dark border-gray-light border rounded-xl px-1 py-2 w-full
peer-checked:text-blue-600 peer-checked:dark:bg-green text-center justify-center">
@day.ToString()
</label>
</div>
}
</div>
</div>
<div :class="{ 'opacity-50 pointer-events-none': schedule !== 'Monthly' }" class=" mt-2 border-neutral-400 dark:border-neutral-600 border rounded-lg" >
<h4
class="px-5 py-1 bg-neutral-500 bg-opacity-10 rounded-lg font-body text-primary dark:text-white w-full flex justify-between items-center cursor-pointer">
<span class="flex flex-row items-center space-x-1 ">
Day of Month to Send On
</span>
</h4>
<div class="grid grid-cols-[repeat(auto-fit,minmax(35px,1fr))] w-full mx-2" x-show="schedule === 'Monthly'">
@for(int i=1; i<32; i++)
{
var checkedMonthDay = i == Model.DayOfMonth ? "checked" : "";
<div class="flex items-center w-auto my-2 h-full min-h-[35px]">
<input id="Day_@i" type="radio" value="@i" name="daypfmonth" @checkedMonthDay class="hidden peer">
<label for="Day_@i" class="ml-2 text-sm font-medium text-white
bg-blue-dark border-gray-light border rounded-xl px-1 py-2 w-full
peer-checked:text-blue-600 peer-checked:dark:bg-green text-center justify-center">
@i.GetOrdinal()
</label>
</div>
}
</div>
</div>
@* Action Buttons *@
<div class="flex flex-row gap-2 mt-4">
<button type="submit" class="btn btn-primary">Subscribe</button>
<button type="reset" class="btn-warning btn">Reset</button>
</div>
</div>
</form>
<div class="grid grid-cols-2 sm:grid-cols-[repeat(auto-fit,minmax(100px,1fr))] w-full lg:w-1/3">
@{
var frequency = Enum.GetValues(typeof(SubscriptionType)).Cast<SubscriptionType>().ToList();
}
@foreach (var freq in frequency)
{
<div class="flex items-center w-auto h-full min-h-[30px] lg:mb-0 mb-3">
<input x-model="schedule" id="@freq" type="radio" value="@freq.ToString()" name="SubscriptionType" class="hidden peer">
<label for="@freq" class="ml-2 text-sm font-medium text-white
bg-blue-dark border-gray-light border rounded-xl px-1 py-2 w-full
peer-checked:text-blue-600 peer-checked:dark:bg-green text-center justify-center">
@freq.EnumDisplayName()
</label>
</div>
}
</div>
Je kunt zien dat dit CSS gebaseerd is, met behulp van het Tailwind-CSS-raamwerk's. peer CSS-toestel om te bepalen dat wanneer het label opklikt, de invoer moet worden ingesteld's gemerkte eigenschap en het moet veranderen's stylingM SK2
Ik gebruik dit later op de pagina om te bepalen welke kieser (Weeksdag / Dag van maand) beschikbaar te maken voor de gebruikers.
<div :class="{ 'opacity-50 pointer-events-none': schedule !== 'Monthly' }" class=" mt-2 border-neutral-400 dark:border-neutral-600 border rounded-lg" >
<h4
class="px-5 py-1 bg-neutral-500 bg-opacity-10 rounded-lg font-body text-primary dark:text-white w-full flex justify-between items-center cursor-pointer">
<span class="flex flex-row items-center space-x-1 ">
Day of Month to Send On
</span>
</h4>
<div class="grid grid-cols-[repeat(auto-fit,minmax(35px,1fr))] w-full mx-2" x-show="schedule === 'Monthly'">
@for(int i=1; i<32; i++)
{
var checkedMonthDay = i == Model.DayOfMonth ? "checked" : "";
<div class="flex items-center w-auto my-2 h-full min-h-[35px]">
<input id="Day_@i" type="radio" value="@i" name="daypfmonth" @checkedMonthDay class="hidden peer">
<label for="Day_@i" class="ml-2 text-sm font-medium text-white
bg-blue-dark border-gray-light border rounded-xl px-1 py-2 w-full
peer-checked:text-blue-600 peer-checked:dark:bg-green text-center justify-center">
@i.GetOrdinal()
</label>
</div>
}
</div>
</div>
Je ziet dat ik een Alpine.js css-template klas heb die de ondoorzichtigheid van het element op 50% stelt en aanwijzer-gebeurtenissen deaktiveert als de schakel niet op Monthly is gesetztM SK2 Dit is een eenvoudige manier om elementen te verbergen die niet nodig zijn.
:class="{ 'opacity-50 pointer-events-none': schedule !== 'Monthly' }"
Het verbergt ook de Monethly-dag-kieser als de tijdschema niet op Monthly is gezet.
x-show="schedule === 'Monthly'"
Zodat ik de achterkant in het volgende bericht zal bedekken.
Volgende stappen
In de volgende post 'll (als ik klaar ben met het bulden) publiceer de nieuwe structurele veranderingen in het project zodat ik een aparte web-applicatie kan gebruiken voor Hanfgire en de e-mail-stuurdienstM SK3 Dit is een substantiele verandering, want ik kom van één project. Mostlylucid Een aantal projecten die het delen van diensten mogelijk maken:
Mostlylucid- Het belangrijkste websiteprojectMostlylucid.SchedulerService- Dit is het belangrijkste Hangfire-project dat de Databasis bereikt.Mostlylucid.Services- Waar de diensten leven die gegevens terugbrengen naar de projecten van het hoogste niveauMostlylucid.Shared- Helders en Konstanten van alle projectenMostlylucid.DbContext- De database-context voor het project
Je ziet dat dit veel meer complexiteit aan de systeemarchitektuur toevoegt, maar in dit geval is het nodig om het project onderhoudbaar en schaalbaar te houden. Ik zal vertellen hoe ik dit deed in de rest van deze serie.
In slotting:
Ik heb nog een ton werk te doen om dit allemaal te laten gebeuren.