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

# **پروژهی اولیه**
برای دانلود **پروژهی اولیه** روی [این لینک](/problemset/assignments/4367/download_problem_initial_project/316814/) کلیک کنید.
<details class="red">
<summary>**ساختار فایلها**</summary>
```
little_shop
├─ api/
│ ├─ <mark class="orange" title="این فایل را تکمیل کنید">getAllProducts.js</mark>
│ └─ <mark class="orange" title="این فایل را تکمیل کنید">getProductById.js</mark>
├─ data/
│ └─ mockProducts.js
├─ images/
│ ├─ headphone.webp
│ ├─ laptop.jpg
│ └─ s25.png
├─ scripts/
│ ├─ <mark class="orange" title="این فایل را تکمیل کنید">checkout.js</mark>
│ ├─ <mark class="orange" title="این فایل را تکمیل کنید">index.js</mark>
│ └─ <mark class="orange" title="این فایل را تکمیل کنید">productDetail.js</mark>
├─ styles/
│ ├─ spinner.css
│ └─ style.css
├─ checkout.html
├─ index.html
├─ productDetail.html
└─ success.html
```
</details>
# **جزئیات پیادهسازی**
در گیفهای زیر میتوانید فرایندهای مختلف برنامه را مشاهده کنید:
+ صفحهی لیست محصولات

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

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

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

طبق توضیحات زیر، باید فایلهای موجود در **دایرکتوری** `api` را ایجاد و تکمیل کنید.
<details class="purple">
<summary>**پیادهسازی دایرکتوری** `api`</summary>
در **فایل** `mockProducts`، یک آرایه از محصولات تعریف شده است.
## **پیادهسازی پرامیسها**
در این بخش، باید در **دایرکتوری** `api` **دو فایل جاوااسکریپت** با نامهای زیر ایجاد کنید:
1. `getAllProducts.js`
2. `getProductById.js`
<details class="yellow">
<summary>**پیادهسازی فایل** `getAllProducts`</summary>
در این فایل باید **تابعی** پیادهسازی شود که **تمام محصولات** را در صورت *resolve* شدن، بازگرداند.
در صورتی که *reject* شود، باید شیئی شامل موارد زیر بازگردانده شود:
+ `status`**: عدد** `401`
+ `data`**: آرایهای خالی** `[]`
+ `message`**: یک رشته با محتوای زیر**
```plaintext
محصولی یافت نشد، لطفا دوباره تلاش بکنید.
```
## **جزئیات پیادهسازی**
1. این تابع **باید** با استفاده از *Promise* پیادهسازی شود.
2. برای **شبیهسازی تاخیر پاسخ سرور،** از `setTimeout` با مدت زمان **۸۰۰ میلیثانیه** استفاده کنید.
3. درون این تاخیر، یک **مقدار تصادفی** با `Math.random()` تولید میشود:
+ اگر مقدار تصادفی **کمتر** از `0.5` باشد، *Promise* با دادههای `mockProducts` *resolve* شود.
+ در غیر این صورت، *Promise* با شیء خطا (دارای `status`، `data` و `message`) *reject* شود.
</details>
<details class="yellow">
<summary>**پیادهسازی فایل** `getProductById`</summary>
در این فایل باید **تابعی** پیادهسازی شود که **محصول مورد نظر را با استفاده از** `id` همان محصول **پیدا کند.**
در صورت *resolve* شدن، محصول پیدا شده **بازگردانده** میشود.
در صورت *reject* شدن، باید شیئی شامل موارد زیر **بازگردانده** شود:
+ `status`: عدد `404`
+ `message`: یک رشته با محتوای زیر:
```plaintext
محصول مورد نظر یافت نشد.
```
## **جزئیات پیادهسازی**
1. این تابع **باید** با استفاده از *Promise* پیادهسازی شود.
2. برای شبیهسازی تاخیر پاسخ سرور، از `setTimeout` با مدت زمان **۸۰۰ میلیثانیه** استفاده کنید.
3. درون این تاخیر، **یک مقدار تصادفی با** `Math.random()` تولید میشود:
+ اگر مقدار تصادفی **کمتر** از `0.5` باشد **و** محصولی با `id` مورد نظر پیدا شود، *Promise* با محصول مورد نظر *resolve* شود.
+ **در غیر این صورت،** *Promise* با شیء خطا (دارای `status` و `message`) *reject* شود.
</details>
+ **نکته:** این منطق به شما کمک میکند **رفتار واقعی یک درخواست شبکه** *(موفق یا ناموفق بودن پاسخ سرور)* را شبیهسازی کنید.
</details>
طبق توضیحات زیر، باید فایلهای موجود در **دایرکتوری** `scripts` را تکمیل کنید.
+ **توجه:** در فایلها، کامنتهای راهنما درج شدهاند که به شما در تکمیل توابع کمک میکنند.
<details class="purple">
<summary>**پیادهسازی فایل** `index.js`</summary>
در این فایل هدف شما این است که **محصولات** را از `getAllProducts` دریافت کرده، نمایش دهید و وضعیت **بارگذاری** و **خطا** را مدیریت کنید.
1. ابتدا باید **سه المنت** را از صفحه `index.html` دریافت کنید:
+ `spinner`: برای **نمایش لودر** در هنگام بارگذاری.
+ `errorBox`: برای **نمایش پیام خطا.**
+ `productsContainer`: **برای نمایش لیست محصولات.**
<details class="yellow">
<summary>**پیادهسازی تابع** `renderProducts`</summary>
این تابع مسئول **نمایش محصولات** است.
ورودی تابع یک آرایه از محصولات است و خروجی آن افزودن کارتهای محصولات به `productsContainer` میباشد.
### وظایف تابع:
1. برای هر محصول یک **کارت محصول** بسازید.
2. اطلاعات نمایش داده شده برای هر محصول شامل موارد زیر باشد:
+ تصویر (`image`) با کلاس `card-img`
+ عنوان محصول (`title`)
+ توضیحات محصول (`description`)
+ قیمت محصول (`price`) با فرمت `toLocaleString()` و واحد تومان
+ دستهبندی محصول (`category`)
+ وضعیت موجودی (`storage`) با کلاسهای `instock` یا `outstock` و متن مناسب:
+ موجود در انبار → `موجود در انبار`
+ ناموجود → `ناموجود`
3. هر کارت باید روی کلیک کاربر، به صفحه جزئیات محصول (`productDetail.html`) با `id` همان محصول هدایت شود.
ساختار کارت محصول به شکل زیر است
```js
<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>
```
</details>
<details class="yellow">
<summary>**تابع loadProducts**</summary>
این تابع **مسئول مدیریت بارگذاری و نمایش خطا** است و از `async/await` برای دریافت محصولات استفاده میکند.
### وظایف تابع:
1. هنگام شروع بارگذاری:
+ نمایش `spinner` → حذف کلاس `hidden`
+ مخفی کردن `errorBox` و `productsContainer` → افزودن کلاس `hidden`
2. فراخوانی تابع `getAllProducts()` و دریافت آرایه محصولات
3. نمایش محصولات با استفاده از `renderProducts`
4. پس از بارگذاری:
+ مخفی کردن `spinner`
+ اگر آرایه محصولات خالی بود → نمایش پیام خطا `"هیچ محصولی یافت نشد."`
+ در غیر این صورت → نمایش `productsContainer`
5. مدیریت خطا:
+ مخفی کردن `spinner`
+ نمایش پیام خطا از `err.message` در `errorBox`
</details>
**نکته:** فراموش نکنید که **تابع** `loadProducts` را فراخوانی کنید!
**نکته:** در داخل تابع `loadProducts` باید از *try/catch* برای **مدیریت خطاها** استفاده شود.
</details>
<details class="purple">
<summary>**فایل** `productDetail.js`</summary>
این فایل مسئول **نمایش جزئیات یک محصول** است که با استفاده از شناسه (`id`) محصول، اطلاعات آن را از *API* دریافت کرده و در صفحه نمایش میدهد.
1. ابتدا باید سه المنت را از صفحه `productDetail.html` دریافت کنید:
+ `spinner`: **برای نمایش لودر در هنگام بارگذاری.**
+ `errorBox`: **برای نمایش پیام خطا.**
+ `productDetail`: **برای نمایش محصول.**
<details class="yellow">
<summary>**تابع** `getIdFromUrl`</summary>
این تابع مسئول **استخراج شناسه محصول** (`id`) از *URL* صفحه است.
</details>
<details class="yellow">
<summary>**تابع** `renderProduct`</summary>
این تابع وظیفه نمایش جزئیات محصول در صفحه را دارد.
### **وظایف تابع:**
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` هدایت شود.
ساختار جزئیات هر محصول:
```js
<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>
```
</details>
<details class="yellow">
<summary>**تابع** `loadProduct`</summary>
این تابع مسئول **مدیریت بارگذاری محصول و نمایش خطا** است و از `async/await` استفاده میکند.
### وظایف تابع:
1. هنگام شروع بارگذاری:
+ نمایش `spinner`
+ مخفی کردن `errorBox` و `productDetail`
+ پاک کردن محتوای قبلی `productDetail`
2. دریافت `id` از *URL* با `getIdFromUrl()`
3. فراخوانی `getProductById(id)` برای دریافت محصول
4. در صورت موفقیت، **فراخوانی** `renderProduct(product)`
5. در صورت خطا، **نمایش پیام خطا** در `errorBox`
6. در نهایت، **مخفی کردن** `spinner`
</details>
**نکته:** فراموش نکنید که تابع `loadProduct` را فراخوانی کنید!
**نکته:** در داخل **تابع** `loadProduct` باید از *try/catch* برای مدیریت خطاها استفاده شود.
</details>
<details class="purple">
<summary>**فایل** `checkout.js`</summary>
+ این فایل مسئول مدیریت **فرم چندمرحلهای پرداخت** *(Checkout)* است و شامل منطق تغییر مراحل، اعتبارسنجی فیلدها و نمایش خلاصه سفارش است. المانهایی که باید با آنها کار کنید به شکل زیر است:
```js
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"); // خطوط پروگرس
```
<details class="yellow">
<summary>**تابع** `getIdFromUrl`</summary>
این `id` برای **دریافت اطلاعات محصول** از *API* در مرحله خلاصه استفاده میشود.
+ در صورت **نبودن** `id` در *URL،* اطلاعات محصول در مرحله خلاصه با **علامت** "—" نمایش داده میشود.
</details>
<details class="yellow">
<summary>**تابع** *showStep*</summary>
این تابع **مرحله جاری فرم را نمایش میدهد**:
+ فرم مربوط به مرحله جاری **فعال** میشود (`active`)
+ مراحل تکمیل شده و خطوط پروگرس نیز **فعال** میشوند
+ اگر مرحله آخر *(Step 3)* باشد، تابع `fillSummary` فراخوانی میشود
**کلاسهای CSS:**
| کلاس | کاربرد |
| ------------- | ------------------------ |
| `active` | نمایش فرم یا مرحله فعال |
| `line active` | رنگ و پر شدن خطوط پروگرس |
+ **نکته:** مرحله بعد تنها زمانی قابل نمایش است که **تمام ورودیهای مرحله جاری پر شده باشند**.
</details>
<details class="yellow">
<summary>**اعتبارسنجی** `validateStep`</summary>
**تابع** `validateStep(index)`:
+ بررسی همه ورودیها، سلکتها و تکستاریاهایی که `required` هستند
+ اگر ورودی خالی باشد، **کلاس** `error` **اضافه** میشود و **فوکوس** روی آن قرار میگیرد
+ اگر ورودی پر باشد، **کلاس** `error` **حذف** میشود
+ اگر همه ورودیها پر باشند، `true` **برمیگرداند** و **اجازه رفتن به مرحله بعد داده میشود**
**کلاس CSS:**
+ `error` نمایش ورودی با خطا (مثلاً رنگ قرمز)
+ **توجه:** کاربر نمیتواند به مرحله بعد برود تا زمانی که تمام فیلدهای الزامی پر شده باشند.
</details>
<details class="yellow">
<summary>**توضیح مراحل فرم**</summary>
| مرحله | شرح |
| ------------------------ | ---------------------------------------------------------------------------------------------- |
| **Step 1: اطلاعات شخصی** | ورودیها: `name`، `lastname`، `code`، `phone` (تمامی فیلدها `required`) |
| **Step 2: اطلاعات آدرس** | ورودیها: `province`، `city`، `address` (تمامی فیلدها `required`) |
| **Step 3: خلاصه خرید** | نمایش اطلاعات جمعآوریشده از مراحل ۱ و ۲ و اطلاعات محصول از API. دکمه `submit` برای ثبت نهایی |
+ ** توجه:** مرحله ۳ صرفاً نمایش اطلاعات است و ورودی جدیدی ندارد.
</details>
<details class="yellow">
<summary>**پر کردن خلاصه سفارش** `fillSummary`</summary>
+ **اطلاعات شخصی** و **آدرس** از فرمهای مراحل قبل خوانده میشوند
+ **اطلاعات محصول** با استفاده از `getProductById` دریافت میشوند
+ اطلاعات در المنتهای خلاصه (`summary-*`) نمایش داده میشوند
**مثال المنتها:**
```js
document.getElementById("summary-name").textContent = ...;
document.getElementById("summary-product").textContent = ...;
document.getElementById("summary-price").textContent = ...;
```
+ **توجه:** در صورت خطای دریافت محصول، پیام مناسب نمایش داده شود.
</details>
<details class="yellow">
<summary>**دکمههای** *Next* **و** *Prev*</summary>
1. **دکمهی Next:** ابتدا `validateStep(currentStep)` اجرا میشود و فقط در صورت اعتبارسنجی موفق، به مرحله بعد میرود
2. **دکمهی Prev:** بدون اعتبارسنجی به مرحله قبل برمیگردد
</details>
<details class="yellow">
<summary>**کلیک روی مراحل Stepper**</summary>
1. کاربر میتواند فقط به مراحل تکمیل شده یا فعلی برود
2. اگر مرحله بالاتر را انتخاب کند، ابتدا اعتبارسنجی همه مراحل قبل انجام میشود
</details>
<details class="yellow">
<summary>**دکمهی submit**</summary>
1. بررسی اعتبارسنجی مرحله آخر
2. در صورت موفقیت، هدایت به صفحه `success.html`
</details>
<details class="yellow">
<summary>**مدیریت ورودیها**</summary>
1. هر ورودی با `required` هنگام تغییر مقدار، کلاس `error` حذف میشود
2. **جلوگیری** از نمایش خطا پس از اصلاح ورودی توسط کاربر
</details>
<details class="blue">
<summary>**کلاسهای مهم** *CSS*</summary>
| کلاس | کاربرد |
| --------------- | -------------------------- |
| `active` | فرم یا مرحله فعال |
| `error` | ورودی اشتباه |
| `line active` | پر شدن خطوط پروگرس |
| `next` / `prev` | دکمههای ناوبری بین مراحل |
| `submit` | دکمه ثبت نهایی |
| `summary-*` | نمایش اطلاعات جمعآوری شده |
</details>
</details>
# **آنچه باید آپلود کنید**
+ **توجه**: پس از اعمال تغییرات، فایلهای داخل `scripts` و `api` را _Zip_ کرده و آپلود کنید. **همانند پروژه اولیه در فایل زیپ شده نباید کد در پوشهی دیگری قرار بگیرد در غیر این صورت سیستم داوری فایل را شناسایی نکرده و نمرهای دریافت نخواهید کرد.**
<details class="red">
<summary>**ساختار مناسب برای آپلود پاسخ**</summary>
```
answer/
├─ api/
│ ├─ getAllProducts.js
│ └─ getProductById.js
└─ scripts/
├─ checkout.js
├─ index.js
└─ productDetail.js
```
</details>