حالا دیگه آخرین روز سال شده بود و فرانتیوم میخواست یک پروژه مناسب طراحی بکند، میخواست تواناییهای خود را بسنجد. توی خونهی مامانبزرگش ریاکتیوم در حالی که داشت به مامانبزرگ توی خونه تکونی کمک میکرد، چشمش خورد به جدول کلماتی که بابابزرگش حل میکرد.
حالا یک ایدهی خوب برای پیادهسازی به ذهنش رسیده بود، جدول اطلاعات ...
پروژه اولیه
برای دانلود پروژهی اولیه روی این لینک کلیک کنید.
ساختار فایلها
data_table/
├─ src/
│ ├─ api/
│ │ ├─ createToken.ts
│ │ ├─ logout.ts
│ │ └─ productApi.ts
│ ├─ components/
│ │ ├─ components/
│ │ │ ├─ productTable/
│ │ │ │ ├─ components/
│ │ │ │ │ ├─ ProductTableBody/
│ │ │ │ │ │ ├─ modals/
│ │ │ │ │ │ │ ├─ DeleteProductDialog.tsx
│ │ │ │ │ │ │ └─ EditProductModal.tsx
│ │ │ │ │ │ └─ ProductTableBody.tsx
│ │ │ │ │ ├─ ProductTableFooter/
│ │ │ │ │ │ ├─ components/
│ │ │ │ │ │ │ ├─ PageSizeSelector.tsx
│ │ │ │ │ │ │ ├─ PaginationControls.tsx
│ │ │ │ │ │ │ └─ TotalCountDisplay.tsx
│ │ │ │ │ │ └─ ProductTableFooter.tsx
│ │ │ │ │ └─ ProductTableHeader/
│ │ │ │ │ ├─ components/
│ │ │ │ │ │ └─ ProductTableControls.tsx
│ │ │ │ │ └─ ProductTableHeader.tsx
│ │ │ │ ├─ hooks/
│ │ │ │ │ └─ useProducts.ts
│ │ │ │ └─ ProductTable.tsx
│ │ │ ├─ AddProductModal.tsx
│ │ │ ├─ ProductForm.tsx
│ │ │ └─ ProductPageHeader.tsx
│ │ └─ ProductPage.tsx
│ ├─ pages/
│ │ ├─ LoginPage.tsx
│ │ └─ useLogin.ts
│ ├─ routes/
│ │ └─ AppRoutes.tsx
│ ├─ stores/
│ │ └─ AuthContext.tsx
│ ├─ App.tsx
│ ├─ index.css
│ ├─ main.tsx
│ └─ vite-env.d.ts
├─ .eslintrc.cjs
├─ index.html
├─ package-lock.json
├─ package.json
├─ README.md
├─ tsconfig.json
├─ tsconfig.node.json
└─ vite.config.ts
راه اندازی
-
پس از دانلود کردن فایل پروژه اولیه، آن را از حالت فشرده خارج کنید.
-
سپس در ترمینال خود دستور
npm install
را اجرا کنید. -
در نهایت پروژه را با استفاده از دستور
npm run dev
اجرا کنید.
در صورت نیاز از دستور npm install --force
برای نصب وابستگیها استفاده کنید.
جزئیات پیادهسازی
دایرکتوری api
فایل productApi.ts
در این فایل، نوع دادهای (type) با نام Product
تعریف شده است که مشخص میکند هر محصول باید دارای چه ویژگیهایی (properties) باشد.
در ادامه، مجموعهای از توابع شبیهسازیشده برای endpoint
های مربوط به عملیات CRUD (افزودن، خواندن، ویرایش و حذف دادهها) پیادهسازی شده است تا بدون نیاز به یک بکاند واقعی، بتوان رفتار API را در محیط فرانتاند آزمایش کرد.
نکته: منظور از شبیهسازی این است که در این پروژه، دادهها بهصورت محلی و در حافظه ذخیره میشوند و ارتباطی با سرور واقعی وجود ندارد.
همانطور که مشاهده میکنید، در productAPI
توابع زیر وجود دارند:
-
getProducts
-
addProduct
-
editProduct
-
deleteProduct
که بهترتیب برای موارد زیر بهکار میروند:
-
دریافت محصولات
-
افزودن محصول جدید
-
ویرایش اطلاعات محصول
-
حذف محصول
همچنین در این پیادهسازی، قابلیتهای زیر نیز در نظر گرفته شدهاند:
-
صفحهبندی (Pagination) برای نمایش تدریجی دادهها
-
جستوجو (Search) بر اساس نام محصول
-
مرتبسازی بر اساس قیمت (Sorting) به دو حالت صعودی (asc) و نزولی (desc)
فایل logout.ts
در این فایل اندپوینتی نوشته شده است که با استفاده از آن عملیات خروج انجام میشود.
فایل createToken.ts
در این فایل، تابعی با نام createToken
پیادهسازی شده است که وظیفه دارد پس از ورود موفق کاربر، دو مقدار access token
و refresh token
را بازگرداند.
هدف تابع
این تابع با بررسی اطلاعات کاربری (نام کاربری و رمز عبور) که در آرایهی fakeUsers
از پیش تعریف شدهاند، احراز هویت را شبیهسازی میکند.
در صورت معتبر بودن اطلاعات ورودی، توکنها تولید و بازگردانده میشوند؛ در غیر این صورت، خطایی با پیام «نام کاربری یا رمز عبور اشتباه است» برگردانده میشود.
اطلاعات ورود برای این دو کاربر از پیش تعریف شده است:
{
username: "admin",
password : "admin123",
role : "admin as const"
}
{
username: "user",
password: "user123",
role: "user" as const
}
هر کاربر پس از ورود، با توجه به نقش (role) خود به مسیر (route) متفاوتی هدایت میشود.
مسیرها (Routes) و دسترسیها
مسیر adminTableRoute
-
در ProductPageHeader، دکمهی افزودن محصول نمایش داده میشود و ادمین میتواند محصولات جدید اضافه کند.
-
در ProductTableRow، دو آیکون ویرایش و حذف وجود دارد که امکان ویرایش یا حذف محصول را فراهم میکنند.
مسیر userTableRoute
-
در این مسیر، امکانات مدیریتی برای کاربر عادی نمایش داده نمیشوند.
-
کاربر عادی تنها مجاز است محصولات را مشاهده (Read) کند.
نکات ورود (Login)
-
تا زمانی که عملیات ورود انجام نشده باشد، کاربر اجازهی مشاهدهی صفحات داخلی را ندارد.
-
در صورت تلاش برای دسترسی مستقیم به آدرسهای دیگر، کاربر به صفحهی Login هدایت (redirect) میشود.
-
پس از ورود موفق، بسته به نقش کاربر:
-
کاربر ادمین → هدایت به
adminTableRoute
-
کاربر عادی → هدایت به
userTableRoute
-
فرایند ورود کاربر
تمامی اطلاعات مربوط به ورود کاربر، شامل access token
، refresh token
و role
، باید با استفاده از Context API مدیریت شده و در Local Storage ذخیره شوند.
دلیل این کار آن است که تا زمانی که کاربر از سیستم خارج نشده (Log out) یا رفرش توکن (refresh token) او منقضی نشده باشد، نباید صفحهی Login مجدداً نمایش داده شود. نمایش دوبارهی فرم ورود در این شرایط، از نظر تجربهی کاربری (UX) مناسب نیست و باعث ناهماهنگی در جریان کاربر میشود.
فایلهایی که باید تکمیل شوند
فایل AppRoutes.ts
در این فایل قرار است مسیرهای اصلی برنامه (Routes) بر اساس وضعیت ورود و نقش کاربر تنظیم شوند. هدف این است که تنها کاربران مجاز بتوانند به مسیرهای مربوط به خود دسترسی داشته باشند.
-
از React Router برای تعریف مسیرها استفاده میشود.
-
با استفاده از
useAuth()
ازAuthContext
، وضعیت ورود (isLoggedIn
) و نقش (role
) کاربر دریافت میشود.
منطق پیادهسازی:
-
اگر کاربر وارد نشده باشد:
-
تنها مسیر
/login
باید در دسترس باشد. -
سایر مسیرها باید با استفاده از
<Navigate>
به/login
هدایت شوند.
-
-
اگر کاربر وارد شده باشد:
-
اگر نقش او
admin
باشد → به مسیر/adminTableRoute
هدایت شده و تنها مسیرهای مخصوص ادمین نمایش داده میشوند. -
اگر نقش او
user
باشد → به مسیر/userTableRoute
هدایت شده و تنها مسیرهای مخصوص کاربر معمولی نمایش داده میشوند.
-
فایل AuthContext.tsx
در این فایل قرار است سیستم احراز هویت (Authentication) کاربران پیادهسازی شود. هدف این است که با استفاده از Context API، وضعیت ورود کاربر، نقش او و توکنها در سطح کل اپلیکیشن مدیریت شود.
AuthContext
نقش یک Store مرکزی را دارد که اطلاعات مربوط به ورود کاربر را نگهداری کرده و آن را برای سایر بخشهای برنامه (مانند صفحات و کامپوننتها) در دسترس قرار میدهد.
این context شامل اطلاعات زیر خواهد بود:
-
وضعیت ورود (
isLoggedIn
) -
نقش کاربر (
role
) -
توکنها (
accessToken
وrefreshToken
) -
توابع ورود و خروج (
login
وlogout
)
-
تعریف تابع
login
برای ذخیرهی اطلاعات ورود در Local Storage و بهروزرسانی stateها. -
تعریف تابع
logout
که با صدا زدنlogoutApi()
کاربر را از سیستم خارج میکند و اطلاعات را از Local Storage حذف میکند.
فایل LoginPage.tsx
-
با استفاده از useState، مقادیر ورودی فرم را مدیریت کنید.
-
از تابع handleLogin برای ارسال فرم استفاده کنید و توجه داشته باشید که هنگام سابمیت نباید صفحه رفرش شود.
فایل useLogin.ts
در این فایل قرار است یک هوک سفارشی (Custom Hook) برای مدیریت فرآیند ورود کاربر پیادهسازی شود. هدف این است که عملیات Login بهصورت جداگانه از رابط کاربری انجام شود و منطق آن در یک هوک مستقل قرار گیرد.
توضیحات منطق عملکرد:
-
از
useAuth()
برای دسترسی به تابعlogin
که درAuthContext
تعریف شده، استفاده میشود. با استفاده از این تابع، پس از تأیید اطلاعات کاربر، توکنها و نقش او در Local Storage ذخیره میشوند. -
از
createToken()
برای ارسال اطلاعات ورود (username
وpassword
) و دریافت Access Token، Refresh Token و Role استفاده میشود. -
پس از موفقیت در ورود:
-
اگر نقش کاربر
admin
بود → کاربر باید به مسیر/adminTableRoute
هدایت شود. -
اگر نقش کاربر
user
بود → کاربر باید به مسیر/userTableRoute
هدایت شود.
-
-
در صورت خطا (مثلاً واردکردن نام کاربری یا رمز عبور اشتباه)، پیام مناسب باید در state مربوط به
error
ذخیره و نمایش داده شود.
- توجه: ساختار فایل App.tsx را تغییر ندهید!
پیادهسازی هوک useProducts
هوک useProducts
مسئول مدیریت وضعیت محصولات است. این هوک دادهها را از سرور دریافت میکند و قابلیتهای زیر را در اختیار کامپوننتهای دیگر قرار میدهد:
-
دریافت لیست محصولات از API
-
پشتیبانی از صفحهبندی (pagination)
-
جستوجو بر اساس نام محصول
-
مرتبسازی بر اساس قیمت
-
کنترل وضعیت بارگذاری (
loading
)
متغیرهای داخلی
متغیر | نوع | توضیح |
---|---|---|
products |
Product[] |
لیست محصولات دریافتی از API |
totalCount |
number |
تعداد کل محصولات موجود در سرور |
page |
number |
شماره صفحه فعلی |
pageSize |
number |
تعداد محصولات در هر صفحه (پیشفرض: ۵) |
search |
string |
عبارت جستوجو برای فیلتر محصولات |
sortByPrice |
`"asc" | "desc" |
loading |
boolean |
نشاندهنده وضعیت بارگذاری دادهها |
توابع اصلی
1. fetchProducts
-
وظیفه: فراخوانی
productApi.getProducts()
و تنظیم دادهها. -
پارامترهای مورد استفاده:
page
,pageSize
,search
,sortByPrice
-
مقادیر زیر را بهروزرسانی میکند:
-
products
-
totalCount
-
loading
-
2. useEffect
-
وظیفه: نظارت بر تغییر در
page
,pageSize
,search
,sortByPrice
-
هر زمان یکی از مقادیر بالا تغییر کند، دوباره
fetchProducts()
را اجرا میکند. -
در نهایت متغیرها و توابع را return کنید و در جاهایی که نیاز هست از آن استفاده کنید.
نمایش اطاعات در جدول
- جدول ما 3 قسمت با این اسامی دارد :
کامپوننت ProductTableHeader.tsx
در این بخش، ستونهای جدول محصولات بهترتیب زیر نمایش داده میشوند:
-
نام محصول (
productName
) -
دستهبندی (
productCategory
) -
قیمت (
productPrice
) -
موجودی (
stock
) -
عملیات (
operation
)
همچنین کامپوننت ProductTableControls در این قسمت قرار دارد که شامل فیلد جستوجو و دکمهی مرتبسازی است.
قابلیت جستوجو
-
باید قابلیت جستوجو بر اساس نام محصول پیادهسازی شود.
-
محتوای بخش بدنهی جدول (tbody) با تغییر مقدار جستوجو بهروزرسانی خواهد شد.
قابلیت مرتبسازی
- دکمهی مرتبسازی با کلیک بین سه حالت جابهجا میشود:
-
مرتبسازی صعودی (ارزانترین ← گرانترین)
-
مرتبسازی نزولی (گرانترین ← ارزانترین)
-
حالت پیشفرض (بدون مرتبسازی)
-
در کد مربوطه، متن دکمه بسته به وضعیت فعلی بهشکل زیر تغییر میکند:
{sortByPrice === "asc"
? "مرتبسازی: ارزانترین ← گرانترین"
: sortByPrice === "desc"
? "مرتبسازی: گرانترین ← ارزانترین"
: "مرتبسازی بر اساس قیمت"}
کامپوننت ProductTableBody.tsx
در این بخش، لیست محصولات دریافتشده باید بهصورت ردیفهای جداگانه در جدول نمایش داده شود. این دادهها در کامپوننت ProductTableBody نمایش داده میشوند.
شرح عملکرد
-
هر محصول شامل اطلاعاتی مانند نام، دستهبندی، قیمت و موجودی است که در ستونهای جدول نمایش داده میشود.
-
در ستون آخر (عملیات)، آیکونهای ویرایش (FaEdit) و حذف (FaTrash) تنها برای نقش ادمین (admin) نمایش داده میشوند.
-
کاربران معمولی (user) فقط اطلاعات محصولات را مشاهده میکنند و به عملیات ویرایش و حذف دسترسی ندارند.
نکات مهم
نکته 1
تفاوت بین نقشهای admin
و user
در این بخش فقط در نمایش آیکونهای ویرایش و حذف است.
اگر نقش کاربر admin
باشد، این آیکونها نمایش داده میشوند؛ در غیر این صورت پنهان خواهند بود.
نکته 2 تغییرات مربوط به جستوجو یا تغییر تعداد محصولات در هر صفحه، مستقیماً بر دادههای نمایشدادهشده در ProductTableBody تأثیر میگذارند.
نکته 3 اگر هیچ محصولی یافت نشود، باید پیام زیر نمایش داده شود:
محصولی یافت نشد.
نکته 4 در هنگام بارگذاری دادهها، باید پیام زیر نمایش داده شود:
در حال بارگذاری...
نکته 5
با کلیک روی آیکون ویرایش (FaEdit) باید تابع
setShowEditModal(true, product)
فراخوانی شود تا مودال ویرایش باز شده و اطلاعات محصول انتخابشده در آن نمایش داده شود.
نکته 6
با کلیک روی آیکون حذف (FaTrash) باید تابع
setShowDeleteConfirm(true, product)
فراخوانی شود تا پنجرهی تأیید حذف نمایش داده شود.
دکمهی حذف محصول
-
وقتی روی دکمهی حذف محصول کلیک میکنیم، مودال DeleteProductModal باز میشود.
-
در این مودال از کاربر پرسیده میشود که آیا مطمئن است میخواهد محصول را حذف کند یا خیر.
وظایف اصلی
-
انصراف از عملیات
- اگر کاربر دکمهی «انصراف» را بزند، هیچ تغییری ایجاد نمیشود و مودال بسته خواهد شد.
-
حذف محصول
- اگر کاربر دکمهی «حذف» را بزند:
-
محصول مورد نظر حذف میشود.
-
پس از موفقیتآمیز بودن عملیات، یک refetch انجام میشود تا جدول محصولات بروزرسانی شود.
-
- اگر کاربر دکمهی «حذف» را بزند:
دکمهی ویرایش محصول در سطر هر محصول
-
وقتی روی دکمهی ویرایش محصول کلیک میکنیم، مودال EditProductModal باز میشود.
-
داخل این مودال، کامپوننت ProductForm نمایش داده میشود و اطلاعات محصول انتخابشده در ورودیهای فرم مقداردهی اولیه میشوند.
وظایف اصلی
-
ویرایش اطلاعات محصول
- کاربر میتواند مقادیر فیلدهای فرم را تغییر دهد.
-
بهروزرسانی دادهها
-
پس از ثبت تغییرات، درخواست به سرور ارسال میشود.
-
پس از موفقیتآمیز بودن عملیات، باید یک refetch انجام شود تا جدول محصولات بروزرسانی شود.
-
نکته
- توجه کنید که کامپوننت ProductForm هم در مودال AddProductModal و هم در EditProductModal استفاده میشود.
کامپوننت ProductTableFooter
- در این بخش سه کامپوننت وجود دارد:
1. کامپوننت TotalCountDisplay
-
این کامپوننت وظیفهی نمایش تعداد کل محصولات موجود را دارد.
-
این عدد باید دقیقاً مطابق با تعداد محصولاتی باشد که از سمت سرور یا پرامیس داده دریافت میشوند.
2. کامپوننت PaginationControls
-
این کامپوننت مسئول مدیریت صفحهبندی است.
-
دادهها باید بهصورت صفحهبندیشده نمایش داده شوند.
-
دو دکمهی «>» و «<» باید وجود داشته باشند:
-
اگر کاربر در صفحهی اول قرار دارد، دکمهی «>» باید غیرفعال شود.
-
اگر کاربر در صفحهی آخر قرار دارد، دکمهی «<» باید غیرفعال شود.
-
3. کامپوننت PagesSizeSelector
-
این کامپوننت شامل یک انتخابگر (selector) برای تعیین تعداد آیتمها در هر صفحه است.
-
گزینههای انتخابی باید شامل ۵، ۱۰ یا ۱۵ آیتم در هر صفحه باشند.
-
با تغییر این مقدار:
-
تعداد کل صفحات دوباره محاسبه میشود.
-
دادههای نمایشدادهشده در جدول بهروزرسانی میشوند.
-
کامپوننت ProductPage
این کامپوننت شامل اجزای زیر است:
- مودال AddProductModal
- مودال EditProductModal
- دیالوگ DeleteProductDialog
- کامپوننت ProductPageHeader
- کامپوننت ProductTable (در باکس راهنمای بالا، توضیح داده شد)
- دکمهی خروج
دکمهی خروج
با زدن این دکمه، باید عملیات logout انجام بشود.
کامپوننت ProductPageHeader
در این کامپوننت دکمهی افزودن محصول قرار دارد که باید به شیوهی زیر کار کند.
- وقتی روی دکمهی افزودن محصول کلیک میکنیم، باید مودال AddProductModal باز شود.
- داخل این مودال، کامپوننت ProductForm نمایش داده میشود.
مودال AddProductModal
این کامپوننت یک مودال برای افزودن محصول جدید است که شامل فرم ثبت محصول (ProductForm
) میباشد.
با فراخوانی این کامپوننت، زمانی که showAddModal
برابر با true
باشد، یک مودال در مرکز صفحه نمایش داده میشود.
- اگر مقدار
showAddModal
برابر باfalse
باشد، مقدارnull
را ریترن کنید.
در صورت کلیک بر روی دکمهی ضربدر (✕)، مقدار showAddModal
به false
تغییر کرده و مودال بسته میشود.
پس از ثبت موفق محصول در فرم:
-
تابع
refetchProducts
برای بهروزرسانی لیست محصولات فراخوانی میشود. -
مودال بهصورت خودکار بسته میشود.
مودال EditProductModal
این کامپوننت یک مودال برای ویرایش اطلاعات محصول موجود است که از ProductForm
برای نمایش و ویرایش دادهها استفاده میکند.
توضیحات عملکرد
-
زمانی که
showEditModal
برابر باtrue
باشد و پراپسproduct
مقدار داشته باشد، مودال نمایش داده میشود. -
اگر مقدار
showEditModal
یاproduct
برابر باfalse
باشد، مقدارnull
را ریترن کنید. -
دادههای محصول انتخابشده از طریق پراپس
product
بهعنوان مقادیر اولیه فرم (initialValues
) بهProductForm
ارسال میشوند. -
با کلیک بر روی دکمهی ضربدر (✕)، مودال بسته میشود.
-
پس از ثبت موفق تغییرات:
-
مودال بهصورت خودکار بسته میشود.
-
تابع
refetchProducts
برای بهروزرسانی لیست محصولات فراخوانی میشود.
-
دیالوگ DeleteProductDialog
این کامپوننت یک دیالوگ تأیید حذف محصول را نمایش میدهد. کاربر باید پیش از حذف نهایی محصول، این عملیات را تأیید کند.
توضیحات عملکرد
-
زمانی که پراپس
showDeleteConfirm
برابر باtrue
باشد و پراپسproduct
مقدار داشته باشد، دیالوگ نمایش داده میشود. -
در دیالوگ، نام محصول مورد نظر برای حذف نمایش داده میشود.
-
دو دکمه وجود دارد:
-
انصراف: دیالوگ را بدون انجام هیچ تغییری میبندد.
-
حذف: تابع
handleDelete
را اجرا کرده و درخواست حذف محصول را به API ارسال میکند.
-
منطق عملکرد handleDelete
-
حالت loading فعال میشود تا از چندبار کلیک جلوگیری شود.
-
تابع
productApi.deleteProduct(product.id)
فراخوانی میشود. -
در صورت موفقیت:
-
دیالوگ بسته میشود.
-
تابع
refetchProducts
برای بهروزرسانی لیست محصولات اجرا میشود.
-
-
در صورت بروز خطا، پیام خطا نمایش داده میشود.
-
پس از پایان عملیات (موفق یا ناموفق)، حالت loading غیرفعال میشود.
کامپوننت ProductForm
شما در این کامپوننت باید تابع handleSubmit را پیادهسازی کنید.
مراحل عملکرد
-
اعتبارسنجی ورودیها
-
بررسی میکند که فیلدهای
name
,category
,price
وstock
پر شده باشند. -
اگر یکی از فیلدها خالی باشد:
-
مقدار خطا (
error
) با متن"لطفا همه فیلدها را پر کنید."
تنظیم میشود. -
عملیات ذخیرهسازی متوقف میشود.
-
-
-
شروع عملیات ذخیرهسازی
-
مقدار
loading
رویtrue
قرار میگیرد. -
مقدار
error
پاک میشود تا پیامهای قبلی نمایش داده نشوند.
- فراخوانی api
- اگر مقدار initialValue موجود بود، محصول مورد نظر ویرایش میشود، اگر نه محصول جدید اضافه میشود.
-
بروزرسانی بعد از موفقیت
-
تابع
onSuccess()
فراخوانی میشود تا والد کامپوننت از موفقیت عملیات مطلع شود. -
تابع
refetch()
از هوکuseProducts
صدا زده میشود تا لیست محصولات دوباره بارگذاری شود.
-
-
مدیریت خطا
- اگر هنگام تماس با API خطایی رخ دهد، مقدار
error
با متن"خطا در ذخیرهسازی محصول."
تنظیم میشود.
- اگر هنگام تماس با API خطایی رخ دهد، مقدار
-
پایان عملیات
- مقدار
loading
دوباره رویfalse
قرار میگیرد تا دکمه ثبت فعال شود.
- مقدار
آنچه باید آپلود کنید
-
توجه: پس از اعمال تغییرات، کل پروژه را Zip کرده و آپلود کنید. همانند پروژه اولیه در فایل زیپ شده نباید کد در پوشهی دیگری قرار بگیرد در غیر این صورت سیستم داوری فایل را شناسایی نکرده و نمرهای دریافت نخواهید کرد.
-
توجه: تنها فایلهایی که در ساختار پروژه مشخص شدهاند، در سیستم داوری مورد پذیرش قرار خواهد گرفت و سایر تغییرات در سایر فایلها بیتاثیر خواهند بود.
-
توجه: به هیچ عنوان نباید هیچ تغییری در استایلها ایجاد کنید.
-
توجه: در برخی فایلها، کامنتهایی برای راهنمایی قرار داده شدهاند؛ طبق این راهنماییها عمل کنید.
-
توجه: لطفاً مواردی که در هر کامپوننت ایمپورت و استفاده شدهاند را حذف نکنید و همچنین ترتیب یا جای کامپوننتها را تغییر ندهید.
ارسال پاسخ برای این سؤال