![ظاهر برنامه](https://quera.org/qbox/view/zgfEDANryK/final.gif)
چند وقت پیش عرفان پروژهای را قبول کرد؛ پروژهای که در آن باید صفحهی یک مقاله را با ریاکت طراحی میکرد؛ صفحهای که از چهار قسمت اصلی تشکیل شده که به شرح زیر است:
1. عنوان و توضیحات مقاله
2. سیستم امتیازدهی به مقاله
3. سیستم ثبت نظر
4. قسمت نمایش نظرات بهصورت تودرتو
متأسفانه در اواسط پروژه بود که عرفان درگیر ویروس کرونا شد و پروژه ناقص ماند، اما از آنجایی که عرفان روی قولی که میدهد بسیار حساس است، از شما خواسته تا قسمتهایی از پروژه را که باقی مانده کامل کنید تا مبادا بد قول شود و بتواند پروژه را سر وقت تحویل دهد.
## جزئیات پروژه
پروژهی اولیه را از [این لینک](/problemset/assignments/4367/download_problem_initial_project/134356/) دانلود کنید.
<details class="green">
<summary>
ساختار فایلهای پروژه
</summary>
```
comments
├── package.json
├── package-lock.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
├── README.md
├── server
│ ├── app.js
│ ├── posts.json
│ └── techs.json
└── src
├── App.css
├── <mark>App.jsx</mark>
├── assets
│ └── avatar.png
├── components
│ ├── <mark>AddComment.jsx</mark>
│ ├── <mark>Comment.jsx</mark>
│ ├── Post.jsx
│ ├── <mark>Rate.jsx</mark>
│ └── <mark>SelectBox.jsx</mark>
├── container
│ └── Comments.jsx
├── data
│ └── data.js
├── index.js
└── __tests__
└── sample.test.js
```
</details>
<details class="brown">
<summary>
راهاندازی پروژه
</summary>
- پروژهی اولیه را دانلود و از حالت فشرده خارج کنید.
- اجرای دستور `npm i` پکیج های مورد نیاز را نصب کنید.
- با اجرای دستور `npm run server` سرور را اجرا کنید.
- با اجرای دستور `npm start` پروژه را اجرا کنید.
</details>
## قسمتهای باقیمانده از پروژه که باید پیادهسازی شوند
### پاسخ به نظر دیگران
در کامپوننت `Comment` دکمهای تحت عنوان `reply` وجود دارد که با کلیک کردن روی آن به وضعیت پاسخ میرویم. در وضعیت پاسخ، اتفاقاتی رخ می دهد که به شرح زیر است:
1. کاربر باید به المانی با کلاس `ac-wrapper` اسکرول شود. این المان در فایل `AddComment` وجود دارد.
2. در فایل `AddComment` یک تگ `h2` وجود دارد که محتوای آن در وضعیت پاسخ باید برابر باشد با:
```js
Write your comment in response to {name}:
```
که `{name}` برابر است با نام فردی که قرار است به او پاسخ داده شود.
و در وضعیت ثبت نظر باید برابر باشد با:
```js
Write your comment:
```
3. در وضعیت پاسخ باید بعد از دکمهی `Send` در فایل `AddComment` دکمهای تحت عنوان `Cancel` اضافه شود که در صورت کلیک کردن روی این دکمه، باید از وضعیت پاسخ به وضعیت ثبت نظر برگردیم.
4. اگر در وضعیت ثبت نظر باشیم، در فایل `AddComment` بعد از `input` با نوع `email` باید کامپوننت `SelectBox` فراخوانی شود، اما اگر در وضعیت پاسخ باشیم، این کامپوننت نباید در صفحه وجود داشته باشد.
### انتخاب *topic*
مقالات موضوعات مختلفی را شامل میشوند. کاربر هنگام ثبت نظر باید *topic* یا موضوعی را مشخص کند. برای این کار، عرفان سروری آماده کرده است که یک رشته دریافت میکند و بین *topic* های موجود جستوجو میکند و لیست *topic* هایی که رشتهی ارسالشده در آنها وجود دارد را بر میگرداند. برای مثال اگر *topic* ها در سرور بهصورت زیر باشند:
```
php , python , java , c++ , Go
```
اگر حرف `p` برای سرور ارسال شود، سرور لیست زیر را برمیگرداند:
```json
{
"data": {
"matchedTechs": [
{ "id": 1, "name": "php" },
{ "id": 2, "name": "python" }
]
},
"status": "success"
}
```
در فایل `components/SelectBox` یک `input` با کلاس `tpc` وجود دارد. در صورت تغییر `value` این `input`، باید یک درخواست *GET* به آدرس `http://127.0.0.1:8000/` ارسال شود و مقدار `input` در *query string* با کلید `search` ارسال کنید تا لیست *topic* ها دریافت شوند.
عرفان برای نمایش *topic* ها یک المان `div` با کلاس `c-selectbox` ساخته است. این المان به نحوی طراحی و استایلدهی شده است که شبیه به یک *dropdown* باشد. اگر استایل های زیر را داشته باشد:
```css
padding: 0;
height: 0;
overflow: "hidden";
```
**لیست topic ها نمایش داده نمیشود و *dropdown* در وضعیت بسته است** و اگر این استایل ها را نداشته باشد، *dropdown* در وضعیت باز است و لیست *topic* ها در صفحه نمایش داده می شود.
در صورتی که لیست *topic* ها خالی باشد، *dropdown* باید بسته باشد.
در صورتی که لیست *topic* ها خالی نباشد، باید لیست را در المان `div` با کلاس` c-selectbox ` بهصورت زیر رندر کنید:
```jsx
<div className="item">
<label htmlFor={آیدی تاپیک}>{نام تاپیک}</label>
<input type="radio" name="" id={آیدی تاپیک} />
</div>
```
با کلیک بر روی المان `div` با کلاس `item` که رندر کردهاید، اتفاقات زیر باید رخ دهد:
**ابتدا باید dropdown بسته شود و سپس باید نام کامل آن _topic_ به عنوان مقدار `input` با کلاس `tpc` قرار بگیرد**
دقت کنید در زمانی که لیست تاپیک ها خالی نیست و ما در حال نمایش *topic* ها هستیم در صورتی که کاربر بهصورت کلی روی `body` کلیک کند، باید *dropdown* بسته شود و مقدار `input` با کلاس `tpc` تغییر نکند.
### امتیازدهی
در این بخش از سؤال، شما باید سیستم امتیازدهی به مقاله را پیادهسازی کنید. در فایل `Rate.jsx` یک استیت به نام `star` وجود دارد که مقدار اولیهی آن به صورت زیر است:
```js
[
{ id: 1, hover: false, clicked: false },
{ id: 2, hover: false, clicked: false },
{ id: 3, hover: false, clicked: false },
{ id: 4, hover: false, clicked: false },
{ id: 5, hover: false, clicked: false }
]
```
در صورتی که مقدار `hover` و `clicked` برابر با `true` باشد، ستارههای تو پر و در غیر اینصورت، ستارههای تو خالی نمایش داده میشوند. در این بخش چهار تابع داریم که دو تا از آنها بهصورت کامل نوشته شده و دو تابع را باید شما پیادهسازی کنید. عملکرد دو تابعی که به صورت کامل از قبل نوشته شده بهشرح زیر است:
<details class="green">
<summary>
تابع hoverHandler
</summary>
این تابع رویداد `onMouseEnter` روی ستارهها را هندل میکند، بهطوری که یک شناسه دریافت میکند و مقدار `hover` هر آبجکتی که شناسهی آنها از نظر عددی کوچکتر از شناسهی دریافتی است را برابر با `true` میکند. برای مثال، فرض کنید کاربر ستارهی چهارم را *hover* کرده است. ما باید ستارههایی با شناسهی `1` تا `4` را آپدیت کنیم و مقدار `hover` آنها را `true` کنیم.
</details>
<details class="green">
<summary>
تابع blurHandler
</summary>
این تابع رویداد `onMouseLeave` را روی ستارهها هندل میکند، بهطوری که مقدار `hover` هر آبجکت را آپدیت کرده و آن را برابر با `false` قرار میدهد.
</details>
و اما دو تابعی که شما باید آنها را کامل کنید:
### تابع `submitRateHandler`
این تابع در قدم اول مانند تابع `hoverHandler` کار می کند، با این تفاوت که به جای `true` کردن مقدار `hover` هر آبجکت، باید مقدار `clicked` هر آبجکت را `true` کند. در ادامه، باید یک درخواست از نوع *PATCH* به آدرس `http://127.0.0.1:8000/posts/` ارسال کند و در بدنهی درخواست، شناسهی آخرین آبجکتی که ویژگی `clicked` آن برابر با `true` شده را به عنوان `rate` به صورت *JSON* به سرور ارسال کند.
متأسفانه در حال حاضر سرور خراب است و عرفان از دوستش سینا خواسته تا سرور را درست کند، اما سینا در سفر به سر میبرد و شما باید با همین سرور خراب کار کنید! اشکال سرور این است که با احتمال ۵۰ درصد، `rate` کاربران را ثبت میکند و با احتمال ۵۰ درصد، درخواست شما با خطا مواجه میشود. علاوه بر این، سرور یک تأخیر یک ثانیه ای نیز دارد. اگر درخواست شما با موفقیت ثبت شود پیام سرور به شما به صورت زیر خواهد بود:
```json
{
"message": "Your rate for this post has been registered.",
"status": "success"
}
```
پس از دریافت پیام بالا، باید مقدار `message` را بهصورت یک *toast* نمایش دهید. برای این کار، عرفان به پیشنهاد مهیار از پکیج `react-toastify` استفاده کرده است. البته کدهای این قسمت را نیز در اختیار شما قرار داده است. کافی است در صورت دریافت پاسخ موفقیتآمیز، از تکهکد زیر استفاده کنید:
```js
toast.success(پیامی که از سرور دریافت کرده اید, {
position: "top-left",
});
```
در صورت *fail* شدن درخواست، پاسخ سرور بهصورت زیر خواهد بود:
```json
{
"message": "Rating registering failed, try again.",
"rate": آخرین امتیازی که در سرور ثبت شده است,
"status": "error"
}
```
مقدار `rate` برابر با آخرین امتیاز ثبتشدهی کاربر است. شما باید این مقدار را به تابع `stepBackward` ارسال کنید.
به دلیل این که سرور تأخیر دارد، ما در زمان تآخیر باید امتیازی که کاربر ثبت کرده است را آپدیت و رندر کنیم، اما به محض این که پاسخ از سرور دریافت شود، در صورتی که درخواست *fail* شده باشد، باید سیستم *rating* را مجدد آپدیت کنیم و به `rate` قبلی برگردیم **(منظور آخرین امتیازی است که کاربر وارد کرده و در سرور ثبت شده)**. به این تکنیک، *optimistic rendering* گفته میشود. تابع `stepBackward` در واقع قرار است این کار را برای ما هندل کند. پس از فراخوانی تابع `stepBackward` باید مجدداً یک *toast* به کاربر نمایش دهید. برای این کار، از تکهکد زیر استفاده کنید:
```js
toast.error(پیامی که از سرور دریافت کرده اید, {
position: "top-left",
});
```
### تابع `stepBackward`
این تابع یک *rate* دریافت میکند و باید استیت مربوط به ستارهها را آپدیت کند، به طوری که مقدار `clicked` مربوط به المانهای دارای شناسههای کوچکتر یا مساوی `rate` را `true` کند و مقدار `hover` آنها را `false` کند. برای مثال، استیت ستارهها در هر وضعیتی که باشد، در صورت *fail* شدن درخواست اگر سرور مقدار `rate` را `3` برگرداند، باید آبجکتهایی با شناسههای `1` تا `3` طوری آپدیت شوند که مقدار `clicked`شان `true` و مقدار `hover`شان `false` شود.
**توجه:** شما تنها مجاز به اعمال تغییرات در فایلهای زیر هستید:
```
src/App.jsx
src/components/AddComment.jsx
src/components/Comment.jsx
src/components/Rate.jsx
src/components/SelectBox.jsx
```
## آنچه باید آپلود کنید
پس از پیادهسازی موارد خواستهشده، پوشهی `src` را زیپ کرده و آپلود کنید.