خسته از هرچی که بود، خسته از هرچی که هست...

فرانتیوم امروز واقعاً از این وضعیت خسته شده بود! با خودش گفت: «چرا برای یه خرید ساده باید توی این گرمای تابستون، حتماً تا فروشگاه برم؟ اگه می‌تونستم خیلی راحت برم توی سایت، محصولم رو ببینم، آدرسم رو وارد کنم و تموم! چی می‌شد؟»

فرانتیوم فهمید که غر زدن فایده نداره. وقتشه خودش دست‌به‌کار بشه و فروشگاه آنلاین خودش رو راه بندازه!

توضیح تصویر

پروژه‌ی اولیه

برای دانلود پروژه‌ی اولیه روی این لینک کلیک کنید.

ساختار فایل‌ها

little_shop
├─ api/
│  ├─ getAllProducts.js
│  └─ getProductById.js
├─ data/
│  └─ mockProducts.js
├─ images/
│  ├─ headphone.webp
│  ├─ laptop.jpg
│  └─ s25.png
├─ scripts/
│  ├─ checkout.js
│  ├─ index.js
│  └─ productDetail.js
├─ styles/
│  ├─ spinner.css
│  └─ style.css
├─ checkout.html
├─ index.html
├─ productDetail.html
└─ success.html

جزئیات پیاده‌سازی

در گیف‌های زیر می‌توانید فرایند‌های مختلف برنامه را مشاهده کنید:

  • صفحه‌ی لیست محصولات

توضیح تصویر

  • صفحه‌ی جزئیات محصول

توضیح تصویر

  • رفتن به صفحه‌ی تکمیل پرداخت

توضیح تصویر

  • فرایند استپر

توضیح تصویر

طبق توضیحات زیر، باید فایل‌های موجود در دایرکتوری api را ایجاد و تکمیل کنید.

پیاده‌سازی دایرکتوری api

در فایل mockProducts، یک آرایه از محصولات تعریف شده است.

پیاده‌سازی پرامیس‌ها

در این بخش، باید در دایرکتوری api دو فایل جاوااسکریپت با نام‌های زیر ایجاد کنید:

  1. getAllProducts.js

  2. getProductById.js

پیاده‌سازی فایل getAllProducts

در این فایل باید تابعی پیاده‌سازی شود که تمام محصولات را در صورت resolve شدن، بازگرداند. در صورتی که reject شود، باید شیئی شامل موارد زیر بازگردانده شود:

  • status: عدد 401

  • data: آرایه‌ای خالی []

  • message: یک رشته با محتوای زیر

محصولی یافت نشد، لطفا دوباره تلاش بکنید.

جزئیات پیاده‌سازی

  1. این تابع باید با استفاده از Promise پیاده‌سازی شود.

  2. برای شبیه‌سازی تاخیر پاسخ سرور، از setTimeout با مدت زمان ۸۰۰ میلی‌ثانیه استفاده کنید.

  3. درون این تاخیر، یک مقدار تصادفی با Math.random() تولید می‌شود:

    • اگر مقدار تصادفی کمتر از 0.5 باشد، Promise با داده‌های mockProducts resolve شود.
    • در غیر این صورت، Promise با شیء خطا (دارای status، data و message) reject شود.

پیاده‌سازی فایل getProductById

در این فایل باید تابعی پیاده‌سازی شود که محصول مورد نظر را با استفاده از id همان محصول پیدا کند. در صورت resolve شدن، محصول پیدا شده بازگردانده می‌شود. در صورت reject شدن، باید شیئی شامل موارد زیر بازگردانده شود:

  • status: عدد 404
  • message: یک رشته با محتوای زیر:
محصول مورد نظر یافت نشد.

جزئیات پیاده‌سازی

  1. این تابع باید با استفاده از Promise پیاده‌سازی شود.

  2. برای شبیه‌سازی تاخیر پاسخ سرور، از setTimeout با مدت زمان ۸۰۰ میلی‌ثانیه استفاده کنید.

  3. درون این تاخیر، یک مقدار تصادفی با Math.random() تولید می‌شود:

    • اگر مقدار تصادفی کمتر از 0.5 باشد و محصولی با id مورد نظر پیدا شود، Promise با محصول مورد نظر resolve شود.
    • در غیر این صورت، Promise با شیء خطا (دارای status و message) reject شود.
  • نکته: این منطق به شما کمک می‌کند رفتار واقعی یک درخواست شبکه (موفق یا ناموفق بودن پاسخ سرور) را شبیه‌سازی کنید.

طبق توضیحات زیر، باید فایل‌های موجود در دایرکتوری scripts را تکمیل کنید.

  • توجه: در فایل‌ها، کامنت‌های راهنما درج شده‌اند که به شما در تکمیل توابع کمک می‌کنند.

پیاده‌سازی فایل index.js

در این فایل هدف شما این است که محصولات را از getAllProducts دریافت کرده، نمایش دهید و وضعیت بارگذاری و خطا را مدیریت کنید.

  1. ابتدا باید سه المنت را از صفحه index.html دریافت کنید:
  • spinner: برای نمایش لودر در هنگام بارگذاری.
  • errorBox: برای نمایش پیام خطا.
  • productsContainer: برای نمایش لیست محصولات.

پیاده‌سازی تابع renderProducts

این تابع مسئول نمایش محصولات است. ورودی تابع یک آرایه از محصولات است و خروجی آن افزودن کارت‌های محصولات به productsContainer می‌باشد.

وظایف تابع:

  1. برای هر محصول یک کارت محصول بسازید.

  2. اطلاعات نمایش داده شده برای هر محصول شامل موارد زیر باشد:

    • تصویر (image) با کلاس card-img

    • عنوان محصول (title)

    • توضیحات محصول (description)

    • قیمت محصول (price) با فرمت toLocaleString() و واحد تومان

    • دسته‌بندی محصول (category)

    • وضعیت موجودی (storage) با کلاس‌های instock یا outstock و متن مناسب:

      • موجود در انبار → موجود در انبار

      • ناموجود → ناموجود

  3. هر کارت باید روی کلیک کاربر، به صفحه جزئیات محصول (productDetail.html) با id همان محصول هدایت شود.

ساختار کارت محصول به شکل زیر است

 <div class="card"">
        <img src="${p.image}" alt="${p.title}" class="card-img"/>
        <h2>${p.title}</h2>
        <p>${p.description}</p>
        <span class="price">قیمت: ${p.price.toLocaleString()} تومان</span>
        <span class="category">دسته‌بندی: ${p.category}</span>
        <span class="${p.storage ? "instock" : "outstock"}">
          ${p.storage ? "موجود در انبار" : "ناموجود"}
        </span>
      </div>

تابع loadProducts

این تابع مسئول مدیریت بارگذاری و نمایش خطا است و از async/await برای دریافت محصولات استفاده می‌کند.

وظایف تابع:

  1. هنگام شروع بارگذاری:

    • نمایش spinner → حذف کلاس hidden

    • مخفی کردن errorBox و productsContainer → افزودن کلاس hidden

  2. فراخوانی تابع getAllProducts() و دریافت آرایه محصولات

  3. نمایش محصولات با استفاده از renderProducts

  4. پس از بارگذاری:

    • مخفی کردن spinner

    • اگر آرایه محصولات خالی بود → نمایش پیام خطا "هیچ محصولی یافت نشد."

    • در غیر این صورت → نمایش productsContainer

  5. مدیریت خطا:

    • مخفی کردن spinner

    • نمایش پیام خطا از err.message در errorBox

نکته: فراموش نکنید که تابع loadProducts را فراخوانی کنید!

نکته: در داخل تابع loadProducts باید از try/catch برای مدیریت خطاها استفاده شود.

فایل productDetail.js

این فایل مسئول نمایش جزئیات یک محصول است که با استفاده از شناسه (id) محصول، اطلاعات آن را از API دریافت کرده و در صفحه نمایش می‌دهد.

  1. ابتدا باید سه المنت را از صفحه productDetail.html دریافت کنید:
  • spinner: برای نمایش لودر در هنگام بارگذاری.
  • errorBox: برای نمایش پیام خطا.
  • productDetail: برای نمایش محصول.

تابع getIdFromUrl

این تابع مسئول استخراج شناسه محصول (id) از URL صفحه است.

تابع renderProduct

این تابع وظیفه نمایش جزئیات محصول در صفحه را دارد.

وظایف تابع:

  1. مخفی کردن errorBox و پاک کردن پیام‌های خطا

  2. نمایش productDetail

  3. افزودن محتوای محصول شامل:

    • تصویر محصول (product.image) با جایگزین پیش‌فرض "images/placeholder.png"
    • عنوان محصول (product.title)
    • توضیحات محصول (product.description)
    • قیمت محصول (product.price.toLocaleString())
    • دسته‌بندی محصول (product.category)
    • وضعیت موجودی محصول (product.storage)
  4. افزودن دکمه خرید (buy-btn) که با کلیک کاربر به صفحه checkout.html با همان id هدایت شود.

ساختار جزئیات هر محصول:

    <img src="${product.image || "images/placeholder.png"}" alt="${
    product.title
  }" />
    <h2>${product.title}</h2>
    <p class="description">${product.description}</p>
    <p><strong>قیمت:</strong> ${product.price.toLocaleString()} تومان</p>
    <p><strong>دسته‌بندی:</strong> ${product.category}</p>
    <p><strong>وضعیت:</strong> ${
      product.storage ? "✅ موجود در انبار" : "❌ ناموجود"
    }</p>
    <button 
      class="buy-btn" 
      onclick="window.location.href='checkout.html?id=${encodeURIComponent(
        product.id
      )}'">
      خرید محصول
    </button>

تابع loadProduct

این تابع مسئول مدیریت بارگذاری محصول و نمایش خطا است و از async/await استفاده می‌کند.

وظایف تابع:

  1. هنگام شروع بارگذاری:

    • نمایش spinner
    • مخفی کردن errorBox و productDetail
    • پاک کردن محتوای قبلی productDetail
  2. دریافت id از URL با getIdFromUrl()

  3. فراخوانی getProductById(id) برای دریافت محصول

  4. در صورت موفقیت، فراخوانی renderProduct(product)

  5. در صورت خطا، نمایش پیام خطا در errorBox

  6. در نهایت، مخفی کردن spinner

نکته: فراموش نکنید که تابع loadProduct را فراخوانی کنید!

نکته: در داخل تابع loadProduct باید از try/catch برای مدیریت خطاها استفاده شود.

فایل checkout.js

  • این فایل مسئول مدیریت فرم چندمرحله‌ای پرداخت (Checkout) است و شامل منطق تغییر مراحل، اعتبارسنجی فیلدها و نمایش خلاصه سفارش است. المان‌هایی که باید با آن‌ها کار کنید به شکل زیر است:
const steps = document.querySelectorAll(".step");           // مراحل پروگرس
const forms = document.querySelectorAll(".form-step");      // فرم‌های هر مرحله
const nextBtns = document.querySelectorAll(".next");        // دکمه‌های مرحله بعد
const prevBtns = document.querySelectorAll(".prev");        // دکمه‌های مرحله قبل
const lines = document.querySelectorAll(".line");           // خطوط پروگرس

تابع getIdFromUrl

این id برای دریافت اطلاعات محصول از API در مرحله خلاصه استفاده می‌شود.

  • در صورت نبودن id در URL، اطلاعات محصول در مرحله خلاصه با علامت "—" نمایش داده می‌شود.

تابع showStep

این تابع مرحله جاری فرم را نمایش می‌دهد:

  • فرم مربوط به مرحله جاری فعال می‌شود (active)
  • مراحل تکمیل شده و خطوط پروگرس نیز فعال می‌شوند
  • اگر مرحله آخر (Step 3) باشد، تابع fillSummary فراخوانی می‌شود

کلاس‌های CSS:

کلاس کاربرد
active نمایش فرم یا مرحله فعال
line active رنگ و پر شدن خطوط پروگرس
  • نکته: مرحله بعد تنها زمانی قابل نمایش است که تمام ورودی‌های مرحله جاری پر شده باشند.

اعتبارسنجی validateStep

تابع validateStep(index):

  • بررسی همه ورودی‌ها، سلکت‌ها و تکست‌اریاهایی که required هستند
  • اگر ورودی خالی باشد، کلاس error اضافه می‌شود و فوکوس روی آن قرار می‌گیرد
  • اگر ورودی پر باشد، کلاس error حذف می‌شود
  • اگر همه ورودی‌ها پر باشند، true برمی‌گرداند و اجازه رفتن به مرحله بعد داده می‌شود

کلاس CSS:

  • error نمایش ورودی با خطا (مثلاً رنگ قرمز)

  • توجه: کاربر نمی‌تواند به مرحله بعد برود تا زمانی که تمام فیلد‌های الزامی پر شده باشند.

توضیح مراحل فرم

مرحله شرح
Step 1: اطلاعات شخصی ورودی‌ها: name، lastname، code، phone (تمامی فیلدها required)
Step 2: اطلاعات آدرس ورودی‌ها: province، city، address (تمامی فیلدها required)
Step 3: خلاصه خرید نمایش اطلاعات جمع‌آوری‌شده از مراحل ۱ و ۲ و اطلاعات محصول از API. دکمه submit برای ثبت نهایی
  • ** توجه:** مرحله ۳ صرفاً نمایش اطلاعات است و ورودی جدیدی ندارد.

پر کردن خلاصه سفارش fillSummary

  • اطلاعات شخصی و آدرس از فرم‌های مراحل قبل خوانده می‌شوند

  • اطلاعات محصول با استفاده از getProductById دریافت می‌شوند

  • اطلاعات در المنت‌های خلاصه (summary-*) نمایش داده می‌شوند

مثال المنت‌ها:

document.getElementById("summary-name").textContent = ...;
document.getElementById("summary-product").textContent = ...;
document.getElementById("summary-price").textContent = ...;
  • توجه: در صورت خطای دریافت محصول، پیام مناسب نمایش داده شود.

دکمه‌های Next و Prev

  1. دکمه‌ی Next: ابتدا validateStep(currentStep) اجرا می‌شود و فقط در صورت اعتبارسنجی موفق، به مرحله بعد می‌رود

  2. دکمه‌ی Prev: بدون اعتبارسنجی به مرحله قبل برمی‌گردد

کلیک روی مراحل Stepper

  1. کاربر می‌تواند فقط به مراحل تکمیل شده یا فعلی برود
  2. اگر مرحله بالاتر را انتخاب کند، ابتدا اعتبارسنجی همه مراحل قبل انجام می‌شود

دکمه‌ی submit

  1. بررسی اعتبارسنجی مرحله آخر
  2. در صورت موفقیت، هدایت به صفحه success.html

مدیریت ورودی‌ها

  1. هر ورودی با required هنگام تغییر مقدار، کلاس error حذف می‌شود
  2. جلوگیری از نمایش خطا پس از اصلاح ورودی توسط کاربر

کلاس‌های مهم CSS

کلاس کاربرد
active فرم یا مرحله فعال
error ورودی اشتباه
line active پر شدن خطوط پروگرس
next / prev دکمه‌های ناوبری بین مراحل
submit دکمه ثبت نهایی
summary-* نمایش اطلاعات جمع‌آوری شده

آن‌چه باید آپلود کنید

  • توجه: پس از اعمال تغییرات، فایل‌های داخل scripts و api را Zip کرده و آپلود کنید. همانند پروژه اولیه در فایل زیپ شده نباید کد در پوشه‌ی دیگری قرار بگیرد در غیر این صورت سیستم داوری فایل را شناسایی نکرده و نمره‌ای دریافت نخواهید کرد.

ساختار مناسب برای آپلود پاسخ

answer/
├─ api/
│  ├─ getAllProducts.js
│  └─ getProductById.js
└─ scripts/
   ├─ checkout.js
   ├─ index.js
   └─ productDetail.js
ارسال پاسخ برای این سؤال
فایلی انتخاب نشده است.