diff --git a/README.md b/README.md new file mode 100644 index 0000000..5d80e3f --- /dev/null +++ b/README.md @@ -0,0 +1,233 @@ +# ⚽ فانتزی فوتبال - Fantasy Football + +یک پلتفرم فانتزی فوتبال کامل با Next.js، Prisma و PostgreSQL + +## 🌟 ویژگی‌ها + +### برای کاربران +- ✅ ثبت‌نام و ورود امن با NextAuth +- ✅ ساخت تیم به صورت استپ‌بای‌استپ +- ✅ انتخاب لوگو و نام تیم +- ✅ انتخاب از 6 ترکیب مختلف +- ✅ مدیریت بودجه (100 میلیون) +- ✅ انتخاب 11 بازیکن اصلی + 4 ذخیره +- ✅ Drag & Drop برای جابجایی بازیکنان +- ✅ انتخاب کاپیتان و نایب کاپیتان +- ✅ فیلتر و جستجوی بازیکنان +- ✅ نمایش زمین فوتبال واقعی +- ✅ پیگیری امتیازات + +### برای ادمین +- ✅ مدیریت کشورها و پرچم‌ها +- ✅ مدیریت بازیکنان +- ✅ مدیریت مسابقات و راند‌ها +- ✅ ثبت رویدادهای بازی (گل، کارت، و...) +- ✅ محاسبه خودکار امتیازات +- ✅ تایید تیم‌های کاربران +- ✅ مدیریت قوانین امتیازدهی +- ✅ آمار و گزارش‌گیری + +## 🚀 نصب و راه‌اندازی + +### پیش‌نیازها +- Node.js 18+ +- PostgreSQL +- npm یا yarn + +### مراحل نصب + +1. **کلون کردن پروژه** +```bash +git clone +cd football-next +``` + +2. **نصب وابستگی‌ها** +```bash +npm install +``` + +3. **تنظیم متغیرهای محیطی** +فایل `.env` را ایجاد کنید: +```env +DATABASE_URL="postgresql://user:password@host:port/database" +NEXTAUTH_SECRET="your-secret-key" +NEXTAUTH_URL="http://localhost:3000" +``` + +4. **راه‌اندازی دیتابیس** +```bash +npm run db:generate +npm run db:push +``` + +5. **ساخت کاربران تست** +```bash +# کاربر عادی +npm run setup:test-user + +# کاربر ادمین +npm run setup:admin +``` + +6. **اجرای پروژه** +```bash +npm run dev +``` + +پروژه روی `http://localhost:3000` اجرا می‌شود. + +## 📋 اسکریپت‌های NPM + +```bash +npm run dev # اجرای development server +npm run build # ساخت برای production +npm run start # اجرای production server +npm run db:push # اعمال تغییرات schema به دیتابیس +npm run db:generate # تولید Prisma Client +npm run db:studio # باز کردن Prisma Studio +npm run setup:test-user # ساخت کاربر تست +npm run setup:admin # ساخت کاربر ادمین +npm run check:users # بررسی کاربران موجود +``` + +## 🔐 کاربران پیش‌فرض + +### کاربر عادی +- ایمیل: `test@test.com` +- رمز عبور: `123456` + +### ادمین +- ایمیل: `admin@admin.com` +- رمز عبور: `admin123` + +## 📁 ساختار پروژه + +``` +football-next/ +├── app/ # Next.js App Router +│ ├── (admin)/ # صفحات ادمین +│ ├── (user)/ # صفحات کاربر +│ └── api/ # API Routes +├── components/ # کامپوننت‌های React +├── lib/ # توابع کمکی +│ ├── auth.ts # تنظیمات NextAuth +│ └── db.ts # Prisma Client +├── prisma/ # Schema و Migrations +├── public/ # فایل‌های استاتیک +├── scripts/ # اسکریپت‌های کمکی +└── styles/ # فایل‌های CSS +``` + +## 🎮 راهنمای استفاده + +### برای کاربران +مستندات کامل در [USER-GUIDE.md](./USER-GUIDE.md) + +### برای توسعه‌دهندگان +مستندات راه‌اندازی در [SETUP.md](./SETUP.md) + +## 🛠️ تکنولوژی‌ها + +- **Framework**: Next.js 16 (App Router) +- **Database**: PostgreSQL +- **ORM**: Prisma +- **Authentication**: NextAuth.js +- **Styling**: Tailwind CSS +- **Language**: TypeScript +- **Drag & Drop**: Native HTML5 + +## 📊 مدل دیتابیس + +### جداول اصلی +- `User` - کاربران +- `Team` - تیم‌های کاربران +- `Player` - بازیکنان +- `Country` - کشورها +- `Match` - مسابقات +- `Round` - راندها +- `MatchEvent` - رویدادهای بازی +- `PlayerMatchStat` - آمار بازیکنان +- `ScoringRule` - قوانین امتیازدهی + +## 🎨 ویژگی‌های طراحی + +- طراحی Responsive +- حالت RTL برای فارسی +- انیمیشن‌های روان +- رنگ‌بندی مدرن +- UX بهینه +- دسترسی‌پذیری + +## 🔄 فرآیند ساخت تیم + +### استپ 1: مشخصات تیم +1. انتخاب لوگو (10 گزینه) +2. وارد کردن نام تیم +3. ساخت تیم + +### استپ 2: انتخاب بازیکنان +1. انتخاب ترکیب +2. اضافه کردن 11 بازیکن اصلی +3. اضافه کردن 4 بازیکن ذخیره +4. انتخاب کاپیتان و نایب کاپیتان +5. ثبت نهایی + +## 🐛 رفع مشکلات + +### خطای Foreign Key +```bash +# بررسی کاربران +npm run check:users + +# ساخت کاربر جدید +npm run setup:test-user +``` + +### مشکل Session +1. از مرورگر خارج شوید +2. Cache را پاک کنید +3. دوباره وارد شوید + +### خطای دیتابیس +```bash +# ریست کردن دیتابیس +npx prisma db push --force-reset + +# تولید مجدد Client +npm run db:generate +``` + +## 📈 TODO + +- [ ] آپلود تصویر برای لوگو تیم +- [ ] پیش‌نمایش تیم +- [ ] انیمیشن‌های بهتر +- [ ] نمایش آمار تفصیلی بازیکنان +- [ ] فیلتر پیشرفته +- [ ] مقایسه بازیکنان +- [ ] پیش‌بینی امتیازات +- [ ] اعلان‌های Real-time +- [ ] چت و نظرات +- [ ] لیگ‌های خصوصی + +## 🤝 مشارکت + +برای مشارکت در پروژه: +1. Fork کنید +2. Branch جدید بسازید +3. تغییرات را Commit کنید +4. Push کنید +5. Pull Request بزنید + +## 📄 لایسنس + +این پروژه تحت لایسنس MIT است. + +## 📞 تماس + +برای سوالات و پشتیبانی، با ما تماس بگیرید. + +--- + +**ساخته شده با ❤️ برای علاقه‌مندان فوتبال** diff --git a/SETUP.md b/SETUP.md new file mode 100644 index 0000000..0466631 --- /dev/null +++ b/SETUP.md @@ -0,0 +1,112 @@ +# راهنمای راه‌اندازی پروژه فانتزی فوتبال + +## 🚀 نصب و راه‌اندازی + +### 1. نصب وابستگی‌ها +```bash +npm install +``` + +### 2. تنظیم دیتابیس +```bash +npx prisma generate +npx prisma db push +``` + +### 3. ساخت کاربران تست + +#### کاربر عادی +```bash +npx tsx scripts/create-test-user.ts +``` +- ایمیل: `test@test.com` +- رمز عبور: `123456` + +#### کاربر ادمین +```bash +npx tsx scripts/create-admin-user.ts +``` +- ایمیل: `admin@admin.com` +- رمز عبور: `admin123` + +### 4. اجرای پروژه +```bash +npm run dev +``` + +پروژه روی `http://localhost:3000` اجرا می‌شود. + +## 📋 مراحل ساخت تیم + +### استپ 1: نام تیم و لوگو +1. به صفحه `/team` بروید +2. یک لوگو برای تیم انتخاب کنید (از 10 گزینه موجود) +3. نام تیم را وارد کنید (حداکثر 30 کاراکتر) +4. روی "بعدی: انتخاب ترکیب" کلیک کنید + +### استپ 2: انتخاب ترکیب و بازیکنان +1. یک ترکیب انتخاب کنید (4-3-3، 4-4-2، و...) +2. از لیست سمت راست، بازیکنان را انتخاب کنید +3. باید 11 بازیکن اصلی + 4 بازیکن ذخیره داشته باشید +4. بودجه شما 100 میلیون است +5. می‌توانید با drag & drop بازیکنان را جابجا کنید +6. یک کاپیتان و یک نایب کاپیتان انتخاب کنید +7. وقتی تیم کامل شد، روی "وارد رقابت شو" کلیک کنید + +## 🔧 اسکریپت‌های مفید + +### بررسی کاربران +```bash +npx tsx scripts/check-users.ts +``` + +### تست Session +مراجعه به: `http://localhost:3000/api/test-session` + +## 🎨 ویژگی‌های جدید + +### صفحه ساخت تیم +- ✅ طراحی استپ‌بای‌استپ (2 مرحله) +- ✅ انتخاب لوگو تیم (10 گزینه) +- ✅ انتخاب نام تیم با محدودیت 30 کاراکتر +- ✅ نمایش پیشرفت (Progress Indicator) +- ✅ طراحی مدرن و شماتیک +- ✅ رنگ‌بندی بهتر و gradient ها +- ✅ پیام‌های خطا و موفقیت واضح‌تر +- ✅ راهنمای تکمیل تیم + +### بهبودهای API +- ✅ بررسی وجود کاربر قبل از ساخت تیم +- ✅ ذخیره formation در زمان ساخت تیم +- ✅ پیام‌های خطای بهتر + +## 🐛 رفع مشکلات + +### خطای "Foreign key constraint violated" +این خطا زمانی رخ می‌دهد که userId در دیتابیس وجود ندارد. برای رفع: +1. مطمئن شوید که لاگین کرده‌اید +2. اسکریپت `check-users.ts` را اجرا کنید +3. در صورت نیاز، کاربر جدید بسازید + +### تیم ساخته نمی‌شود +1. از مرورگر خارج شوید (Logout) +2. دوباره وارد شوید (Login) +3. به صفحه `/team` بروید +4. اگر باز هم مشکل دارید، `/api/test-session` را چک کنید + +## 📱 صفحات + +- `/` - صفحه اصلی +- `/login` - ورود +- `/register` - ثبت‌نام +- `/team` - ساخت و مدیریت تیم +- `/admin` - پنل ادمین +- `/profile` - پروفایل کاربر + +## 🎯 TODO + +- [ ] آپلود تصویر برای لوگو تیم +- [ ] پیش‌نمایش تیم قبل از ثبت نهایی +- [ ] انیمیشن‌های بهتر برای drag & drop +- [ ] نمایش آمار بازیکنان در هنگام انتخاب +- [ ] فیلتر پیشرفته بازیکنان (بر اساس قیمت، امتیاز، و...) diff --git a/USER-GUIDE.md b/USER-GUIDE.md new file mode 100644 index 0000000..bf3e457 --- /dev/null +++ b/USER-GUIDE.md @@ -0,0 +1,168 @@ +# 📖 راهنمای کاربر - فانتزی فوتبال + +## 🎮 شروع کار + +### ثبت‌نام و ورود +1. به صفحه `/register` بروید +2. ایمیل و رمز عبور خود را وارد کنید +3. پس از ثبت‌نام، وارد شوید + +یا از کاربران تست استفاده کنید: +- **کاربر عادی**: `test@test.com` / `123456` +- **ادمین**: `admin@admin.com` / `admin123` + +## ⚽ ساخت تیم (2 مرحله) + +### مرحله 1️⃣: مشخصات تیم + +#### انتخاب لوگو +- 10 لوگوی مختلف در دسترس است +- روی هر لوگو کلیک کنید تا انتخاب شود +- لوگوی انتخابی با رنگ سبز هایلایت می‌شود + +#### نام تیم +- یک نام منحصر به فرد برای تیم خود انتخاب کنید +- حداکثر 30 کاراکتر +- مثال: "شیران طلایی"، "عقاب‌های آبی" + +#### دکمه بعدی +- پس از وارد کردن نام، روی "بعدی: انتخاب ترکیب" کلیک کنید +- تیم شما ساخته می‌شود و به مرحله بعد می‌روید + +### مرحله 2️⃣: انتخاب بازیکنان + +#### انتخاب ترکیب +شش ترکیب مختلف در دسترس است: +- **4-3-3**: 4 مدافع، 3 هافبک، 3 مهاجم (متعادل) +- **4-4-2**: 4 مدافع، 4 هافبک، 2 مهاجم (دفاعی) +- **4-5-1**: 4 مدافع، 5 هافبک، 1 مهاجم (خیلی دفاعی) +- **3-5-2**: 3 مدافع، 5 هافبک، 2 مهاجم (کنترل میانه) +- **3-4-3**: 3 مدافع، 4 هافبک، 3 مهاجم (تهاجمی) +- **5-3-2**: 5 مدافع، 3 هافبک، 2 مهاجم (خیلی دفاعی) + +#### بودجه +- بودجه اولیه: **100 میلیون** +- قیمت هر بازیکن از بودجه کم می‌شود +- بودجه باقیمانده در بالای صفحه نمایش داده می‌شود +- اگر بودجه کافی نداشته باشید، نمی‌توانید بازیکن اضافه کنید + +#### انتخاب بازیکنان + +##### فیلتر کردن +- **فیلتر پست**: GK (دروازه‌بان), DEF (مدافع), MID (هافبک), FWD (مهاجم) +- **جستجو**: نام بازیکن یا کشور را جستجو کنید + +##### اضافه کردن بازیکن +1. بازیکن مورد نظر را پیدا کنید +2. روی دکمه **+** کلیک کنید +3. بازیکن به تیم شما اضافه می‌شود + +##### تعداد بازیکنان مورد نیاز +- **11 بازیکن اصلی**: + - 1 دروازه‌بان + - تعداد مدافع، هافبک، مهاجم بر اساس ترکیب +- **4 بازیکن ذخیره** (حداقل): + - از هر پست حداقل 1 نفر + +#### مدیریت بازیکنان + +##### جابجایی (Drag & Drop) +- بازیکن را بگیرید و به جای دیگری بکشید +- می‌توانید بازیکنان اصلی و ذخیره را جابجا کنید + +##### منوی بازیکن +روی هر بازیکن کلیک کنید تا منو باز شود: +- **کاپیتان (©)**: امتیاز 2 برابر می‌شود +- **نایب کاپیتان (VC)**: اگر کاپیتان بازی نکند، جایگزین می‌شود +- **حذف از تیم**: بازیکن را از تیم حذف کنید + +##### بازیکنان حذف شده +- بازیکنانی که تیم ملی‌شان حذف شده با علامت ❌ نمایش داده می‌شوند +- این بازیکنان دیگر امتیازی نمی‌آورند +- بهتر است آنها را حذف کنید + +#### ثبت نهایی تیم +وقتی تیم کامل شد: +1. دکمه سبز "✓ تیم کامله! وارد رقابت شو" نمایش داده می‌شود +2. روی آن کلیک کنید +3. تیم شما برای تایید ادمین ارسال می‌شود +4. پس از تایید، می‌توانید در رقابت شرکت کنید + +## 📊 نمایش اطلاعات + +### در بالای صفحه +- **امتیاز**: مجموع امتیازات تیم شما +- **بودجه**: بودجه باقیمانده +- **بازیکن**: تعداد بازیکنان اصلی / 11 + +### در لیست بازیکنان +- **نام بازیکن** +- **کشور و پرچم** +- **پست** (GK, DEF, MID, FWD) +- **قیمت** (به میلیون) +- **امتیاز** (pts) + +### روی زمین +- **نام کوتاه بازیکن** +- **امتیاز** +- **کاپیتان (©)** یا **نایب کاپیتان (VC)** +- **وضعیت تیم ملی** (حذف شده یا فعال) + +## 💡 نکات مهم + +### استراتژی انتخاب +1. **تعادل**: بازیکنان گران و ارزان را ترکیب کنید +2. **فرم**: بازیکنانی که امتیاز بیشتری دارند را انتخاب کنید +3. **تیم ملی**: از بازیکنان تیم‌های قوی انتخاب کنید +4. **ذخیره**: ذخیره‌های خوب داشته باشید + +### کاپیتان +- کاپیتان امتیاز 2 برابر می‌آورد +- بهترین بازیکن خود را کاپیتان کنید +- نایب کاپیتان را هم انتخاب کنید + +### بودجه +- بودجه را هوشمندانه خرج کنید +- همه بودجه را خرج نکنید (برای تغییرات بعدی) +- بازیکنان ارزان با امتیاز خوب پیدا کنید + +## ❓ سوالات متداول + +### چرا نمی‌توانم بازیکن اضافه کنم؟ +- بودجه کافی ندارید +- تیم شما کامل است (15 بازیکن) +- بازیکن قبلاً در تیم شماست + +### چگونه ترکیب را عوض کنم؟ +- در بالای زمین، روی ترکیب مورد نظر کلیک کنید +- بازیکنان خودکار جابجا نمی‌شوند، باید دستی جابجا کنید + +### چرا تیمم ثبت نمی‌شود؟ +- باید 11 بازیکن اصلی داشته باشید +- باید 4 بازیکن ذخیره داشته باشید +- بودجه باید مثبت باشد + +### بازیکن حذف شده چیست؟ +- بازیکنی که تیم ملی‌اش از مسابقات حذف شده +- دیگر امتیازی نمی‌آورد +- بهتر است حذف شود + +## 🎯 مراحل بعدی + +پس از ساخت تیم: +1. منتظر تایید ادمین بمانید +2. به صفحه `/profile` بروید +3. تیم خود را مدیریت کنید +4. امتیازات را دنبال کنید +5. در جدول رتبه‌بندی شرکت کنید + +## 🆘 پشتیبانی + +اگر مشکلی دارید: +1. از مرورگر خارج شوید و دوباره وارد شوید +2. Cache مرورگر را پاک کنید +3. با ادمین تماس بگیرید + +--- + +**موفق باشید! ⚽🏆** diff --git a/app/(admin)/admin/countries/CountryForm.tsx b/app/(admin)/admin/countries/CountryForm.tsx index f209d9b..55fe171 100644 --- a/app/(admin)/admin/countries/CountryForm.tsx +++ b/app/(admin)/admin/countries/CountryForm.tsx @@ -11,7 +11,19 @@ export default function CountryForm({ countryId, }: { groups: Group[]; - initial?: { name: string; code: string; flagUrl?: string | null; groupId?: string | null }; + initial?: { + name: string; + code: string; + flagUrl?: string | null; + flagImage?: string | null; + groupId?: string | null; + confederation?: string | null; + qualificationMethod?: string | null; + qualificationDate?: string | null; + participationHistory?: string | null; + bestResult?: string | null; + description?: string | null; + }; countryId?: string; }) { const router = useRouter(); @@ -19,52 +31,194 @@ export default function CountryForm({ name: initial?.name ?? "", code: initial?.code ?? "", flagUrl: initial?.flagUrl ?? "", + flagImage: initial?.flagImage ?? "", groupId: initial?.groupId ?? "", + confederation: initial?.confederation ?? "", + qualificationMethod: initial?.qualificationMethod ?? "", + qualificationDate: initial?.qualificationDate ?? "", + participationHistory: initial?.participationHistory ?? "", + bestResult: initial?.bestResult ?? "", + description: initial?.description ?? "", }); const [loading, setLoading] = useState(false); async function handleSubmit(e: React.FormEvent) { e.preventDefault(); setLoading(true); - const payload = { ...form, groupId: form.groupId || null, flagUrl: form.flagUrl || null }; + const payload = { + ...form, + groupId: form.groupId || null, + flagUrl: form.flagUrl || null, + flagImage: form.flagImage || null, + confederation: form.confederation || null, + qualificationMethod: form.qualificationMethod || null, + qualificationDate: form.qualificationDate || null, + participationHistory: form.participationHistory || null, + bestResult: form.bestResult || null, + description: form.description || null, + }; const res = await fetch(countryId ? `/api/countries/${countryId}` : "/api/countries", { method: countryId ? "PUT" : "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }); - if (res.ok) { router.push("/admin/countries"); router.refresh(); } + if (res.ok) { + router.push("/admin/countries"); + router.refresh(); + } setLoading(false); } return ( -
-
- - setForm({ ...form, name: e.target.value })} - className="w-full border rounded-xl px-4 py-2.5 focus:outline-none focus:ring-2 focus:ring-green-500" required /> + +

اطلاعات پایه

+ +
+
+ + setForm({ ...form, name: e.target.value })} + className="w-full border rounded-xl px-4 py-2.5 focus:outline-none focus:ring-2 focus:ring-green-500" + required + /> +
+
+ + setForm({ ...form, code: e.target.value.toUpperCase() })} + maxLength={3} + className="w-full border rounded-xl px-4 py-2.5 focus:outline-none focus:ring-2 focus:ring-green-500" + required + /> +
-
- - setForm({ ...form, code: e.target.value.toUpperCase() })} - maxLength={3} - className="w-full border rounded-xl px-4 py-2.5 focus:outline-none focus:ring-2 focus:ring-green-500" required /> + +
+
+ + setForm({ ...form, flagUrl: e.target.value })} + placeholder="🇮🇷" + className="w-full border rounded-xl px-4 py-2.5 focus:outline-none focus:ring-2 focus:ring-green-500" + /> +
+
+ + setForm({ ...form, flagImage: e.target.value })} + placeholder="Flag_of_Iran.webp" + className="w-full border rounded-xl px-4 py-2.5 focus:outline-none focus:ring-2 focus:ring-green-500" + /> +

فایل باید در public/imgs/flags باشد

+
-
- - setForm({ ...form, flagUrl: e.target.value })} - placeholder="🇮🇷" - className="w-full border rounded-xl px-4 py-2.5 focus:outline-none focus:ring-2 focus:ring-green-500" /> + +
+
+ + +
+
+ + +
-
- - + +
+

اطلاعات راه‌یابی

+ +
+
+ + setForm({ ...form, qualificationMethod: e.target.value })} + placeholder="مثلاً: صعود از مرحله مقدماتی" + className="w-full border rounded-xl px-4 py-2.5 focus:outline-none focus:ring-2 focus:ring-green-500" + /> +
+
+ + setForm({ ...form, qualificationDate: e.target.value })} + placeholder="مثلاً: ۲۵ مارس ۲۰۲۵" + className="w-full border rounded-xl px-4 py-2.5 focus:outline-none focus:ring-2 focus:ring-green-500" + /> +
-