)أ( الخدمات المقدمة من فئة الخدمات العامة )الرتب الأخرى( - الاحتياجات والاشتراكات (العربية (Arabic))

)أ( الخدمات المقدمة من فئة الخدمات العامة )الرتب الأخرى( - الاحتياجات والاشتراكات

Comments

NOTE: Apart from English (and even then it's questionable, I'm Scottish). These are machine translated in languages I don't read. If they're terrible please contact me.
You can see how this translation was done in this article.

Saturday, 21 September 2024

//

10 minute read

أولاً

بينما كنت أتصفح مدونات أشخاص آخرين لاحظت أن الكثير منهم لديهم خدمة اشتراكية تسمح للمستخدمين بالتوقيع على إرسال بريد إلكتروني لهم أسبوعياً مع نشرات من تلك المدونة. قررت تنفيذ نسختي الخاصة من هذا و أشارك كيف فعلت ذلك.

ملاحظة: لا أتوقع أن يستخدم أي أحد هذا فعلاً، لدي الكثير من الوقت على يدي بعد أن وقع العقد

)أ))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))

لذا لإنشاء هذه الخدمة قررت على المتطلبات التالية.

  1. (ب) صفحة اشتراك مرنة تتيح للمستعملين بعض المرونة.
    1. المستخدمون فقط وضعوا في البريد الإلكتروني.
    2. الـ قدرة تحديد لغة من بريد إلكتروني.
    3. الـ إلى تحديد تكرار من بريد إلكتروني.
      • إذا كان الشهر يختار يوم الشهر الذي يرسله.
      • إذا اختار الأسبوعي يوم الأسبوع الذي أرسله.
      • الرخصة اليومية (وقت اليوم غير مهم).
      • السماح للإحالة الذاتية لإرسال البريد عندما أدوّن.
    4. اسمح للمستعمل أن يختار الفئات التي يهمها
  2. اسمح
  3. اسمح معاينة من البريد الذي سيُنقِص
  4. السماح للمستخدم بتغيير تفضيلاتهم في أي وقت.
  5. خدمة مجزأة تتولى إرسال البريد الإلكتروني.
    • تقوم دائرة المدونات بالدعوة إلى هذه الخدمة كلما تم إنشاء وظيفة جديدة.
    • وسوف ترسل هذه الخدمة بعد ذلك البريد الإلكتروني إلى جميع المشتركين.
    • وسوف تستخدم Thalthfire لجدولة إرسال البريد الإلكتروني.

إن تأثير هذا هو أن لدي الكثير من العمل للقيام به.

إن مجلس الإدارة،

بدأت مع الجزء الممتع، كتابة صفحة الاشتراك. أردت لهذا أن يعمل بشكل جيد على أسطح المكتب وكذلك على المتصفحات؛ من تحليل أميمي أستطيع أن أرى نسبة عادلة من المستخدمين الوصول إلى هذه الحركة من الأجهزة المحمولة.

منصات ما قبل الولادة

كما أردت أيضاً أن تكون صفحة الإشتراك واضحة كيفية استخدامها، أنا مؤمن كبير في (ستيف كروج) "لا تجعلني أفكر" فلسفة حيث يجب أن تكون الصفحة واضحة للاستخدام وليس مطالبة المستخدم للتفكير في كيفية استخدامها.

وهذا يعني أنه ينبغي أن تكون القيم الافتراضية هي غالبية المستخدمين الذين سيرغبون في استخدامها. قرّرتُ على الإفتراضات التالية:

  1. ما يُد
  2. اللغة الإنكليزية
  3. أُرسِل البريد الإلكتروني:

ويمكنني بالطبع أن أغير هذا لاحقا إذا ثبت أن هذه غير صحيحة.

إذاً هذه هي الصفحة التي انتهيت من بنائها:

أولاً - مقدمـة

رمز الصفحة

كما هو الحال مع بقية هذا الموقع أردت أن أجعل الرمز بسيطاً قدر الإمكان وهو يستخدم 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>
يمكنكم ان تروا ان هذا بسيط كما هو وهو يستخدم الألبين-js للتعامل مع جميع تفاعلات المستخدم وعنصر UI مشترك لكل الاختيارات.
<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>

يمكنك أن ترى أن هذا مبني على CSSS، باستخدام إطار Tailwind CSS peer س س س إلى تحديد مُنتَزَر على المُشَارَعَة يجب أن تُعِدّ خِصْيَّة المُدخِل المُحَكَّمَة و تُغيِّر مُشَغِّلَتها.

استخدم هذا لاحقاً في الصفحة لتحديد أي الانتقاء (يوم الأسبوع/يوم الشهر) لجعله متاحاً للمستخدمين إظهار العناصر التي تسمح بالانتقاء.

<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>

يمكنك أن ترى أن لدي Alpin.js css comp صنف يحدد عدم وضوح العنصر إلى 50% و مؤشر تعطيل الأحداث إذا لم يكن الجدول مُحدداً للشهرية. وهذه طريقة بسيطة لإخفاء العناصر التي لا حاجة إليها.

:class="{ 'opacity-50 pointer-events-none': schedule !== 'Monthly' }" 

كما انه يخفي مختار يوم الجبلي اذا لم يكن الجدول مبرمجا للشهرية.

x-show="schedule === 'Monthly'"

اذاً هذا هو jist لصفحة الجدول سأغطّي الخلفية في الموقع التالي.

الخطوات المقبلة

في المقال التالي سأقوم (عندما أنهي مسحه) بوضع التغييرات الهيكلية الجديدة في المشروع للسماح لي باستخدام تطبيق منفصل على شبكة الإنترنت لـ Hanfgire وخدمة إرسال البريد الإلكتروني. هذا تغيير كبير عندما انتقل من مشروع واحد Mostlylucid عدد من المشاريع التي تسمح بتقاسم الخدمات:

  1. Mostlylucid - مشروع الموقع الرئيسي.

  2. Mostlylucid.SchedulerService - هذا هو مشروع "هالفاير" الرئيسي الذي سيدعم قاعدة البيانات، ويبني البريد الإلكتروني ويرسلها.

  3. Mostlylucid.Services - مكان إقامة الخدمات التي تعيد البيانات إلى مشاريع المستوى الأعلى

  4. Mostlylucid.Shared - المساعدة والثوابت المستخدمة في جميع المشاريع.

  5. Mostlylucid.DbContext - قاعدة بيانات سياق المشروع.

يمكنكم أن تروا أن هذا يضيف تعقيداً أكبر بكثير إلى بنية النظام، لكن في هذه الحالة من الضروري إبقاء المشروع قابلاً للصيانة والتعديل. سأغطي كيف فعلت هذا في بقية هذه السلسلة.

في الإستنتاج

لا يزال لدي الكثير من العمل للقيام به لجعل كل هذا يحدث. إعادة التصنيع معقدة إلى حد ما لأنها تنطوي على إضافة طبقات متعددة إلى النظام (ومفاهيم مثل DTOs إلى المشروع) ولكن أعتقد أنه يستحق ذلك على المدى الطويل.

logo

©2024 Scott Galloway