From aa9ed69dd2466bba379bacd8eebbbc5d42b5247f Mon Sep 17 00:00:00 2001 From: "a.alinaghipour" Date: Sun, 5 Apr 2026 15:53:20 +0330 Subject: [PATCH] first commit --- .gitignore | 47 + app/(admin)/admin/countries/CountryForm.tsx | 72 + .../admin/countries/[id]/edit/page.tsx | 18 + app/(admin)/admin/countries/new/page.tsx | 12 + app/(admin)/admin/countries/page.tsx | 43 + app/(admin)/admin/gameweeks/GameweekForm.tsx | 50 + app/(admin)/admin/gameweeks/page.tsx | 6 + app/(admin)/admin/layout.tsx | 46 + app/(admin)/admin/matches/MatchForm.tsx | 138 + app/(admin)/admin/matches/[id]/edit/page.tsx | 20 + app/(admin)/admin/matches/new/page.tsx | 15 + app/(admin)/admin/matches/page.tsx | 67 + app/(admin)/admin/page.tsx | 84 + app/(admin)/admin/players/PlayerForm.tsx | 106 + .../admin/players/[id]/edit/DeleteButton.tsx | 34 + app/(admin)/admin/players/[id]/edit/page.tsx | 32 + app/(admin)/admin/players/new/page.tsx | 12 + app/(admin)/admin/players/page.tsx | 51 + .../admin/rounds/ActivateRoundButton.tsx | 25 + app/(admin)/admin/rounds/RoundForm.tsx | 62 + .../match/[matchId]/MatchEventManager.tsx | 302 ++ .../rounds/[id]/match/[matchId]/page.tsx | 31 + app/(admin)/admin/rounds/[id]/page.tsx | 74 + app/(admin)/admin/rounds/page.tsx | 49 + .../admin/scoring/ScoringRulesEditor.tsx | 101 + app/(admin)/admin/scoring/page.tsx | 15 + app/(admin)/admin/stats/StatsForm.tsx | 138 + app/(admin)/admin/stats/page.tsx | 55 + app/(admin)/admin/teams/TeamApprovalRow.tsx | 82 + app/(admin)/admin/teams/page.tsx | 71 + app/(admin)/admin/users/page.tsx | 44 + app/(user)/profile/ProfileForm.tsx | 54 + app/(user)/profile/page.tsx | 56 + app/(user)/team/TeamBuilder.tsx | 467 +++ app/(user)/team/page.tsx | 24 + .../admin/matches/[id]/calc-points/route.ts | 78 + .../matches/[id]/events/[eventId]/route.ts | 13 + app/api/admin/matches/[id]/events/route.ts | 18 + app/api/admin/matches/[id]/lineup/route.ts | 23 + app/api/admin/scoring/route.ts | 22 + app/api/admin/teams/[id]/route.ts | 17 + app/api/admin/teams/route.ts | 20 + app/api/auth/[...nextauth]/route.ts | 5 + app/api/auth/register/route.ts | 23 + app/api/countries/[id]/route.ts | 23 + app/api/countries/route.ts | 22 + app/api/gameweeks/[id]/activate/route.ts | 16 + app/api/gameweeks/route.ts | 19 + app/api/leaderboard/route.ts | 20 + app/api/matches/[id]/route.ts | 32 + app/api/matches/[id]/stats/route.ts | 65 + app/api/matches/route.ts | 29 + app/api/payment/request/route.ts | 35 + app/api/payment/verify/route.ts | 43 + app/api/players/[id]/route.ts | 28 + app/api/players/route.ts | 32 + app/api/rounds/[id]/activate/route.ts | 14 + app/api/rounds/route.ts | 25 + app/api/team/captain/route.ts | 23 + app/api/team/formation/route.ts | 29 + app/api/team/players/route.ts | 65 + app/api/team/route.ts | 34 + app/api/team/submit/route.ts | 33 + app/api/user/profile/route.ts | 16 + app/error.tsx | 14 + app/globals.css | 15 + app/layout.tsx | 22 + app/leaderboard/page.tsx | 48 + app/login/page.tsx | 77 + app/matches/page.tsx | 82 + app/not-found.tsx | 14 + app/page.tsx | 69 + app/players/page.tsx | 72 + app/register/page.tsx | 71 + app/shop/ShopClient.tsx | 75 + app/shop/page.tsx | 53 + components/Navbar.tsx | 43 + components/PositionBadge.tsx | 21 + components/SessionProvider.tsx | 7 + lib/auth.ts | 52 + lib/db.ts | 13 + lib/points.ts | 69 + lib/session.ts | 15 + lib/teamValidation.ts | 86 + lib/zarinpal.ts | 55 + next.config.ts | 5 + package-lock.json | 2810 +++++++++++++++++ package.json | 34 + postcss.config.mjs | 7 + prisma/schema.prisma | 252 ++ prisma/seed.ts | 235 ++ proxy.ts | 21 + public/fonts/lahze.woff | Bin 0 -> 48696 bytes public/fonts/lahze.woff2 | Bin 0 -> 37744 bytes tailwind.config.ts | 18 + tsconfig.json | 41 + 96 files changed, 7721 insertions(+) create mode 100644 .gitignore create mode 100644 app/(admin)/admin/countries/CountryForm.tsx create mode 100644 app/(admin)/admin/countries/[id]/edit/page.tsx create mode 100644 app/(admin)/admin/countries/new/page.tsx create mode 100644 app/(admin)/admin/countries/page.tsx create mode 100644 app/(admin)/admin/gameweeks/GameweekForm.tsx create mode 100644 app/(admin)/admin/gameweeks/page.tsx create mode 100644 app/(admin)/admin/layout.tsx create mode 100644 app/(admin)/admin/matches/MatchForm.tsx create mode 100644 app/(admin)/admin/matches/[id]/edit/page.tsx create mode 100644 app/(admin)/admin/matches/new/page.tsx create mode 100644 app/(admin)/admin/matches/page.tsx create mode 100644 app/(admin)/admin/page.tsx create mode 100644 app/(admin)/admin/players/PlayerForm.tsx create mode 100644 app/(admin)/admin/players/[id]/edit/DeleteButton.tsx create mode 100644 app/(admin)/admin/players/[id]/edit/page.tsx create mode 100644 app/(admin)/admin/players/new/page.tsx create mode 100644 app/(admin)/admin/players/page.tsx create mode 100644 app/(admin)/admin/rounds/ActivateRoundButton.tsx create mode 100644 app/(admin)/admin/rounds/RoundForm.tsx create mode 100644 app/(admin)/admin/rounds/[id]/match/[matchId]/MatchEventManager.tsx create mode 100644 app/(admin)/admin/rounds/[id]/match/[matchId]/page.tsx create mode 100644 app/(admin)/admin/rounds/[id]/page.tsx create mode 100644 app/(admin)/admin/rounds/page.tsx create mode 100644 app/(admin)/admin/scoring/ScoringRulesEditor.tsx create mode 100644 app/(admin)/admin/scoring/page.tsx create mode 100644 app/(admin)/admin/stats/StatsForm.tsx create mode 100644 app/(admin)/admin/stats/page.tsx create mode 100644 app/(admin)/admin/teams/TeamApprovalRow.tsx create mode 100644 app/(admin)/admin/teams/page.tsx create mode 100644 app/(admin)/admin/users/page.tsx create mode 100644 app/(user)/profile/ProfileForm.tsx create mode 100644 app/(user)/profile/page.tsx create mode 100644 app/(user)/team/TeamBuilder.tsx create mode 100644 app/(user)/team/page.tsx create mode 100644 app/api/admin/matches/[id]/calc-points/route.ts create mode 100644 app/api/admin/matches/[id]/events/[eventId]/route.ts create mode 100644 app/api/admin/matches/[id]/events/route.ts create mode 100644 app/api/admin/matches/[id]/lineup/route.ts create mode 100644 app/api/admin/scoring/route.ts create mode 100644 app/api/admin/teams/[id]/route.ts create mode 100644 app/api/admin/teams/route.ts create mode 100644 app/api/auth/[...nextauth]/route.ts create mode 100644 app/api/auth/register/route.ts create mode 100644 app/api/countries/[id]/route.ts create mode 100644 app/api/countries/route.ts create mode 100644 app/api/gameweeks/[id]/activate/route.ts create mode 100644 app/api/gameweeks/route.ts create mode 100644 app/api/leaderboard/route.ts create mode 100644 app/api/matches/[id]/route.ts create mode 100644 app/api/matches/[id]/stats/route.ts create mode 100644 app/api/matches/route.ts create mode 100644 app/api/payment/request/route.ts create mode 100644 app/api/payment/verify/route.ts create mode 100644 app/api/players/[id]/route.ts create mode 100644 app/api/players/route.ts create mode 100644 app/api/rounds/[id]/activate/route.ts create mode 100644 app/api/rounds/route.ts create mode 100644 app/api/team/captain/route.ts create mode 100644 app/api/team/formation/route.ts create mode 100644 app/api/team/players/route.ts create mode 100644 app/api/team/route.ts create mode 100644 app/api/team/submit/route.ts create mode 100644 app/api/user/profile/route.ts create mode 100644 app/error.tsx create mode 100644 app/globals.css create mode 100644 app/layout.tsx create mode 100644 app/leaderboard/page.tsx create mode 100644 app/login/page.tsx create mode 100644 app/matches/page.tsx create mode 100644 app/not-found.tsx create mode 100644 app/page.tsx create mode 100644 app/players/page.tsx create mode 100644 app/register/page.tsx create mode 100644 app/shop/ShopClient.tsx create mode 100644 app/shop/page.tsx create mode 100644 components/Navbar.tsx create mode 100644 components/PositionBadge.tsx create mode 100644 components/SessionProvider.tsx create mode 100644 lib/auth.ts create mode 100644 lib/db.ts create mode 100644 lib/points.ts create mode 100644 lib/session.ts create mode 100644 lib/teamValidation.ts create mode 100644 lib/zarinpal.ts create mode 100644 next.config.ts create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 postcss.config.mjs create mode 100644 prisma/schema.prisma create mode 100644 prisma/seed.ts create mode 100644 proxy.ts create mode 100644 public/fonts/lahze.woff create mode 100644 public/fonts/lahze.woff2 create mode 100644 tailwind.config.ts create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7458623 --- /dev/null +++ b/.gitignore @@ -0,0 +1,47 @@ +# Dependencies +node_modules/ +.pnp +.pnp.js + +# Next.js +.next/ +out/ +build/ + +# Environment variables +.env +.env.local +.env.development.local +.env.test.local +.env.production.local +.env2 + +# Prisma +prisma/migrations/ + +# Logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +*.log + +# OS +.DS_Store +Thumbs.db +desktop.ini + +# Editor +.vscode/ +.idea/ +*.swp +*.swo + +# TypeScript +*.tsbuildinfo +next-env.d.ts + +# Misc +.vercel +.turbo +coverage/ diff --git a/app/(admin)/admin/countries/CountryForm.tsx b/app/(admin)/admin/countries/CountryForm.tsx new file mode 100644 index 0000000..f209d9b --- /dev/null +++ b/app/(admin)/admin/countries/CountryForm.tsx @@ -0,0 +1,72 @@ +"use client"; + +import { useState } from "react"; +import { useRouter } from "next/navigation"; + +type Group = { id: string; name: string }; + +export default function CountryForm({ + groups, + initial, + countryId, +}: { + groups: Group[]; + initial?: { name: string; code: string; flagUrl?: string | null; groupId?: string | null }; + countryId?: string; +}) { + const router = useRouter(); + const [form, setForm] = useState({ + name: initial?.name ?? "", + code: initial?.code ?? "", + flagUrl: initial?.flagUrl ?? "", + groupId: initial?.groupId ?? "", + }); + 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 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(); } + 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, 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" /> +
+
+ + +
+ +
+ ); +} diff --git a/app/(admin)/admin/countries/[id]/edit/page.tsx b/app/(admin)/admin/countries/[id]/edit/page.tsx new file mode 100644 index 0000000..c9dadc1 --- /dev/null +++ b/app/(admin)/admin/countries/[id]/edit/page.tsx @@ -0,0 +1,18 @@ +import { db } from "@/lib/db"; +import { notFound } from "next/navigation"; +import CountryForm from "../../CountryForm"; + +export default async function EditCountryPage({ params }: { params: { id: string } }) { + const [country, groups] = await Promise.all([ + db.country.findUnique({ where: { id: params.id } }), + db.group.findMany({ orderBy: { name: "asc" } }), + ]); + if (!country) notFound(); + + return ( +
+

ویرایش تیم ملی

+ +
+ ); +} diff --git a/app/(admin)/admin/countries/new/page.tsx b/app/(admin)/admin/countries/new/page.tsx new file mode 100644 index 0000000..3bdf69b --- /dev/null +++ b/app/(admin)/admin/countries/new/page.tsx @@ -0,0 +1,12 @@ +import { db } from "@/lib/db"; +import CountryForm from "../CountryForm"; + +export default async function NewCountryPage() { + const groups = await db.group.findMany({ orderBy: { name: "asc" } }); + return ( +
+

تیم ملی جدید

+ +
+ ); +} diff --git a/app/(admin)/admin/countries/page.tsx b/app/(admin)/admin/countries/page.tsx new file mode 100644 index 0000000..2e3b112 --- /dev/null +++ b/app/(admin)/admin/countries/page.tsx @@ -0,0 +1,43 @@ +import { db } from "@/lib/db"; +import Link from "next/link"; + +export default async function AdminCountriesPage() { + const countries = await db.country.findMany({ + include: { + group: true, + _count: { select: { players: true } }, + }, + orderBy: { name: "asc" }, + }); + + return ( +
+
+

تیم‌های ملی

+ + + تیم جدید + +
+
+ {countries.map((c) => ( +
+
+
+ {c.flagUrl ?? "🏳️"} +
+
+
{c.name}
+
+ {c.code} · گروه {c.group?.name ?? "-"} · {c._count.players} بازیکن +
+
+
+ + ویرایش + +
+ ))} +
+
+ ); +} diff --git a/app/(admin)/admin/gameweeks/GameweekForm.tsx b/app/(admin)/admin/gameweeks/GameweekForm.tsx new file mode 100644 index 0000000..2b1b5cd --- /dev/null +++ b/app/(admin)/admin/gameweeks/GameweekForm.tsx @@ -0,0 +1,50 @@ +"use client"; + +import { useState } from "react"; +import { useRouter } from "next/navigation"; + +export default function GameweekForm() { + const router = useRouter(); + const [form, setForm] = useState({ number: "", name: "", deadline: "" }); + const [loading, setLoading] = useState(false); + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + setLoading(true); + const res = await fetch("/api/gameweeks", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ ...form, number: parseInt(form.number) }), + }); + if (res.ok) { + setForm({ number: "", name: "", deadline: "" }); + router.refresh(); + } + setLoading(false); + } + + return ( +
+
+ + setForm({ ...form, number: 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 })} + placeholder="مثلاً: مرحله گروهی - روز ۱" + className="w-full border rounded-xl px-4 py-2.5 focus:outline-none focus:ring-2 focus:ring-green-500" required /> +
+
+ + setForm({ ...form, deadline: 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 /> +
+ +
+ ); +} diff --git a/app/(admin)/admin/gameweeks/page.tsx b/app/(admin)/admin/gameweeks/page.tsx new file mode 100644 index 0000000..c2af529 --- /dev/null +++ b/app/(admin)/admin/gameweeks/page.tsx @@ -0,0 +1,6 @@ +import { redirect } from "next/navigation"; + +// هفته‌های بازی به دورهای بازی تغییر نام داد +export default function GameweeksPage() { + redirect("/admin/rounds"); +} diff --git a/app/(admin)/admin/layout.tsx b/app/(admin)/admin/layout.tsx new file mode 100644 index 0000000..edc5163 --- /dev/null +++ b/app/(admin)/admin/layout.tsx @@ -0,0 +1,46 @@ +import Link from "next/link"; +import { requireAdmin } from "@/lib/session"; + +export default async function AdminLayout({ children }: { children: React.ReactNode }) { + await requireAdmin(); + + const links = [ + { href: "/admin", label: "داشبورد", icon: "📊" }, + { href: "/admin/rounds", label: "دورهای بازی", icon: "🏆" }, + { href: "/admin/players", label: "بازیکنان", icon: "⚽" }, + { href: "/admin/matches", label: "بازی‌ها", icon: "🏟️" }, + { href: "/admin/scoring", label: "قوانین امتیازدهی", icon: "⚙️" }, + { href: "/admin/teams", label: "تیم‌های فانتزی", icon: "👥" }, + { href: "/admin/countries", label: "تیم‌های ملی", icon: "🌍" }, + { href: "/admin/users", label: "کاربران", icon: "👤" }, + ]; + + return ( +
+ +
{children}
+
+ ); +} diff --git a/app/(admin)/admin/matches/MatchForm.tsx b/app/(admin)/admin/matches/MatchForm.tsx new file mode 100644 index 0000000..e65e2db --- /dev/null +++ b/app/(admin)/admin/matches/MatchForm.tsx @@ -0,0 +1,138 @@ +"use client"; + +import { useState } from "react"; +import { useRouter } from "next/navigation"; + +type Country = { id: string; name: string }; +type Round = { id: string; name: string; number: number }; + +export default function MatchForm({ + countries, + rounds, + initial, + matchId, +}: { + countries: Country[]; + rounds: Round[]; + initial?: any; + matchId?: string; +}) { + const router = useRouter(); + const [form, setForm] = useState({ + homeTeamId: initial?.homeTeamId ?? "", + awayTeamId: initial?.awayTeamId ?? "", + stage: initial?.stage ?? "GROUP", + status: initial?.status ?? "SCHEDULED", + matchDate: initial?.matchDate ? new Date(initial.matchDate).toISOString().slice(0, 16) : "", + homeScore: initial?.homeScore ?? "", + awayScore: initial?.awayScore ?? "", + roundId: initial?.roundId ?? "", + }); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(""); + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + setLoading(true); + const payload = { + ...form, + homeScore: form.homeScore !== "" ? parseInt(String(form.homeScore)) : null, + awayScore: form.awayScore !== "" ? parseInt(String(form.awayScore)) : null, + roundId: form.roundId || null, + }; + const res = await fetch(matchId ? `/api/matches/${matchId}` : "/api/matches", { + method: matchId ? "PUT" : "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(payload), + }); + if (res.ok) { + router.push("/admin/matches"); + router.refresh(); + } else { + const d = await res.json(); + setError(d.error ?? "خطا"); + } + setLoading(false); + } + + const stages = [ + { value: "GROUP", label: "مرحله گروهی" }, + { value: "ROUND_OF_16", label: "یک‌هشتم نهایی" }, + { value: "QUARTER_FINAL", label: "یک‌چهارم نهایی" }, + { value: "SEMI_FINAL", label: "نیمه‌نهایی" }, + { value: "THIRD_PLACE", label: "رده‌بندی" }, + { value: "FINAL", label: "فینال" }, + ]; + + return ( +
+ {error &&

{error}

} +
+
+ + +
+
+ + +
+
+
+
+ + setForm({ ...form, homeScore: e.target.value })} + className="w-full border rounded-xl px-3 py-2.5 focus:outline-none focus:ring-2 focus:ring-green-500" /> +
+
+ + setForm({ ...form, awayScore: e.target.value })} + className="w-full border rounded-xl px-3 py-2.5 focus:outline-none focus:ring-2 focus:ring-green-500" /> +
+
+
+ + +
+
+ + +
+
+ + setForm({ ...form, matchDate: e.target.value })} + className="w-full border rounded-xl px-3 py-2.5 focus:outline-none focus:ring-2 focus:ring-green-500" required /> +
+
+ + +
+ +
+ ); +} diff --git a/app/(admin)/admin/matches/[id]/edit/page.tsx b/app/(admin)/admin/matches/[id]/edit/page.tsx new file mode 100644 index 0000000..76f5edf --- /dev/null +++ b/app/(admin)/admin/matches/[id]/edit/page.tsx @@ -0,0 +1,20 @@ +import { db } from "@/lib/db"; +import { notFound } from "next/navigation"; +import MatchForm from "../../MatchForm"; + +export default async function EditMatchPage({ params }: { params: { id: string } }) { + const [match, countries, rounds] = await Promise.all([ + db.match.findUnique({ where: { id: params.id } }), + db.country.findMany({ orderBy: { name: "asc" } }), + db.round.findMany({ orderBy: { number: "asc" } }), + ]); + + if (!match) notFound(); + + return ( +
+

ویرایش بازی

+ +
+ ); +} diff --git a/app/(admin)/admin/matches/new/page.tsx b/app/(admin)/admin/matches/new/page.tsx new file mode 100644 index 0000000..e609b07 --- /dev/null +++ b/app/(admin)/admin/matches/new/page.tsx @@ -0,0 +1,15 @@ +import { db } from "@/lib/db"; +import MatchForm from "../MatchForm"; + +export default async function NewMatchPage() { + const [countries, rounds] = await Promise.all([ + db.country.findMany({ orderBy: { name: "asc" } }), + db.round.findMany({ orderBy: { number: "asc" } }), + ]); + return ( +
+

بازی جدید

+ +
+ ); +} diff --git a/app/(admin)/admin/matches/page.tsx b/app/(admin)/admin/matches/page.tsx new file mode 100644 index 0000000..875533a --- /dev/null +++ b/app/(admin)/admin/matches/page.tsx @@ -0,0 +1,67 @@ +import { db } from "@/lib/db"; +import Link from "next/link"; + +const stageLabel: Record = { + GROUP: "گروهی", ROUND_OF_16: "یک‌هشتم", QUARTER_FINAL: "یک‌چهارم", + SEMI_FINAL: "نیمه‌نهایی", THIRD_PLACE: "رده‌بندی", FINAL: "فینال", +}; + +export default async function AdminMatchesPage() { + const matches = await db.match.findMany({ + include: { homeTeam: true, awayTeam: true, round: true }, + orderBy: { matchDate: "asc" }, + }); + + return ( +
+
+

بازی‌ها

+ + + بازی جدید + +
+
+ + + + + + + + + + + + + + + {matches.map((m) => ( + + + + + + + + + + + ))} + +
میزباننتیجهمهمانمرحلهدورتاریخوضعیت
{m.homeTeam.name} + {m.status !== "SCHEDULED" ? `${m.homeScore ?? 0} - ${m.awayScore ?? 0}` : "-"} + {m.awayTeam.name}{stageLabel[m.stage]}{m.round?.name ?? "-"}{new Date(m.matchDate).toLocaleDateString("fa-IR")} + + {m.status === "LIVE" ? "زنده" : m.status === "FINISHED" ? "پایان" : "برنامه"} + + + ویرایش +
+
+
+ ); +} diff --git a/app/(admin)/admin/page.tsx b/app/(admin)/admin/page.tsx new file mode 100644 index 0000000..f3c9779 --- /dev/null +++ b/app/(admin)/admin/page.tsx @@ -0,0 +1,84 @@ +import { db } from "@/lib/db"; +import Link from "next/link"; + +export default async function AdminDashboard() { + const [playerCount, userCount, teamCount, matchCount] = await Promise.all([ + db.player.count(), + db.user.count(), + db.team.count(), + db.match.count(), + ]); + + const topTeams = await db.team.findMany({ + orderBy: { totalPoints: "desc" }, + take: 5, + include: { user: { select: { name: true } } }, + }); + + const recentMatches = await db.match.findMany({ + orderBy: { matchDate: "desc" }, + take: 5, + include: { homeTeam: true, awayTeam: true }, + }); + + return ( +
+

داشبورد

+ +
+ {[ + { label: "بازیکنان", value: playerCount, icon: "⚽", color: "bg-green-500" }, + { label: "کاربران", value: userCount, icon: "👥", color: "bg-blue-500" }, + { label: "تیم‌های فانتزی", value: teamCount, icon: "🏆", color: "bg-yellow-500" }, + { label: "بازی‌ها", value: matchCount, icon: "🏟️", color: "bg-purple-500" }, + ].map((s) => ( +
+
+ {s.icon} +
+
+
{s.value}
+
{s.label}
+
+
+ ))} +
+ +
+
+

برترین تیم‌ها

+
+ {topTeams.map((t, i) => ( +
+
+ {i + 1} +
+
{t.name}
+
{t.user.name}
+
+
+ {t.totalPoints} pts +
+ ))} +
+
+ +
+

آخرین بازی‌ها

+
+ {recentMatches.map((m) => ( +
+ {m.homeTeam.name} + + {m.status === "FINISHED" ? `${m.homeScore} - ${m.awayScore}` : "vs"} + + {m.awayTeam.name} +
+ ))} + {recentMatches.length === 0 &&

بازی‌ای ثبت نشده

} +
+
+
+
+ ); +} diff --git a/app/(admin)/admin/players/PlayerForm.tsx b/app/(admin)/admin/players/PlayerForm.tsx new file mode 100644 index 0000000..7e54398 --- /dev/null +++ b/app/(admin)/admin/players/PlayerForm.tsx @@ -0,0 +1,106 @@ +"use client"; + +import { useState } from "react"; +import { useRouter } from "next/navigation"; + +type Country = { id: string; name: string }; + +export default function PlayerForm({ + countries, + initial, + playerId, +}: { + countries: Country[]; + initial?: { name: string; position: string; countryId: string; price: number }; + playerId?: string; +}) { + const router = useRouter(); + const [form, setForm] = useState({ + name: initial?.name ?? "", + position: initial?.position ?? "FWD", + countryId: initial?.countryId ?? "", + price: initial?.price ?? 5.0, + }); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(""); + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + setLoading(true); + const res = await fetch(playerId ? `/api/players/${playerId}` : "/api/players", { + method: playerId ? "PUT" : "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(form), + }); + if (res.ok) { + router.push("/admin/players"); + router.refresh(); + } else { + const d = await res.json(); + setError(d.error ?? "خطا"); + } + setLoading(false); + } + + return ( +
+ {error &&

{error}

} +
+ + 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, price: parseFloat(e.target.value) })} + className="w-full border rounded-xl px-4 py-2.5 focus:outline-none focus:ring-2 focus:ring-green-500" + /> +
+ +
+ ); +} diff --git a/app/(admin)/admin/players/[id]/edit/DeleteButton.tsx b/app/(admin)/admin/players/[id]/edit/DeleteButton.tsx new file mode 100644 index 0000000..0cadf9f --- /dev/null +++ b/app/(admin)/admin/players/[id]/edit/DeleteButton.tsx @@ -0,0 +1,34 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { useState } from "react"; + +export default function DeleteButton({ playerId }: { playerId: string }) { + const router = useRouter(); + const [confirm, setConfirm] = useState(false); + + async function handleDelete() { + await fetch(`/api/players/${playerId}`, { method: "DELETE" }); + router.push("/admin/players"); + router.refresh(); + } + + if (confirm) { + return ( +
+ + +
+ ); + } + + return ( + + ); +} diff --git a/app/(admin)/admin/players/[id]/edit/page.tsx b/app/(admin)/admin/players/[id]/edit/page.tsx new file mode 100644 index 0000000..dfd2c0a --- /dev/null +++ b/app/(admin)/admin/players/[id]/edit/page.tsx @@ -0,0 +1,32 @@ +import { db } from "@/lib/db"; +import { notFound } from "next/navigation"; +import PlayerForm from "../../PlayerForm"; +import DeleteButton from "./DeleteButton"; + +export default async function EditPlayerPage({ params }: { params: { id: string } }) { + const [player, countries] = await Promise.all([ + db.player.findUnique({ where: { id: params.id } }), + db.country.findMany({ orderBy: { name: "asc" } }), + ]); + + if (!player) notFound(); + + return ( +
+
+

ویرایش بازیکن

+ +
+ +
+ ); +} diff --git a/app/(admin)/admin/players/new/page.tsx b/app/(admin)/admin/players/new/page.tsx new file mode 100644 index 0000000..9be5015 --- /dev/null +++ b/app/(admin)/admin/players/new/page.tsx @@ -0,0 +1,12 @@ +import { db } from "@/lib/db"; +import PlayerForm from "../PlayerForm"; + +export default async function NewPlayerPage() { + const countries = await db.country.findMany({ orderBy: { name: "asc" } }); + return ( +
+

بازیکن جدید

+ +
+ ); +} diff --git a/app/(admin)/admin/players/page.tsx b/app/(admin)/admin/players/page.tsx new file mode 100644 index 0000000..fddd21a --- /dev/null +++ b/app/(admin)/admin/players/page.tsx @@ -0,0 +1,51 @@ +import { db } from "@/lib/db"; +import Link from "next/link"; +import PositionBadge from "@/components/PositionBadge"; + +export default async function AdminPlayersPage() { + const players = await db.player.findMany({ + include: { country: true }, + orderBy: { name: "asc" }, + }); + + return ( +
+
+

بازیکنان

+ + + بازیکن جدید + +
+
+ + + + + + + + + + + + + {players.map((p) => ( + + + + + + + + + ))} + +
نامپستتیم ملیقیمتامتیاز
{p.name}{p.country.name}{p.price}M{p.totalPoints} + + ویرایش + +
+
+
+ ); +} diff --git a/app/(admin)/admin/rounds/ActivateRoundButton.tsx b/app/(admin)/admin/rounds/ActivateRoundButton.tsx new file mode 100644 index 0000000..5f3ae4a --- /dev/null +++ b/app/(admin)/admin/rounds/ActivateRoundButton.tsx @@ -0,0 +1,25 @@ +"use client"; + +import { useState } from "react"; +import { useRouter } from "next/navigation"; + +export default function ActivateRoundButton({ roundId, isActive }: { roundId: string; isActive: boolean }) { + const router = useRouter(); + const [loading, setLoading] = useState(false); + + async function activate() { + setLoading(true); + await fetch(`/api/rounds/${roundId}/activate`, { method: "POST" }); + router.refresh(); + setLoading(false); + } + + if (isActive) return ✓ فعال; + + return ( + + ); +} diff --git a/app/(admin)/admin/rounds/RoundForm.tsx b/app/(admin)/admin/rounds/RoundForm.tsx new file mode 100644 index 0000000..90402da --- /dev/null +++ b/app/(admin)/admin/rounds/RoundForm.tsx @@ -0,0 +1,62 @@ +"use client"; + +import { useState } from "react"; +import { useRouter } from "next/navigation"; + +export default function RoundForm() { + const router = useRouter(); + const [form, setForm] = useState({ number: "", name: "", deadline: "" }); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(""); + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + setLoading(true); + setError(""); + const res = await fetch("/api/rounds", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ ...form, number: parseInt(form.number) }), + }); + if (res.ok) { + setForm({ number: "", name: "", deadline: "" }); + router.refresh(); + } else { + const d = await res.json(); + setError(d.error ?? "خطا در ذخیره"); + } + setLoading(false); + } + + return ( +
+ {error &&

{error}

} +
+ + setForm({ ...form, number: 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 })} + placeholder="مثلاً: دور اول - مرحله گروهی" + className="w-full border rounded-xl px-4 py-2.5 focus:outline-none focus:ring-2 focus:ring-green-500" + required /> +
+
+ + setForm({ ...form, deadline: 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 /> +
+ +
+ ); +} diff --git a/app/(admin)/admin/rounds/[id]/match/[matchId]/MatchEventManager.tsx b/app/(admin)/admin/rounds/[id]/match/[matchId]/MatchEventManager.tsx new file mode 100644 index 0000000..2405edd --- /dev/null +++ b/app/(admin)/admin/rounds/[id]/match/[matchId]/MatchEventManager.tsx @@ -0,0 +1,302 @@ +"use client"; + +import { useState } from "react"; +import { useRouter } from "next/navigation"; + +const EVENT_LABELS: Record = { + GOAL: { label: "گل", icon: "⚽", color: "bg-green-100 text-green-700" }, + ASSIST: { label: "پاس گل", icon: "🎯", color: "bg-blue-100 text-blue-700" }, + YELLOW_CARD: { label: "کارت زرد", icon: "🟨", color: "bg-yellow-100 text-yellow-700" }, + RED_CARD: { label: "کارت قرمز", icon: "🟥", color: "bg-red-100 text-red-700" }, + SECOND_YELLOW: { label: "کارت زرد دوم", icon: "🟨🟥", color: "bg-orange-100 text-orange-700" }, + SUBSTITUTION_IN: { label: "ورود تعویضی", icon: "🔄↑", color: "bg-teal-100 text-teal-700" }, + SUBSTITUTION_OUT: { label: "خروج تعویضی", icon: "🔄↓", color: "bg-gray-100 text-gray-600" }, + INJURY_NO_SUB: { label: "مصدومیت بدون تعویض", icon: "🤕", color: "bg-red-50 text-red-500" }, + CLEAN_SHEET: { label: "کلین‌شیت", icon: "🧤", color: "bg-green-100 text-green-700" }, + PENALTY_SAVED: { label: "پنالتی گرفته", icon: "🛡️", color: "bg-purple-100 text-purple-700" }, + PENALTY_MISSED: { label: "پنالتی از دست داده", icon: "❌", color: "bg-red-100 text-red-600" }, + OWN_GOAL: { label: "گل به خودی", icon: "😬", color: "bg-orange-100 text-orange-700" }, + EXTRA_TIME_BONUS: { label: "وقت اضافه", icon: "⏱️", color: "bg-indigo-100 text-indigo-700" }, + MOTM: { label: "بازیکن برتر", icon: "🌟", color: "bg-yellow-100 text-yellow-700" }, +}; + +export default function MatchEventManager({ match, roundId }: { match: any; roundId: string }) { + const router = useRouter(); + const [tab, setTab] = useState<"events" | "lineup" | "score">("events"); + const [eventForm, setEventForm] = useState({ playerId: "", type: "GOAL", minute: "", extraInfo: "" }); + const [score, setScore] = useState({ homeScore: match.homeScore ?? 0, awayScore: match.awayScore ?? 0, status: match.status }); + const [loading, setLoading] = useState(false); + const [msg, setMsg] = useState(""); + + const allPlayers = [...match.homeTeam.players, ...match.awayTeam.players]; + + async function addEvent() { + if (!eventForm.playerId || !eventForm.type) return; + setLoading(true); + const res = await fetch(`/api/admin/matches/${match.id}/events`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ ...eventForm, minute: eventForm.minute ? parseInt(eventForm.minute) : null }), + }); + if (res.ok) { router.refresh(); setMsg("رویداد ثبت شد"); setEventForm({ playerId: "", type: "GOAL", minute: "", extraInfo: "" }); } + else { const d = await res.json(); setMsg(d.error); } + setLoading(false); + } + + async function deleteEvent(eventId: string) { + await fetch(`/api/admin/matches/${match.id}/events/${eventId}`, { method: "DELETE" }); + router.refresh(); + } + + async function updateScore() { + setLoading(true); + await fetch(`/api/matches/${match.id}`, { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(score), + }); + router.refresh(); + setLoading(false); + setMsg("نتیجه ذخیره شد"); + } + + async function calcPoints() { + setLoading(true); + const res = await fetch(`/api/admin/matches/${match.id}/calc-points`, { method: "POST" }); + const d = await res.json(); + setMsg(res.ok ? "امتیازات محاسبه شد" : d.error); + setLoading(false); + router.refresh(); + } + + return ( +
+ {/* پنل چپ - رویدادها */} +
+ {/* تب‌ها */} +
+ {[["events", "رویدادها"], ["lineup", "ترکیب تیم‌ها"], ["score", "نتیجه"]].map(([key, label]) => ( + + ))} + +
+ + {msg &&
{msg}
} + + {tab === "events" && ( +
+ {/* فرم ثبت رویداد */} +
+

ثبت رویداد جدید

+
+
+ + +
+
+ + +
+
+ + setEventForm({ ...eventForm, minute: e.target.value })} + placeholder="مثلاً 45" + className="w-full border rounded-xl px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-green-400" /> +
+
+ + setEventForm({ ...eventForm, extraInfo: e.target.value })} + placeholder="مثلاً نام بازیکن تعویضی" + className="w-full border rounded-xl px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-green-400" /> +
+
+ +
+ + {/* لیست رویدادها */} +
+
رویدادهای ثبت‌شده ({match.events.length})
+ {match.events.length === 0 ? ( +
رویدادی ثبت نشده
+ ) : ( +
+ {match.events.map((ev: any) => { + const evInfo = EVENT_LABELS[ev.type]; + return ( +
+
+ + {evInfo?.icon} {evInfo?.label} + + {ev.player.name} + {ev.minute && {ev.minute}'} + {ev.extraInfo && ({ev.extraInfo})} +
+ +
+ ); + })} +
+ )} +
+
+ )} + + {tab === "lineup" && ( + + )} + + {tab === "score" && ( +
+

نتیجه بازی

+
+
+
{match.homeTeam.name}
+ setScore({ ...score, homeScore: parseInt(e.target.value) || 0 })} + className="w-20 border-2 rounded-xl px-3 py-3 text-center text-2xl font-bold focus:outline-none focus:border-green-500" /> +
+
-
+
+
{match.awayTeam.name}
+ setScore({ ...score, awayScore: parseInt(e.target.value) || 0 })} + className="w-20 border-2 rounded-xl px-3 py-3 text-center text-2xl font-bold focus:outline-none focus:border-green-500" /> +
+
+
+ + +
+ +
+ )} +
+ + {/* پنل راست - آمار بازیکنان */} +
+
+
آمار بازیکنان
+
+ {match.playerStats.map((s: any) => ( +
+
+ {s.player.name} + {s.points} pts +
+
+ {s.goals > 0 && ⚽ {s.goals}} + {s.assists > 0 && 🎯 {s.assists}} + {s.yellowCards > 0 && 🟨 {s.yellowCards}} + {s.redCards > 0 && 🟥} + {s.cleanSheet && 🧤} + {s.minutesPlayed}' +
+
+ ))} + {match.playerStats.length === 0 &&
امتیازی محاسبه نشده
} +
+
+
+
+ ); +} + +function LineupTab({ match }: { match: any }) { + const [homeLineup, setHomeLineup] = useState([]); + const [awayLineup, setAwayLineup] = useState([]); + const [homeFormation, setHomeFormation] = useState(match.homeTeam.defaultFormation ?? "4-3-3"); + const [awayFormation, setAwayFormation] = useState(match.awayTeam.defaultFormation ?? "4-3-3"); + const [loading, setLoading] = useState(false); + const [msg, setMsg] = useState(""); + + async function saveLineup() { + setLoading(true); + await fetch(`/api/admin/matches/${match.id}/lineup`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify([ + { countryId: match.homeTeamId, formation: homeFormation, playerIds: homeLineup }, + { countryId: match.awayTeamId, formation: awayFormation, playerIds: awayLineup }, + ]), + }); + setMsg("ترکیب ذخیره شد"); + setLoading(false); + } + + const togglePlayer = (id: string, team: "home" | "away") => { + if (team === "home") setHomeLineup((prev) => prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]); + else setAwayLineup((prev) => prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]); + }; + + return ( +
+

ثبت ترکیب رسمی

+ {msg &&
{msg}
} +
+ {[{ team: match.homeTeam, side: "home" as const, lineup: homeLineup, formation: homeFormation, setFormation: setHomeFormation }, + { team: match.awayTeam, side: "away" as const, lineup: awayLineup, formation: awayFormation, setFormation: setAwayFormation } + ].map(({ team, side, lineup, formation, setFormation }) => ( +
+
{team.flagUrl} {team.name}
+ setFormation(e.target.value)} + placeholder="ترکیب مثلاً 4-3-3" + className="w-full border rounded-lg px-3 py-1.5 text-sm mb-2 focus:outline-none focus:ring-2 focus:ring-green-400" /> +
+ {team.players.map((p: any) => ( + + ))} +
+
{lineup.length} بازیکن انتخاب شده
+
+ ))} +
+ +
+ ); +} diff --git a/app/(admin)/admin/rounds/[id]/match/[matchId]/page.tsx b/app/(admin)/admin/rounds/[id]/match/[matchId]/page.tsx new file mode 100644 index 0000000..2f60aef --- /dev/null +++ b/app/(admin)/admin/rounds/[id]/match/[matchId]/page.tsx @@ -0,0 +1,31 @@ +import { db } from "@/lib/db"; +import { notFound } from "next/navigation"; +import Link from "next/link"; +import MatchEventManager from "./MatchEventManager"; + +export default async function MatchDetailPage({ params }: { params: { id: string; matchId: string } }) { + const match = await db.match.findUnique({ + where: { id: params.matchId }, + include: { + homeTeam: { include: { players: { orderBy: { position: "asc" } } } }, + awayTeam: { include: { players: { orderBy: { position: "asc" } } } }, + events: { include: { player: true }, orderBy: { minute: "asc" } }, + lineups: true, + playerStats: { include: { player: true } }, + }, + }); + + if (!match) notFound(); + + return ( +
+
+ ← دور +

+ {match.homeTeam.name} {match.homeScore ?? "-"} - {match.awayScore ?? "-"} {match.awayTeam.name} +

+
+ +
+ ); +} diff --git a/app/(admin)/admin/rounds/[id]/page.tsx b/app/(admin)/admin/rounds/[id]/page.tsx new file mode 100644 index 0000000..1773752 --- /dev/null +++ b/app/(admin)/admin/rounds/[id]/page.tsx @@ -0,0 +1,74 @@ +import { db } from "@/lib/db"; +import { notFound } from "next/navigation"; +import Link from "next/link"; + +const statusStyle: Record = { + SCHEDULED: "bg-gray-100 text-gray-600", + LIVE: "bg-red-100 text-red-600", + FINISHED: "bg-green-100 text-green-700", +}; +const statusLabel: Record = { SCHEDULED: "برنامه", LIVE: "🔴 زنده", FINISHED: "پایان" }; + +export default async function RoundDetailPage({ params }: { params: { id: string } }) { + const round = await db.round.findUnique({ + where: { id: params.id }, + include: { + matches: { + include: { + homeTeam: true, awayTeam: true, + _count: { select: { events: true, lineups: true } }, + }, + orderBy: { matchDate: "asc" }, + }, + }, + }); + + if (!round) notFound(); + + return ( +
+
+ ← دورها +

{round.name}

+ {round.isActive && فعال} +
+ +
+ {round.matches.map((m) => ( +
+
+
+ {m.homeTeam.name} + {m.homeTeam.flagUrl} +
+
+ {m.status !== "SCHEDULED" ? ( +
{m.homeScore} - {m.awayScore}
+ ) : ( +
{new Date(m.matchDate).toLocaleDateString("fa-IR")}
+ )} + + {statusLabel[m.status]} + +
+
+ {m.awayTeam.flagUrl} + {m.awayTeam.name} +
+
+
+
{m._count.events} رویداد
+
{m._count.lineups > 0 ? "✓ ترکیب" : "بدون ترکیب"}
+
+ + جزئیات + +
+
+
+ ))} +
+
+ ); +} diff --git a/app/(admin)/admin/rounds/page.tsx b/app/(admin)/admin/rounds/page.tsx new file mode 100644 index 0000000..0a13c70 --- /dev/null +++ b/app/(admin)/admin/rounds/page.tsx @@ -0,0 +1,49 @@ +import { db } from "@/lib/db"; +import Link from "next/link"; +import RoundForm from "./RoundForm"; +import ActivateRoundButton from "./ActivateRoundButton"; + +export default async function AdminRoundsPage() { + const rounds = await db.round.findMany({ + orderBy: { number: "asc" }, + include: { _count: { select: { matches: true } } }, + }); + + return ( +
+
+

دورهای بازی

+
+ {rounds.map((r) => ( +
+
+
+
+ دور {r.number} - {r.name} + {r.isActive && فعال} +
+
+ {r._count.matches} بازی · deadline: {new Date(r.deadline).toLocaleDateString("fa-IR")} +
+
+
+ + + بازی‌ها + +
+
+
+ ))} + {rounds.length === 0 &&

هنوز دوری ثبت نشده

} +
+
+ +
+

افزودن دور جدید

+ +
+
+ ); +} diff --git a/app/(admin)/admin/scoring/ScoringRulesEditor.tsx b/app/(admin)/admin/scoring/ScoringRulesEditor.tsx new file mode 100644 index 0000000..776cd96 --- /dev/null +++ b/app/(admin)/admin/scoring/ScoringRulesEditor.tsx @@ -0,0 +1,101 @@ +"use client"; + +import { useState } from "react"; + +const POSITIONS = ["GK", "DEF", "MID", "FWD"] as const; +const POS_LABELS: Record = { GK: "دروازه‌بان", DEF: "مدافع", MID: "هافبک", FWD: "مهاجم" }; + +const EVENT_LABELS: Record = { + GOAL: "گل", ASSIST: "پاس گل", YELLOW_CARD: "کارت زرد", RED_CARD: "کارت قرمز", + SECOND_YELLOW: "کارت زرد دوم", CLEAN_SHEET: "کلین‌شیت", PENALTY_SAVED: "پنالتی گرفته", + PENALTY_MISSED: "پنالتی از دست داده", OWN_GOAL: "گل به خودی", MOTM: "بازیکن برتر", + EXTRA_TIME_BONUS: "وقت اضافه", INJURY_NO_SUB: "مصدومیت بدون تعویض", +}; + +type Rule = { id: string; position: string; eventType: string; points: number }; + +export default function ScoringRulesEditor({ rules, defaultRules }: { rules: Rule[]; defaultRules: any }) { + const [values, setValues] = useState>(() => { + const map: Record = {}; + for (const r of rules) map[`${r.position}_${r.eventType}`] = r.points; + return map; + }); + const [saving, setSaving] = useState(false); + const [saved, setSaved] = useState(false); + + function getValue(pos: string, evt: string): number { + const key = `${pos}_${evt}`; + if (key in values) return values[key]; + return defaultRules[pos]?.[evt] ?? 0; + } + + function setValue(pos: string, evt: string, val: number) { + setValues((v) => ({ ...v, [`${pos}_${evt}`]: val })); + } + + async function handleSave() { + setSaving(true); + const payload = Object.entries(values).map(([key, points]) => { + const [position, eventType] = key.split("_"); + return { position, eventType, points }; + }); + await fetch("/api/admin/scoring", { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(payload), + }); + setSaved(true); + setSaving(false); + setTimeout(() => setSaved(false), 3000); + } + + const events = Object.keys(EVENT_LABELS); + + return ( +
+
+ + + + + {POSITIONS.map((pos) => ( + + ))} + + + + {events.map((evt) => ( + + + {POSITIONS.map((pos) => { + const val = getValue(pos, evt); + return ( + + ); + })} + + ))} + +
رویداد{POS_LABELS[pos]}
{EVENT_LABELS[evt]} + setValue(pos, evt, parseInt(e.target.value) || 0)} + className={`w-16 border rounded-lg px-2 py-1 text-center focus:outline-none focus:ring-2 focus:ring-green-400 font-bold ${ + val > 0 ? "text-green-700 border-green-200" : + val < 0 ? "text-red-600 border-red-200" : + "text-gray-400" + }`} + /> +
+
+
+ + {saved && ✓ ذخیره شد} +
+
+ ); +} diff --git a/app/(admin)/admin/scoring/page.tsx b/app/(admin)/admin/scoring/page.tsx new file mode 100644 index 0000000..4ffca25 --- /dev/null +++ b/app/(admin)/admin/scoring/page.tsx @@ -0,0 +1,15 @@ +import { db } from "@/lib/db"; +import ScoringRulesEditor from "./ScoringRulesEditor"; +import { DEFAULT_RULES } from "@/lib/points"; + +export default async function AdminScoringPage() { + const rules = await db.scoringRule.findMany({ orderBy: [{ position: "asc" }, { eventType: "asc" }] }); + + return ( +
+

مدیریت امتیازدهی

+

امتیاز هر رویداد را به ازای هر پست جداگانه تنظیم کنید

+ +
+ ); +} diff --git a/app/(admin)/admin/stats/StatsForm.tsx b/app/(admin)/admin/stats/StatsForm.tsx new file mode 100644 index 0000000..6f779ec --- /dev/null +++ b/app/(admin)/admin/stats/StatsForm.tsx @@ -0,0 +1,138 @@ +"use client"; + +import { useState } from "react"; + +type Player = { id: string; name: string; position: string }; +type StatRow = { + playerId: string; + goals: number; + assists: number; + yellowCards: number; + redCards: number; + minutesPlayed: number; + cleanSheet: boolean; +}; + +export default function StatsForm({ match }: { match: any }) { + const allPlayers: Player[] = [ + ...match.homeTeam.players, + ...match.awayTeam.players, + ]; + + const initStats = (): Record => { + const map: Record = {}; + for (const p of allPlayers) { + const existing = match.playerStats.find((s: any) => s.playerId === p.id); + map[p.id] = existing ?? { + playerId: p.id, + goals: 0, + assists: 0, + yellowCards: 0, + redCards: 0, + minutesPlayed: 0, + cleanSheet: false, + }; + } + return map; + }; + + const [stats, setStats] = useState>(initStats); + const [loading, setLoading] = useState(false); + const [saved, setSaved] = useState(false); + + function update(playerId: string, field: keyof StatRow, value: any) { + setStats((prev) => ({ ...prev, [playerId]: { ...prev[playerId], [field]: value } })); + } + + async function handleSave() { + setLoading(true); + const payload = Object.values(stats).filter((s) => s.minutesPlayed > 0); + await fetch(`/api/matches/${match.id}/stats`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(payload), + }); + setSaved(true); + setLoading(false); + setTimeout(() => setSaved(false), 3000); + } + + const renderTeam = (players: Player[], teamName: string) => ( +
+

{teamName}

+
+ + + + + + + + + + + + + + {players.map((p) => { + const s = stats[p.id]; + if (!s) return null; + return ( + + + {(["minutesPlayed", "goals", "assists", "yellowCards", "redCards"] as const).map((field) => ( + + ))} + + + ); + })} + +
بازیکندقیقهگلپاس گلزردقرمزکلین‌شیت
+
{p.name}
+
{p.position}
+
+ update(p.id, field, parseInt(e.target.value) || 0)} + className="w-14 border rounded-lg px-2 py-1 text-center focus:outline-none focus:ring-2 focus:ring-green-400" + /> + + update(p.id, "cleanSheet", e.target.checked)} + className="w-4 h-4 accent-green-600" + /> +
+
+
+ ); + + return ( +
+
+ {match.homeTeam.name} + vs + {match.awayTeam.name} +
+ + {renderTeam(match.homeTeam.players, match.homeTeam.name)} + {renderTeam(match.awayTeam.players, match.awayTeam.name)} + +
+ + {saved && ✓ ذخیره شد} +
+
+ ); +} diff --git a/app/(admin)/admin/stats/page.tsx b/app/(admin)/admin/stats/page.tsx new file mode 100644 index 0000000..62bd992 --- /dev/null +++ b/app/(admin)/admin/stats/page.tsx @@ -0,0 +1,55 @@ +import { db } from "@/lib/db"; +import StatsForm from "./StatsForm"; + +export default async function AdminStatsPage({ + searchParams, +}: { + searchParams: { matchId?: string }; +}) { + const matches = await db.match.findMany({ + include: { homeTeam: true, awayTeam: true }, + orderBy: { matchDate: "desc" }, + }); + + const selectedMatch = searchParams.matchId + ? await db.match.findUnique({ + where: { id: searchParams.matchId }, + include: { + homeTeam: { include: { players: true } }, + awayTeam: { include: { players: true } }, + playerStats: { include: { player: true } }, + }, + }) + : null; + + return ( +
+

ثبت آمار بازیکنان

+ +
+ +
+ +
+
+ + {selectedMatch && } +
+ ); +} diff --git a/app/(admin)/admin/teams/TeamApprovalRow.tsx b/app/(admin)/admin/teams/TeamApprovalRow.tsx new file mode 100644 index 0000000..d295bef --- /dev/null +++ b/app/(admin)/admin/teams/TeamApprovalRow.tsx @@ -0,0 +1,82 @@ +"use client"; + +import { useState } from "react"; +import { useRouter } from "next/navigation"; +import PositionBadge from "@/components/PositionBadge"; + +export default function TeamApprovalRow({ team }: { team: any }) { + const router = useRouter(); + const [loading, setLoading] = useState(false); + const [open, setOpen] = useState(false); + + async function updateStatus(status: "APPROVED" | "REJECTED") { + setLoading(true); + await fetch(`/api/admin/teams/${team.id}`, { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ status }), + }); + router.refresh(); + setLoading(false); + } + + const starters = team.players.filter((tp: any) => !tp.isBench); + const bench = team.players.filter((tp: any) => tp.isBench); + + return ( +
+
+
+
{team.name}
+
{team.user.name ?? team.user.email} · ترکیب {team.formation} · {team.players.length} بازیکن
+
+
+ + + +
+
+ + {open && ( +
+
+
+

ترکیب اصلی ({starters.length})

+
+ {starters.map((tp: any) => ( +
+ {tp.player.name} +
+ {tp.player.country.flagUrl} + + {tp.isCaptain && ©} +
+
+ ))} +
+
+
+

ذخیره‌ها ({bench.length})

+
+ {bench.map((tp: any) => ( +
+ {tp.player.name} + +
+ ))} +
+
+
+
+ )} +
+ ); +} diff --git a/app/(admin)/admin/teams/page.tsx b/app/(admin)/admin/teams/page.tsx new file mode 100644 index 0000000..11287ab --- /dev/null +++ b/app/(admin)/admin/teams/page.tsx @@ -0,0 +1,71 @@ +import { db } from "@/lib/db"; +import TeamApprovalRow from "./TeamApprovalRow"; + +export default async function AdminTeamsPage() { + const teams = await db.team.findMany({ + include: { + user: { select: { name: true, email: true } }, + players: { include: { player: { include: { country: true } } } }, + }, + orderBy: { createdAt: "desc" }, + }); + + const pending = teams.filter((t) => t.status === "PENDING"); + const others = teams.filter((t) => t.status !== "PENDING"); + + return ( +
+

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

+ + {pending.length > 0 && ( +
+

+ + در انتظار تایید ({pending.length}) +

+
+ {pending.map((t) => )} +
+
+ )} + +
+

سایر تیم‌ها

+
+ + + + + + + + + + + + + {others.map((t) => ( + + + + + + + + + ))} + +
تیممدیرترکیببازیکنانامتیازوضعیت
{t.name}{t.user.name ?? t.user.email}{t.formation}{t.players.length}{t.totalPoints} + + {t.status === "APPROVED" ? "تایید" : t.status === "REJECTED" ? "رد" : "پیش‌نویس"} + +
+
+
+
+ ); +} diff --git a/app/(admin)/admin/users/page.tsx b/app/(admin)/admin/users/page.tsx new file mode 100644 index 0000000..fa7e557 --- /dev/null +++ b/app/(admin)/admin/users/page.tsx @@ -0,0 +1,44 @@ +import { db } from "@/lib/db"; + +export default async function AdminUsersPage() { + const users = await db.user.findMany({ + include: { team: true }, + orderBy: { createdAt: "desc" }, + }); + + return ( +
+

کاربران

+
+ + + + + + + + + + + + {users.map((u) => ( + + + + + + + + ))} + +
نامایمیلنقشتیمتاریخ ثبت
{u.name ?? "-"}{u.email} + + {u.role === "ADMIN" ? "ادمین" : "کاربر"} + + {u.team?.name ?? "-"}{new Date(u.createdAt).toLocaleDateString("fa-IR")}
+
+
+ ); +} diff --git a/app/(user)/profile/ProfileForm.tsx b/app/(user)/profile/ProfileForm.tsx new file mode 100644 index 0000000..a14b11b --- /dev/null +++ b/app/(user)/profile/ProfileForm.tsx @@ -0,0 +1,54 @@ +"use client"; + +import { useState } from "react"; + +export default function ProfileForm({ user }: { user: { id: string; name: string; email: string } }) { + const [name, setName] = useState(user.name); + const [saved, setSaved] = useState(false); + const [loading, setLoading] = useState(false); + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + setLoading(true); + await fetch("/api/user/profile", { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ name }), + }); + setSaved(true); + setLoading(false); + setTimeout(() => setSaved(false), 3000); + } + + return ( +
+

اطلاعات حساب

+
+ + setName(e.target.value)} + className="w-full border rounded-xl px-4 py-2.5 focus:outline-none focus:ring-2 focus:ring-green-500" + /> +
+
+ + +
+ + {saved &&

✓ ذخیره شد

} +
+ ); +} diff --git a/app/(user)/profile/page.tsx b/app/(user)/profile/page.tsx new file mode 100644 index 0000000..2396c4b --- /dev/null +++ b/app/(user)/profile/page.tsx @@ -0,0 +1,56 @@ +import { requireAuth } from "@/lib/session"; +import { db } from "@/lib/db"; +import ProfileForm from "./ProfileForm"; + +export default async function ProfilePage() { + const session = await requireAuth(); + const userId = (session.user as any).id; + + const user = await db.user.findUnique({ + where: { id: userId }, + include: { + team: { + include: { + players: { include: { player: { include: { country: true } } } }, + }, + }, + }, + }); + + if (!user) return null; + + return ( +
+

پروفایل

+
+ + +
+

آمار من

+
+
+ نام تیم + {user.team?.name ?? "-"} +
+
+ امتیاز کل + {user.team?.totalPoints ?? 0} +
+
+ بودجه باقی‌مانده + + {user.team + ? (user.team.budget - user.team.players.reduce((s, tp) => s + tp.player.price, 0)).toFixed(1) + : 100}M + +
+
+ تعداد بازیکنان + {user.team?.players.length ?? 0} / 15 +
+
+
+
+
+ ); +} diff --git a/app/(user)/team/TeamBuilder.tsx b/app/(user)/team/TeamBuilder.tsx new file mode 100644 index 0000000..a731d04 --- /dev/null +++ b/app/(user)/team/TeamBuilder.tsx @@ -0,0 +1,467 @@ +"use client"; + +import { useState, useRef } from "react"; +import PositionBadge from "@/components/PositionBadge"; + +type Player = { + id: string; + name: string; + position: string; + price: number; + totalPoints: number; + country: { name: string; code: string; flagUrl?: string | null }; +}; + +type TeamPlayer = { + playerId: string; + isCaptain: boolean; + isViceCaptain: boolean; + isBench: boolean; + positionIndex: number; + player: Player; +}; + +type Team = { + id: string; + name: string; + budget: number; + totalPoints: number; + formation: string; + status: string; + players: TeamPlayer[]; +} | null; + +const FORMATIONS: Record = { + "4-3-3": { label: "۴-۳-۳", def: 4, mid: 3, fwd: 3 }, + "4-4-2": { label: "۴-۴-۲", def: 4, mid: 4, fwd: 2 }, + "4-5-1": { label: "۴-۵-۱", def: 4, mid: 5, fwd: 1 }, + "3-5-2": { label: "۳-۵-۲", def: 3, mid: 5, fwd: 2 }, + "3-4-3": { label: "۳-۴-۳", def: 3, mid: 4, fwd: 3 }, + "5-3-2": { label: "۵-۳-۲", def: 5, mid: 3, fwd: 2 }, + "5-4-1": { label: "۵-۴-۱", def: 5, mid: 4, fwd: 1 }, +}; + +const POS_COLORS: Record = { + GK: "bg-yellow-400 text-yellow-900 border-yellow-500", + DEF: "bg-blue-500 text-white border-blue-600", + MID: "bg-green-500 text-white border-green-600", + FWD: "bg-red-500 text-white border-red-600", +}; + +export default function TeamBuilder({ + team: initialTeam, + allPlayers, +}: { + team: Team; + allPlayers: Player[]; +}) { + const [team, setTeam] = useState(initialTeam); + const [teamName, setTeamName] = useState(""); + const [formation, setFormation] = useState(initialTeam?.formation ?? "4-3-3"); + const [filter, setFilter] = useState(""); + const [posFilter, setPosFilter] = useState(""); + const [loading, setLoading] = useState(false); + const [msg, setMsg] = useState<{ text: string; type: "error" | "success" } | null>(null); + const [draggedId, setDraggedId] = useState(null); + const [submitLoading, setSubmitLoading] = useState(false); + + const spent = team?.players.filter((tp) => !tp.isBench).reduce((s, tp) => s + tp.player.price, 0) ?? 0; + const benchSpent = team?.players.filter((tp) => tp.isBench).reduce((s, tp) => s + tp.player.price, 0) ?? 0; + const remaining = (team?.budget ?? 100) - spent - benchSpent; + + const fmt = FORMATIONS[formation] ?? FORMATIONS["4-3-3"]; + const starters = team?.players.filter((tp) => !tp.isBench) ?? []; + const bench = team?.players.filter((tp) => tp.isBench) ?? []; + + const gkSlots = starters.filter((tp) => tp.player.position === "GK"); + const defSlots = starters.filter((tp) => tp.player.position === "DEF"); + const midSlots = starters.filter((tp) => tp.player.position === "MID"); + const fwdSlots = starters.filter((tp) => tp.player.position === "FWD"); + + async function createTeam() { + if (!teamName.trim()) return; + setLoading(true); + const res = await fetch("/api/team", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ name: teamName, formation }), + }); + const data = await res.json(); + if (res.ok) setTeam({ ...data, players: [] }); + else setMsg({ text: data.error, type: "error" }); + setLoading(false); + } + + async function addPlayer(playerId: string, isBench = false) { + setLoading(true); + const res = await fetch("/api/team/players", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ playerId, isBench }), + }); + const data = await res.json(); + if (res.ok) { + const player = allPlayers.find((p) => p.id === playerId)!; + setTeam((t) => t ? { ...t, players: [...t.players, { ...data, player }] } : t); + setMsg(null); + } else { + setMsg({ text: data.error, type: "error" }); + } + setLoading(false); + } + + async function removePlayer(playerId: string) { + setLoading(true); + await fetch("/api/team/players", { + method: "DELETE", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ playerId }), + }); + setTeam((t) => t ? { ...t, players: t.players.filter((tp) => tp.playerId !== playerId) } : t); + setLoading(false); + } + + async function setCaptain(playerId: string, type: "captain" | "vice") { + await fetch("/api/team/captain", { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ playerId, type }), + }); + setTeam((t) => { + if (!t) return t; + return { + ...t, + players: t.players.map((tp) => ({ + ...tp, + isCaptain: type === "captain" ? tp.playerId === playerId : tp.isCaptain, + isViceCaptain: type === "vice" ? tp.playerId === playerId : tp.isViceCaptain, + })), + }; + }); + } + + async function submitTeam() { + setSubmitLoading(true); + const res = await fetch("/api/team/submit", { method: "POST" }); + const data = await res.json(); + if (res.ok) { + setTeam((t) => t ? { ...t, status: "PENDING" } : t); + setMsg({ text: "تیم برای تایید ارسال شد", type: "success" }); + } else { + setMsg({ text: data.error, type: "error" }); + } + setSubmitLoading(false); + } + + // drag & drop swap + function onDragStart(playerId: string) { setDraggedId(playerId); } + function onDrop(targetId: string) { + if (!draggedId || draggedId === targetId) return; + setTeam((t) => { + if (!t) return t; + const a = t.players.find((p) => p.playerId === draggedId); + const b = t.players.find((p) => p.playerId === targetId); + if (!a || !b) return t; + // swap bench status + return { + ...t, + players: t.players.map((p) => { + if (p.playerId === draggedId) return { ...p, isBench: b.isBench }; + if (p.playerId === targetId) return { ...p, isBench: a.isBench }; + return p; + }), + }; + }); + setDraggedId(null); + } + + const myPlayerIds = new Set(team?.players.map((tp) => tp.playerId) ?? []); + const filtered = allPlayers.filter( + (p) => + !myPlayerIds.has(p.id) && + (posFilter ? p.position === posFilter : true) && + (filter ? p.name.includes(filter) || p.country.name.includes(filter) : true) + ); + + const isComplete = starters.length === 11 && bench.length >= 4; + const canSubmit = isComplete && team?.status === "INACTIVE"; + + if (!team) { + return ( +
+
+

تیمت رو بساز

+

با بودجه ۱۰۰ میلیون، ۱۵ بازیکن انتخاب کن

+ setTeamName(e.target.value)} + className="w-full border-2 rounded-xl px-4 py-3 mb-4 focus:outline-none focus:border-green-500 text-center text-lg" /> +
+ +
+ {Object.entries(FORMATIONS).map(([key, val]) => ( + + ))} +
+
+ +
+ ); + } + + return ( +
+ {/* هدر */} +
+
+

{team.name}

+
+ + {team.status === "ACTIVE" ? "✓ فعال - در رقابت" : "در حال تکمیل"} + + ترکیب: {formation} +
+
+
+
+
{team.totalPoints}
+
امتیاز
+
+
+
+ {remaining.toFixed(1)}M +
+
بودجه
+
+
+
{starters.length}/11
+
بازیکن
+
+
+
+ + {msg && ( +
+ {msg.text} +
+ )} + +
+ {/* زمین - ۳ ستون */} +
+ {/* انتخاب ترکیب */} + {team.status === "DRAFT" && ( +
+ {Object.keys(FORMATIONS).map((f) => ( + + ))} +
+ )} + + {/* زمین فوتبال */} +
+ {/* خطوط */} + + + + + + + + + +
+ {/* مهاجمان */} + + {/* هافبک‌ها */} + + {/* مدافعان */} + + {/* دروازه‌بان */} + +
+
+ + {/* ذخیره‌ها */} +
+

ذخیره‌ها (حداکثر ۴ نفر)

+
+ {bench.map((tp) => ( + + ))} + {Array.from({ length: Math.max(0, 4 - bench.length) }).map((_, i) => ( + + ))} +
+
+ + {/* دکمه ارسال */} + {canSubmit && ( + + )} + {!isComplete && team.status === "DRAFT" && ( +

+ برای ورود به رقابت باید ۱۱ بازیکن اصلی + ۴ ذخیره (هر پست ۱ ذخیره) داشته باشی +

+ )} +
+ + {/* لیست بازیکنان - ۲ ستون */} +
+

انتخاب بازیکن

+
+ {["", "GK", "DEF", "MID", "FWD"].map((pos) => ( + + ))} +
+ setFilter(e.target.value)} + className="w-full border rounded-xl px-4 py-2 text-sm mb-3 focus:outline-none focus:ring-2 focus:ring-green-400" /> + +
+ + + + + + + + + + + {filtered.map((p) => ( + + + + + + + ))} + {filtered.length === 0 && ( + + )} + +
بازیکنقیمتpts
+
{p.name}
+
+ {p.country.flagUrl} {p.country.name} + +
+
{p.price}M{p.totalPoints} + +
بازیکنی پیدا نشد
+
+
+
+
+ ); +} + +function PitchRow({ players, slots, position, onRemove, onDragStart, onDrop, onCaptain, draggedId, }: { + players: TeamPlayer[]; slots: number; position: string; + onRemove: (id: string) => void; onDragStart: (id: string) => void; + onDrop: (id: string) => void; onCaptain: (id: string, t: "captain" | "vice") => void; + draggedId: string | null; +}) { + return ( +
+ {Array.from({ length: slots }).map((_, i) => { + const tp = players[i]; + return tp ? ( + + ) : ( + + ); + })} +
+ ); +} + +function PitchCard({ tp, onRemove, onDragStart, onDrop, onCaptain, draggedId, small }: { + tp: TeamPlayer; onRemove: (id: string) => void; onDragStart: (id: string) => void; + onDrop: (id: string) => void; onCaptain: (id: string, t: "captain" | "vice") => void; + draggedId: string | null; small?: boolean; +}) { + const [showMenu, setShowMenu] = useState(false); + const isDragging = draggedId === tp.playerId; + const isEliminated = (tp.player as any).country?.isEliminated; + const color = isEliminated + ? "bg-gray-500 text-gray-300 border-gray-600" + : POS_COLORS[tp.player.position] ?? "bg-gray-400 text-white border-gray-500"; + const shortName = tp.player.name.split(" ").slice(-1)[0]; + return ( +
onDragStart(tp.playerId)} + onDragOver={(e) => e.preventDefault()} + onDrop={() => onDrop(tp.playerId)} + onClick={() => setShowMenu((v) => !v)}> +
+ {tp.player.position === "GK" ? "🧤" : tp.player.position === "DEF" ? "🛡️" : tp.player.position === "MID" ? "⚙️" : "⚡"} + {isEliminated &&
} +
+
+ {shortName} +
+
+ {tp.isCaptain && ©} + {tp.isViceCaptain && VC} + {tp.player.totalPoints}pts +
+ {isEliminated && ( +
+ تیم ملی حذف شده +
+ )} + {showMenu && ( +
e.stopPropagation()}> + {isEliminated &&
⚠️ تیم ملی حذف شده
} + + + +
+ )} +
+ ); +} + +function EmptySlot({ label }: { label: string }) { + return ( +
+ {label} +
+ ); +} diff --git a/app/(user)/team/page.tsx b/app/(user)/team/page.tsx new file mode 100644 index 0000000..7efd8d7 --- /dev/null +++ b/app/(user)/team/page.tsx @@ -0,0 +1,24 @@ +import { requireAuth } from "@/lib/session"; +import { db } from "@/lib/db"; +import TeamBuilder from "./TeamBuilder"; + +export default async function TeamPage() { + const session = await requireAuth(); + const userId = (session.user as any).id; + + const team = await db.team.findUnique({ + where: { userId }, + include: { + players: { + include: { player: { include: { country: true } } }, + }, + }, + }); + + const allPlayers = await db.player.findMany({ + include: { country: true }, + orderBy: { totalPoints: "desc" }, + }); + + return ; +} diff --git a/app/api/admin/matches/[id]/calc-points/route.ts b/app/api/admin/matches/[id]/calc-points/route.ts new file mode 100644 index 0000000..3f1b9a5 --- /dev/null +++ b/app/api/admin/matches/[id]/calc-points/route.ts @@ -0,0 +1,78 @@ +import { NextRequest, NextResponse } from "next/server"; +import { db } from "@/lib/db"; +import { getServerSession } from "next-auth"; +import { authOptions } from "@/lib/auth"; +import { calculateMatchPoints } from "@/lib/points"; + +export async function POST(_: NextRequest, { params }: { params: { id: string } }) { + const session = await getServerSession(authOptions); + if (!session || (session.user as any).role !== "ADMIN") + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + + const events = await db.matchEvent.findMany({ + where: { matchId: params.id }, + include: { player: true }, + }); + + // گروه‌بندی رویدادها بر اساس بازیکن + const playerEvents: Record = {}; + for (const ev of events) { + if (!playerEvents[ev.playerId]) playerEvents[ev.playerId] = { player: ev.player, events: [] }; + playerEvents[ev.playerId].events.push(ev); + } + + const results = []; + + for (const [playerId, { player, events: evs }] of Object.entries(playerEvents)) { + const goals = evs.filter((e) => e.type === "GOAL").length; + const assists = evs.filter((e) => e.type === "ASSIST").length; + const yellowCards = evs.filter((e) => e.type === "YELLOW_CARD" || e.type === "SECOND_YELLOW").length; + const redCards = evs.filter((e) => e.type === "RED_CARD" || e.type === "SECOND_YELLOW").length; + const cleanSheet = evs.some((e) => e.type === "CLEAN_SHEET"); + const penaltySaved = evs.filter((e) => e.type === "PENALTY_SAVED").length; + const penaltyMissed = evs.filter((e) => e.type === "PENALTY_MISSED").length; + const ownGoals = evs.filter((e) => e.type === "OWN_GOAL").length; + const isMotm = evs.some((e) => e.type === "MOTM"); + const extraTimeBonus = evs.filter((e) => e.type === "EXTRA_TIME_BONUS").length; + + // دقیقه بازی: اگه تعویض شده کمتر از ۹۰، وگرنه ۹۰ + const subOut = evs.find((e) => e.type === "SUBSTITUTION_OUT"); + const subIn = evs.find((e) => e.type === "SUBSTITUTION_IN"); + const injury = evs.find((e) => e.type === "INJURY_NO_SUB"); + let minutesPlayed = 90; + if (subOut?.minute) minutesPlayed = subOut.minute; + else if (injury?.minute) minutesPlayed = injury.minute; + if (subIn?.minute) minutesPlayed = 90 - subIn.minute; + + const points = await calculateMatchPoints({ + position: player.position, + goals, assists, yellowCards, redCards, minutesPlayed, + cleanSheet, penaltySaved, penaltyMissed, ownGoals, isMotm, extraTimeBonus, + }); + + const stat = await db.playerMatchStat.upsert({ + where: { playerId_matchId: { playerId, matchId: params.id } }, + update: { goals, assists, yellowCards, redCards, minutesPlayed, cleanSheet, penaltySaved, penaltyMissed, ownGoals, isMotm, extraTimeBonus, points }, + create: { playerId, matchId: params.id, goals, assists, yellowCards, redCards, minutesPlayed, cleanSheet, penaltySaved, penaltyMissed, ownGoals, isMotm, extraTimeBonus, points }, + }); + + // آپدیت totalPoints بازیکن + const agg = await db.playerMatchStat.aggregate({ where: { playerId }, _sum: { points: true } }); + await db.player.update({ where: { id: playerId }, data: { totalPoints: agg._sum.points ?? 0 } }); + + results.push(stat); + } + + // آپدیت امتیاز تیم‌های فانتزی + const teams = await db.team.findMany({ include: { players: { include: { player: true } } } }); + for (const team of teams) { + let total = 0; + for (const tp of team.players) { + const mult = tp.isCaptain ? 2 : tp.isViceCaptain ? 1.5 : 1; + total += tp.player.totalPoints * mult; + } + await db.team.update({ where: { id: team.id }, data: { totalPoints: Math.round(total) } }); + } + + return NextResponse.json({ calculated: results.length }); +} diff --git a/app/api/admin/matches/[id]/events/[eventId]/route.ts b/app/api/admin/matches/[id]/events/[eventId]/route.ts new file mode 100644 index 0000000..ea276f2 --- /dev/null +++ b/app/api/admin/matches/[id]/events/[eventId]/route.ts @@ -0,0 +1,13 @@ +import { NextRequest, NextResponse } from "next/server"; +import { db } from "@/lib/db"; +import { getServerSession } from "next-auth"; +import { authOptions } from "@/lib/auth"; + +export async function DELETE(_: NextRequest, { params }: { params: { id: string; eventId: string } }) { + const session = await getServerSession(authOptions); + if (!session || (session.user as any).role !== "ADMIN") + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + + await db.matchEvent.delete({ where: { id: params.eventId } }); + return NextResponse.json({ success: true }); +} diff --git a/app/api/admin/matches/[id]/events/route.ts b/app/api/admin/matches/[id]/events/route.ts new file mode 100644 index 0000000..3dc469a --- /dev/null +++ b/app/api/admin/matches/[id]/events/route.ts @@ -0,0 +1,18 @@ +import { NextRequest, NextResponse } from "next/server"; +import { db } from "@/lib/db"; +import { getServerSession } from "next-auth"; +import { authOptions } from "@/lib/auth"; + +export async function POST(req: NextRequest, { params }: { params: { id: string } }) { + const session = await getServerSession(authOptions); + if (!session || (session.user as any).role !== "ADMIN") + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + + const { playerId, type, minute, extraInfo } = await req.json(); + + const event = await db.matchEvent.create({ + data: { matchId: params.id, playerId, type, minute: minute ?? null, extraInfo: extraInfo || null }, + }); + + return NextResponse.json(event, { status: 201 }); +} diff --git a/app/api/admin/matches/[id]/lineup/route.ts b/app/api/admin/matches/[id]/lineup/route.ts new file mode 100644 index 0000000..fead793 --- /dev/null +++ b/app/api/admin/matches/[id]/lineup/route.ts @@ -0,0 +1,23 @@ +import { NextRequest, NextResponse } from "next/server"; +import { db } from "@/lib/db"; +import { getServerSession } from "next-auth"; +import { authOptions } from "@/lib/auth"; + +export async function POST(req: NextRequest, { params }: { params: { id: string } }) { + const session = await getServerSession(authOptions); + if (!session || (session.user as any).role !== "ADMIN") + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + + const lineups: Array<{ countryId: string; formation: string; playerIds: string[] }> = await req.json(); + + // حذف ترکیب‌های قبلی + await db.matchLineup.deleteMany({ where: { matchId: params.id } }); + + for (const l of lineups) { + await db.matchLineup.create({ + data: { matchId: params.id, countryId: l.countryId, formation: l.formation, playerIds: l.playerIds }, + }); + } + + return NextResponse.json({ success: true }); +} diff --git a/app/api/admin/scoring/route.ts b/app/api/admin/scoring/route.ts new file mode 100644 index 0000000..08ed414 --- /dev/null +++ b/app/api/admin/scoring/route.ts @@ -0,0 +1,22 @@ +import { NextRequest, NextResponse } from "next/server"; +import { db } from "@/lib/db"; +import { getServerSession } from "next-auth"; +import { authOptions } from "@/lib/auth"; + +export async function PUT(req: NextRequest) { + const session = await getServerSession(authOptions); + if (!session || (session.user as any).role !== "ADMIN") + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + + const rules: Array<{ position: string; eventType: string; points: number }> = await req.json(); + + for (const rule of rules) { + await db.scoringRule.upsert({ + where: { position_eventType: { position: rule.position as any, eventType: rule.eventType as any } }, + update: { points: rule.points, updatedBy: (session.user as any).id }, + create: { position: rule.position as any, eventType: rule.eventType as any, points: rule.points, updatedBy: (session.user as any).id }, + }); + } + + return NextResponse.json({ success: true }); +} diff --git a/app/api/admin/teams/[id]/route.ts b/app/api/admin/teams/[id]/route.ts new file mode 100644 index 0000000..bbcc344 --- /dev/null +++ b/app/api/admin/teams/[id]/route.ts @@ -0,0 +1,17 @@ +import { NextRequest, NextResponse } from "next/server"; +import { db } from "@/lib/db"; +import { getServerSession } from "next-auth"; +import { authOptions } from "@/lib/auth"; + +export async function PUT(req: NextRequest, { params }: { params: { id: string } }) { + const session = await getServerSession(authOptions); + if (!session || (session.user as any).role !== "ADMIN") + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + + const { status } = await req.json(); + const team = await db.team.update({ + where: { id: params.id }, + data: { status }, + }); + return NextResponse.json(team); +} diff --git a/app/api/admin/teams/route.ts b/app/api/admin/teams/route.ts new file mode 100644 index 0000000..3d488de --- /dev/null +++ b/app/api/admin/teams/route.ts @@ -0,0 +1,20 @@ +import { NextResponse } from "next/server"; +import { db } from "@/lib/db"; +import { getServerSession } from "next-auth"; +import { authOptions } from "@/lib/auth"; + +export async function GET() { + const session = await getServerSession(authOptions); + if (!session || (session.user as any).role !== "ADMIN") + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + + const teams = await db.team.findMany({ + include: { + user: { select: { name: true, email: true } }, + _count: { select: { players: true } }, + }, + orderBy: { createdAt: "desc" }, + }); + + return NextResponse.json(teams); +} diff --git a/app/api/auth/[...nextauth]/route.ts b/app/api/auth/[...nextauth]/route.ts new file mode 100644 index 0000000..9cd7923 --- /dev/null +++ b/app/api/auth/[...nextauth]/route.ts @@ -0,0 +1,5 @@ +import NextAuth from "next-auth"; +import { authOptions } from "@/lib/auth"; + +const handler = NextAuth(authOptions); +export { handler as GET, handler as POST }; diff --git a/app/api/auth/register/route.ts b/app/api/auth/register/route.ts new file mode 100644 index 0000000..e582691 --- /dev/null +++ b/app/api/auth/register/route.ts @@ -0,0 +1,23 @@ +import { NextRequest, NextResponse } from "next/server"; +import { db } from "@/lib/db"; +import bcrypt from "bcryptjs"; + +export async function POST(req: NextRequest) { + const { name, email, password } = await req.json(); + + if (!email || !password) { + return NextResponse.json({ error: "ایمیل و رمز عبور الزامی است" }, { status: 400 }); + } + + const existing = await db.user.findUnique({ where: { email } }); + if (existing) { + return NextResponse.json({ error: "این ایمیل قبلاً ثبت شده" }, { status: 400 }); + } + + const hashed = await bcrypt.hash(password, 10); + const user = await db.user.create({ + data: { name, email, password: hashed }, + }); + + return NextResponse.json({ id: user.id }, { status: 201 }); +} diff --git a/app/api/countries/[id]/route.ts b/app/api/countries/[id]/route.ts new file mode 100644 index 0000000..d153ab9 --- /dev/null +++ b/app/api/countries/[id]/route.ts @@ -0,0 +1,23 @@ +import { NextRequest, NextResponse } from "next/server"; +import { db } from "@/lib/db"; +import { getServerSession } from "next-auth"; +import { authOptions } from "@/lib/auth"; + +export async function PUT(req: NextRequest, { params }: { params: { id: string } }) { + const session = await getServerSession(authOptions); + if (!session || (session.user as any).role !== "ADMIN") + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + + const body = await req.json(); + const country = await db.country.update({ where: { id: params.id }, data: body }); + return NextResponse.json(country); +} + +export async function DELETE(_: NextRequest, { params }: { params: { id: string } }) { + const session = await getServerSession(authOptions); + if (!session || (session.user as any).role !== "ADMIN") + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + + await db.country.delete({ where: { id: params.id } }); + return NextResponse.json({ success: true }); +} diff --git a/app/api/countries/route.ts b/app/api/countries/route.ts new file mode 100644 index 0000000..d68e4d7 --- /dev/null +++ b/app/api/countries/route.ts @@ -0,0 +1,22 @@ +import { NextRequest, NextResponse } from "next/server"; +import { db } from "@/lib/db"; +import { getServerSession } from "next-auth"; +import { authOptions } from "@/lib/auth"; + +export async function GET() { + const countries = await db.country.findMany({ + include: { group: true }, + orderBy: { name: "asc" }, + }); + return NextResponse.json(countries); +} + +export async function POST(req: NextRequest) { + const session = await getServerSession(authOptions); + if (!session || (session.user as any).role !== "ADMIN") + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + + const body = await req.json(); + const country = await db.country.create({ data: body }); + return NextResponse.json(country, { status: 201 }); +} diff --git a/app/api/gameweeks/[id]/activate/route.ts b/app/api/gameweeks/[id]/activate/route.ts new file mode 100644 index 0000000..a07c501 --- /dev/null +++ b/app/api/gameweeks/[id]/activate/route.ts @@ -0,0 +1,16 @@ +import { NextRequest, NextResponse } from "next/server"; +import { db } from "@/lib/db"; +import { getServerSession } from "next-auth"; +import { authOptions } from "@/lib/auth"; + +export async function POST(_: NextRequest, { params }: { params: { id: string } }) { + const session = await getServerSession(authOptions); + if (!session || (session.user as any).role !== "ADMIN") + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + + // غیرفعال کردن همه + await db.gameweek.updateMany({ data: { isActive: false } }); + // فعال کردن این هفته + const gw = await db.gameweek.update({ where: { id: params.id }, data: { isActive: true } }); + return NextResponse.json(gw); +} diff --git a/app/api/gameweeks/route.ts b/app/api/gameweeks/route.ts new file mode 100644 index 0000000..e398e75 --- /dev/null +++ b/app/api/gameweeks/route.ts @@ -0,0 +1,19 @@ +import { NextRequest, NextResponse } from "next/server"; +import { db } from "@/lib/db"; +import { getServerSession } from "next-auth"; +import { authOptions } from "@/lib/auth"; + +export async function GET() { + const gameweeks = await db.gameweek.findMany({ orderBy: { number: "asc" } }); + return NextResponse.json(gameweeks); +} + +export async function POST(req: NextRequest) { + const session = await getServerSession(authOptions); + if (!session || (session.user as any).role !== "ADMIN") + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + + const body = await req.json(); + const gw = await db.gameweek.create({ data: body }); + return NextResponse.json(gw, { status: 201 }); +} diff --git a/app/api/leaderboard/route.ts b/app/api/leaderboard/route.ts new file mode 100644 index 0000000..20ec67e --- /dev/null +++ b/app/api/leaderboard/route.ts @@ -0,0 +1,20 @@ +import { NextResponse } from "next/server"; +import { db } from "@/lib/db"; + +export async function GET() { + const teams = await db.team.findMany({ + orderBy: { totalPoints: "desc" }, + include: { user: { select: { name: true, email: true } } }, + take: 50, + }); + + return NextResponse.json( + teams.map((t, i) => ({ + rank: i + 1, + teamName: t.name, + userName: t.user.name ?? t.user.email, + totalPoints: t.totalPoints, + budget: t.budget, + })) + ); +} diff --git a/app/api/matches/[id]/route.ts b/app/api/matches/[id]/route.ts new file mode 100644 index 0000000..fb13e2f --- /dev/null +++ b/app/api/matches/[id]/route.ts @@ -0,0 +1,32 @@ +import { NextRequest, NextResponse } from "next/server"; +import { db } from "@/lib/db"; +import { getServerSession } from "next-auth"; +import { authOptions } from "@/lib/auth"; + +export async function GET(_: NextRequest, { params }: { params: { id: string } }) { + const match = await db.match.findUnique({ + where: { id: params.id }, + include: { homeTeam: true, awayTeam: true, playerStats: { include: { player: true } } }, + }); + if (!match) return NextResponse.json({ error: "Not found" }, { status: 404 }); + return NextResponse.json(match); +} + +export async function PUT(req: NextRequest, { params }: { params: { id: string } }) { + const session = await getServerSession(authOptions); + if (!session || (session.user as any).role !== "ADMIN") + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + + const body = await req.json(); + const match = await db.match.update({ where: { id: params.id }, data: body }); + return NextResponse.json(match); +} + +export async function DELETE(_: NextRequest, { params }: { params: { id: string } }) { + const session = await getServerSession(authOptions); + if (!session || (session.user as any).role !== "ADMIN") + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + + await db.match.delete({ where: { id: params.id } }); + return NextResponse.json({ success: true }); +} diff --git a/app/api/matches/[id]/stats/route.ts b/app/api/matches/[id]/stats/route.ts new file mode 100644 index 0000000..9a1fd2b --- /dev/null +++ b/app/api/matches/[id]/stats/route.ts @@ -0,0 +1,65 @@ +import { NextRequest, NextResponse } from "next/server"; +import { db } from "@/lib/db"; +import { getServerSession } from "next-auth"; +import { authOptions } from "@/lib/auth"; +import { calculatePoints } from "@/lib/points"; + +export async function POST(req: NextRequest, { params }: { params: { id: string } }) { + const session = await getServerSession(authOptions); + if (!session || (session.user as any).role !== "ADMIN") + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + + const stats: Array<{ + playerId: string; + goals: number; + assists: number; + yellowCards: number; + redCards: number; + minutesPlayed: number; + cleanSheet: boolean; + }> = await req.json(); + + const results = []; + + for (const stat of stats) { + const player = await db.player.findUnique({ where: { id: stat.playerId } }); + if (!player) continue; + + const points = calculatePoints({ position: player.position, ...stat }); + + const record = await db.playerMatchStat.upsert({ + where: { playerId_matchId: { playerId: stat.playerId, matchId: params.id } }, + update: { ...stat, points }, + create: { ...stat, matchId: params.id, points }, + }); + + // آپدیت امتیاز کل بازیکن + const totalPoints = await db.playerMatchStat.aggregate({ + where: { playerId: stat.playerId }, + _sum: { points: true }, + }); + await db.player.update({ + where: { id: stat.playerId }, + data: { totalPoints: totalPoints._sum.points ?? 0 }, + }); + + results.push(record); + } + + // آپدیت امتیاز تیم‌های فانتزی + await recalcTeamPoints(); + + return NextResponse.json(results); +} + +async function recalcTeamPoints() { + const teams = await db.team.findMany({ include: { players: { include: { player: true } } } }); + for (const team of teams) { + let total = 0; + for (const tp of team.players) { + const multiplier = tp.isCaptain ? 2 : 1; + total += tp.player.totalPoints * multiplier; + } + await db.team.update({ where: { id: team.id }, data: { totalPoints: total } }); + } +} diff --git a/app/api/matches/route.ts b/app/api/matches/route.ts new file mode 100644 index 0000000..50cdd00 --- /dev/null +++ b/app/api/matches/route.ts @@ -0,0 +1,29 @@ +import { NextRequest, NextResponse } from "next/server"; +import { db } from "@/lib/db"; +import { getServerSession } from "next-auth"; +import { authOptions } from "@/lib/auth"; + +export async function GET() { + const matches = await db.match.findMany({ + include: { + homeTeam: true, + awayTeam: true, + gameweek: true, + }, + orderBy: { matchDate: "asc" }, + }); + return NextResponse.json(matches); +} + +export async function POST(req: NextRequest) { + const session = await getServerSession(authOptions); + if (!session || (session.user as any).role !== "ADMIN") + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + + const body = await req.json(); + const match = await db.match.create({ + data: body, + include: { homeTeam: true, awayTeam: true }, + }); + return NextResponse.json(match, { status: 201 }); +} diff --git a/app/api/payment/request/route.ts b/app/api/payment/request/route.ts new file mode 100644 index 0000000..a09b298 --- /dev/null +++ b/app/api/payment/request/route.ts @@ -0,0 +1,35 @@ +import { NextRequest, NextResponse } from "next/server"; +import { db } from "@/lib/db"; +import { getServerSession } from "next-auth"; +import { authOptions } from "@/lib/auth"; +import { requestPayment } from "@/lib/zarinpal"; + +export async function POST(req: NextRequest) { + const session = await getServerSession(authOptions); + if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + + const { packageId } = await req.json(); + const userId = (session.user as any).id; + + const pkg = await db.package.findUnique({ where: { id: packageId } }); + if (!pkg || !pkg.isActive) return NextResponse.json({ error: "پکیج پیدا نشد" }, { status: 404 }); + + const callbackUrl = `${process.env.NEXTAUTH_URL}/api/payment/verify`; + + const result = await requestPayment(pkg.price, `خرید ${pkg.name} - فانتزی جام جهانی`, callbackUrl); + + if (!result.success) return NextResponse.json({ error: result.error }, { status: 400 }); + + // ذخیره پرداخت در دیتابیس + await db.payment.create({ + data: { + userId, + packageId, + amount: pkg.price, + authority: result.authority, + status: "PENDING", + }, + }); + + return NextResponse.json({ paymentUrl: result.paymentUrl }); +} diff --git a/app/api/payment/verify/route.ts b/app/api/payment/verify/route.ts new file mode 100644 index 0000000..744e556 --- /dev/null +++ b/app/api/payment/verify/route.ts @@ -0,0 +1,43 @@ +import { NextRequest, NextResponse } from "next/server"; +import { db } from "@/lib/db"; +import { verifyPayment } from "@/lib/zarinpal"; + +export async function GET(req: NextRequest) { + const { searchParams } = new URL(req.url); + const authority = searchParams.get("Authority"); + const status = searchParams.get("Status"); + + if (!authority) return NextResponse.redirect(new URL("/shop?status=error", req.url)); + + const payment = await db.payment.findUnique({ + where: { authority }, + include: { package: true, user: true }, + }); + + if (!payment) return NextResponse.redirect(new URL("/shop?status=error", req.url)); + + if (status !== "OK") { + await db.payment.update({ where: { id: payment.id }, data: { status: "FAILED" } }); + return NextResponse.redirect(new URL("/shop?status=cancelled", req.url)); + } + + const result = await verifyPayment(authority, payment.amount); + + if (!result.success) { + await db.payment.update({ where: { id: payment.id }, data: { status: "FAILED" } }); + return NextResponse.redirect(new URL("/shop?status=failed", req.url)); + } + + // پرداخت موفق - آپدیت بودجه تیم + await db.payment.update({ + where: { id: payment.id }, + data: { status: "SUCCESS", refId: result.refId }, + }); + + await db.team.updateMany({ + where: { userId: payment.userId }, + data: { budget: { increment: payment.package.budgetBonus } }, + }); + + return NextResponse.redirect(new URL(`/shop?status=success&refId=${result.refId}`, req.url)); +} diff --git a/app/api/players/[id]/route.ts b/app/api/players/[id]/route.ts new file mode 100644 index 0000000..c1cfb8d --- /dev/null +++ b/app/api/players/[id]/route.ts @@ -0,0 +1,28 @@ +import { NextRequest, NextResponse } from "next/server"; +import { db } from "@/lib/db"; +import { getServerSession } from "next-auth"; +import { authOptions } from "@/lib/auth"; + +export async function PUT(req: NextRequest, { params }: { params: { id: string } }) { + const session = await getServerSession(authOptions); + if (!session || (session.user as any).role !== "ADMIN") { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + const body = await req.json(); + const player = await db.player.update({ + where: { id: params.id }, + data: body, + }); + return NextResponse.json(player); +} + +export async function DELETE(req: NextRequest, { params }: { params: { id: string } }) { + const session = await getServerSession(authOptions); + if (!session || (session.user as any).role !== "ADMIN") { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + await db.player.delete({ where: { id: params.id } }); + return NextResponse.json({ success: true }); +} diff --git a/app/api/players/route.ts b/app/api/players/route.ts new file mode 100644 index 0000000..5abd12b --- /dev/null +++ b/app/api/players/route.ts @@ -0,0 +1,32 @@ +import { NextRequest, NextResponse } from "next/server"; +import { db } from "@/lib/db"; +import { getServerSession } from "next-auth"; +import { authOptions } from "@/lib/auth"; + +export async function GET(req: NextRequest) { + const { searchParams } = new URL(req.url); + const position = searchParams.get("position"); + const countryId = searchParams.get("countryId"); + + const players = await db.player.findMany({ + where: { + ...(position ? { position: position as any } : {}), + ...(countryId ? { countryId } : {}), + }, + include: { country: true }, + orderBy: { totalPoints: "desc" }, + }); + + return NextResponse.json(players); +} + +export async function POST(req: NextRequest) { + const session = await getServerSession(authOptions); + if (!session || (session.user as any).role !== "ADMIN") { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + const body = await req.json(); + const player = await db.player.create({ data: body }); + return NextResponse.json(player, { status: 201 }); +} diff --git a/app/api/rounds/[id]/activate/route.ts b/app/api/rounds/[id]/activate/route.ts new file mode 100644 index 0000000..9f70ce0 --- /dev/null +++ b/app/api/rounds/[id]/activate/route.ts @@ -0,0 +1,14 @@ +import { NextRequest, NextResponse } from "next/server"; +import { db } from "@/lib/db"; +import { getServerSession } from "next-auth"; +import { authOptions } from "@/lib/auth"; + +export async function POST(_: NextRequest, { params }: { params: { id: string } }) { + const session = await getServerSession(authOptions); + if (!session || (session.user as any).role !== "ADMIN") + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + + await db.round.updateMany({ data: { isActive: false } }); + const round = await db.round.update({ where: { id: params.id }, data: { isActive: true } }); + return NextResponse.json(round); +} diff --git a/app/api/rounds/route.ts b/app/api/rounds/route.ts new file mode 100644 index 0000000..b8009ca --- /dev/null +++ b/app/api/rounds/route.ts @@ -0,0 +1,25 @@ +import { NextRequest, NextResponse } from "next/server"; +import { db } from "@/lib/db"; +import { getServerSession } from "next-auth"; +import { authOptions } from "@/lib/auth"; + +export async function GET() { + const rounds = await db.round.findMany({ orderBy: { number: "asc" } }); + return NextResponse.json(rounds); +} + +export async function POST(req: NextRequest) { + const session = await getServerSession(authOptions); + if (!session || (session.user as any).role !== "ADMIN") + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + + const { number, name, deadline } = await req.json(); + + const existing = await db.round.findUnique({ where: { number } }); + if (existing) return NextResponse.json({ error: "این شماره دور قبلاً ثبت شده" }, { status: 400 }); + + const round = await db.round.create({ + data: { number, name, deadline: new Date(deadline) }, + }); + return NextResponse.json(round, { status: 201 }); +} diff --git a/app/api/team/captain/route.ts b/app/api/team/captain/route.ts new file mode 100644 index 0000000..0d08c9d --- /dev/null +++ b/app/api/team/captain/route.ts @@ -0,0 +1,23 @@ +import { NextRequest, NextResponse } from "next/server"; +import { db } from "@/lib/db"; +import { getServerSession } from "next-auth"; +import { authOptions } from "@/lib/auth"; + +export async function PUT(req: NextRequest) { + const session = await getServerSession(authOptions); + if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + + const { playerId, type } = await req.json(); + const team = await db.team.findUnique({ where: { userId: (session.user as any).id } }); + if (!team) return NextResponse.json({ error: "تیم پیدا نشد" }, { status: 404 }); + + if (type === "captain") { + await db.teamPlayer.updateMany({ where: { teamId: team.id }, data: { isCaptain: false } }); + await db.teamPlayer.update({ where: { teamId_playerId: { teamId: team.id, playerId } }, data: { isCaptain: true } }); + } else { + await db.teamPlayer.updateMany({ where: { teamId: team.id }, data: { isViceCaptain: false } }); + await db.teamPlayer.update({ where: { teamId_playerId: { teamId: team.id, playerId } }, data: { isViceCaptain: true } }); + } + + return NextResponse.json({ success: true }); +} diff --git a/app/api/team/formation/route.ts b/app/api/team/formation/route.ts new file mode 100644 index 0000000..73b527b --- /dev/null +++ b/app/api/team/formation/route.ts @@ -0,0 +1,29 @@ +import { NextRequest, NextResponse } from "next/server"; +import { db } from "@/lib/db"; +import { getServerSession } from "next-auth"; +import { authOptions } from "@/lib/auth"; +import { getFormationChangeIssues, FORMATIONS } from "@/lib/teamValidation"; + +export async function PUT(req: NextRequest) { + const session = await getServerSession(authOptions); + if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + + const { formation } = await req.json(); + if (!FORMATIONS[formation]) return NextResponse.json({ error: "ترکیب نامعتبر" }, { status: 400 }); + + const team = await db.team.findUnique({ + where: { userId: (session.user as any).id }, + include: { players: { include: { player: true } } }, + }); + if (!team) return NextResponse.json({ error: "تیم پیدا نشد" }, { status: 404 }); + + const playerList = team.players.map((tp) => ({ position: tp.player.position, isBench: tp.isBench })); + const issues = getFormationChangeIssues(playerList, team.formation, formation); + + if (issues.length > 0) { + return NextResponse.json({ error: issues.join(" | "), issues }, { status: 400 }); + } + + const updated = await db.team.update({ where: { id: team.id }, data: { formation } }); + return NextResponse.json(updated); +} diff --git a/app/api/team/players/route.ts b/app/api/team/players/route.ts new file mode 100644 index 0000000..1201fc8 --- /dev/null +++ b/app/api/team/players/route.ts @@ -0,0 +1,65 @@ +import { NextRequest, NextResponse } from "next/server"; +import { db } from "@/lib/db"; +import { getServerSession } from "next-auth"; +import { authOptions } from "@/lib/auth"; + +// اضافه کردن بازیکن به تیم +export async function POST(req: NextRequest) { + const session = await getServerSession(authOptions); + if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + + const { playerId, isBench } = await req.json(); + const userId = (session.user as any).id; + + const team = await db.team.findUnique({ + where: { userId }, + include: { players: { include: { player: true } } }, + }); + + if (!team) return NextResponse.json({ error: "ابتدا تیم بساز" }, { status: 400 }); + + const player = await db.player.findUnique({ where: { id: playerId } }); + if (!player) return NextResponse.json({ error: "بازیکن پیدا نشد" }, { status: 404 }); + + // چک بودجه + const spent = team.players.reduce((s, tp) => s + tp.player.price, 0); + if (spent + player.price > team.budget) + return NextResponse.json({ error: "بودجه کافی نیست" }, { status: 400 }); + + // چک تعداد (۱۵ نفر: ۱۱ اصلی + ۴ ذخیره) + if (team.players.length >= 15) + return NextResponse.json({ error: "تیم پر است (حداکثر ۱۵ بازیکن)" }, { status: 400 }); + + // چک تکراری + const exists = team.players.find((tp) => tp.playerId === playerId); + if (exists) return NextResponse.json({ error: "این بازیکن قبلاً انتخاب شده" }, { status: 400 }); + + // چک حداکثر ۳ بازیکن از یک تیم ملی + const sameCountry = team.players.filter((tp) => tp.player.countryId === player.countryId).length; + if (sameCountry >= 3) + return NextResponse.json({ error: "حداکثر ۳ بازیکن از یک تیم ملی" }, { status: 400 }); + + const tp = await db.teamPlayer.create({ + data: { teamId: team.id, playerId, isBench: isBench ?? false }, + }); + + return NextResponse.json(tp, { status: 201 }); +} + +// حذف بازیکن از تیم +export async function DELETE(req: NextRequest) { + const session = await getServerSession(authOptions); + if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + + const { playerId } = await req.json(); + const userId = (session.user as any).id; + + const team = await db.team.findUnique({ where: { userId } }); + if (!team) return NextResponse.json({ error: "تیم پیدا نشد" }, { status: 404 }); + + await db.teamPlayer.delete({ + where: { teamId_playerId: { teamId: team.id, playerId } }, + }); + + return NextResponse.json({ success: true }); +} diff --git a/app/api/team/route.ts b/app/api/team/route.ts new file mode 100644 index 0000000..8558e6d --- /dev/null +++ b/app/api/team/route.ts @@ -0,0 +1,34 @@ +import { NextRequest, NextResponse } from "next/server"; +import { db } from "@/lib/db"; +import { getServerSession } from "next-auth"; +import { authOptions } from "@/lib/auth"; + +export async function GET() { + const session = await getServerSession(authOptions); + if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + + const team = await db.team.findUnique({ + where: { userId: (session.user as any).id }, + include: { + players: { + include: { player: true }, + }, + }, + }); + + return NextResponse.json(team); +} + +export async function POST(req: NextRequest) { + const session = await getServerSession(authOptions); + if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + + const { name } = await req.json(); + const userId = (session.user as any).id; + + const existing = await db.team.findUnique({ where: { userId } }); + if (existing) return NextResponse.json({ error: "Team already exists" }, { status: 400 }); + + const team = await db.team.create({ data: { name, userId } }); + return NextResponse.json(team, { status: 201 }); +} diff --git a/app/api/team/submit/route.ts b/app/api/team/submit/route.ts new file mode 100644 index 0000000..bdd2ca3 --- /dev/null +++ b/app/api/team/submit/route.ts @@ -0,0 +1,33 @@ +import { NextResponse } from "next/server"; +import { db } from "@/lib/db"; +import { getServerSession } from "next-auth"; +import { authOptions } from "@/lib/auth"; +import { validateTeamComposition } from "@/lib/teamValidation"; + +export async function POST() { + const session = await getServerSession(authOptions); + if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + + const team = await db.team.findUnique({ + where: { userId: (session.user as any).id }, + include: { players: { include: { player: true } } }, + }); + + if (!team) return NextResponse.json({ error: "تیم پیدا نشد" }, { status: 404 }); + + const playerList = team.players.map((tp) => ({ + position: tp.player.position, + isBench: tp.isBench, + })); + + const { valid, errors } = validateTeamComposition(playerList, team.formation); + if (!valid) return NextResponse.json({ error: errors.join(" | ") }, { status: 400 }); + + // مستقیم ACTIVE میشه - نیازی به تایید ادمین نیست + const updated = await db.team.update({ + where: { id: team.id }, + data: { status: "ACTIVE" }, + }); + + return NextResponse.json(updated); +} diff --git a/app/api/user/profile/route.ts b/app/api/user/profile/route.ts new file mode 100644 index 0000000..5caefde --- /dev/null +++ b/app/api/user/profile/route.ts @@ -0,0 +1,16 @@ +import { NextRequest, NextResponse } from "next/server"; +import { db } from "@/lib/db"; +import { getServerSession } from "next-auth"; +import { authOptions } from "@/lib/auth"; + +export async function PUT(req: NextRequest) { + const session = await getServerSession(authOptions); + if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + + const { name } = await req.json(); + const user = await db.user.update({ + where: { id: (session.user as any).id }, + data: { name }, + }); + return NextResponse.json({ name: user.name }); +} diff --git a/app/error.tsx b/app/error.tsx new file mode 100644 index 0000000..a6f2ced --- /dev/null +++ b/app/error.tsx @@ -0,0 +1,14 @@ +"use client"; + +export default function Error({ error, reset }: { error: Error; reset: () => void }) { + return ( +
+
😬
+

یه مشکلی پیش اومد

+

{error.message}

+ +
+ ); +} diff --git a/app/globals.css b/app/globals.css new file mode 100644 index 0000000..ee18c3b --- /dev/null +++ b/app/globals.css @@ -0,0 +1,15 @@ +@import "tailwindcss"; + +@font-face { + font-family: "Lahze"; + src: url("/fonts/lahze.woff2") format("woff2"), + url("/fonts/lahze.woff") format("woff"); + font-weight: normal; + font-style: normal; + font-display: swap; +} + +* { + direction: rtl; + font-family: "Lahze", sans-serif; +} diff --git a/app/layout.tsx b/app/layout.tsx new file mode 100644 index 0000000..c4e48fc --- /dev/null +++ b/app/layout.tsx @@ -0,0 +1,22 @@ +import type { Metadata } from "next"; +import "./globals.css"; +import SessionProvider from "@/components/SessionProvider"; +import Navbar from "@/components/Navbar"; + +export const metadata: Metadata = { + title: "فانتزی جام جهانی", + description: "بازی فوتبال فانتزی جام جهانی", +}; + +export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + + + + {children} + + + + ); +} diff --git a/app/leaderboard/page.tsx b/app/leaderboard/page.tsx new file mode 100644 index 0000000..343edd9 --- /dev/null +++ b/app/leaderboard/page.tsx @@ -0,0 +1,48 @@ +import { db } from "@/lib/db"; + +export default async function LeaderboardPage() { + const teams = await db.team.findMany({ + where: { status: "ACTIVE" }, + orderBy: { totalPoints: "desc" }, + include: { user: { select: { name: true, email: true } } }, + take: 100, + }); + + return ( +
+

🏆 جدول امتیازات

+
+ + + + + + + + + + + {teams.map((t, i) => ( + + + + + + + ))} + +
رتبهتیممدیرامتیاز
+ {i === 0 ? "🥇" : i === 1 ? "🥈" : i === 2 ? "🥉" : i + 1} + {t.name}{t.user.name ?? t.user.email}{t.totalPoints}
+ {teams.length === 0 && ( +
هنوز تیمی ثبت نشده
+ )} +
+
+ ); +} diff --git a/app/login/page.tsx b/app/login/page.tsx new file mode 100644 index 0000000..d981c08 --- /dev/null +++ b/app/login/page.tsx @@ -0,0 +1,77 @@ +"use client"; + +import { signIn } from "next-auth/react"; +import { useState } from "react"; +import { useRouter } from "next/navigation"; +import Link from "next/link"; + +export default function LoginPage() { + const router = useRouter(); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [error, setError] = useState(""); + const [loading, setLoading] = useState(false); + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + setLoading(true); + const res = await signIn("credentials", { email, password, redirect: false }); + if (res?.error) { + setError("ایمیل یا رمز عبور اشتباه است"); + setLoading(false); + } else { + router.push("/team"); + } + } + + return ( +
+
+
+
+

ورود به فانتزی جام جهانی

+
+
+ {error && ( +
+ {error} +
+ )} +
+ + setEmail(e.target.value)} + className="w-full border-2 rounded-xl px-4 py-3 focus:outline-none focus:border-green-500 transition" + required + /> +
+
+ + setPassword(e.target.value)} + className="w-full border-2 rounded-xl px-4 py-3 focus:outline-none focus:border-green-500 transition" + required + /> +
+ +

+ حساب نداری؟{" "} + + ثبت‌نام + +

+
+
+
+ ); +} diff --git a/app/matches/page.tsx b/app/matches/page.tsx new file mode 100644 index 0000000..7fe5efd --- /dev/null +++ b/app/matches/page.tsx @@ -0,0 +1,82 @@ +import { db } from "@/lib/db"; + +const stageLabel: Record = { + GROUP: "مرحله گروهی", + ROUND_OF_16: "یک‌هشتم نهایی", + QUARTER_FINAL: "یک‌چهارم نهایی", + SEMI_FINAL: "نیمه‌نهایی", + THIRD_PLACE: "رده‌بندی", + FINAL: "فینال", +}; + +const statusStyle: Record = { + SCHEDULED: "bg-gray-100 text-gray-600", + LIVE: "bg-red-100 text-red-600 animate-pulse", + FINISHED: "bg-green-100 text-green-700", +}; + +const statusLabel: Record = { + SCHEDULED: "برنامه‌ریزی شده", + LIVE: "🔴 زنده", + FINISHED: "پایان یافته", +}; + +export default async function MatchesPage() { + const matches = await db.match.findMany({ + include: { homeTeam: true, awayTeam: true, round: true }, + orderBy: { matchDate: "asc" }, + }); + + const grouped = matches.reduce((acc, m) => { + const key = m.stage; + if (!acc[key]) acc[key] = []; + acc[key].push(m); + return acc; + }, {} as Record); + + return ( +
+

بازی‌ها

+ + {Object.entries(grouped).map(([stage, stageMatches]) => ( +
+

+ {stageLabel[stage] ?? stage} +

+
+ {stageMatches.map((m) => ( +
+
+ {m.homeTeam.name} + {m.homeTeam.flagUrl ?? "🏳️"} +
+
+ {m.status === "FINISHED" || m.status === "LIVE" ? ( +
+ {m.homeScore} - {m.awayScore} +
+ ) : ( +
+ {new Date(m.matchDate).toLocaleDateString("fa-IR")} +
+ )} + + {statusLabel[m.status]} + +
+
+ {m.awayTeam.flagUrl ?? "🏳️"} + {m.awayTeam.name} +
+
+ ))} +
+
+ ))} + + {matches.length === 0 && ( +
هنوز بازی‌ای ثبت نشده
+ )} +
+ ); +} diff --git a/app/not-found.tsx b/app/not-found.tsx new file mode 100644 index 0000000..9fe8a1c --- /dev/null +++ b/app/not-found.tsx @@ -0,0 +1,14 @@ +import Link from "next/link"; + +export default function NotFound() { + return ( +
+
+

۴۰۴

+

این صفحه پیدا نشد!

+ + برگشت به خانه + +
+ ); +} diff --git a/app/page.tsx b/app/page.tsx new file mode 100644 index 0000000..d18e760 --- /dev/null +++ b/app/page.tsx @@ -0,0 +1,69 @@ +import Link from "next/link"; +import { db } from "@/lib/db"; + +export default async function HomePage() { + const [playerCount, matchCount, teamCount] = await Promise.all([ + db.player.count(), + db.match.count(), + db.team.count(), + ]); + + return ( +
+ {/* Hero */} +
+
+
🏆
+

فانتزی جام جهانی

+

+ تیم رویاییت رو از بهترین بازیکنان جهان بساز، امتیاز جمع کن و با بقیه رقابت کن +

+
+ + شروع کن + + + بازیکنان + +
+
+
+ + {/* Stats */} +
+
+
{playerCount}
+
بازیکن
+
+
+
{matchCount}
+
بازی
+
+
+
{teamCount}
+
تیم فانتزی
+
+
+ + {/* How it works */} +
+
+

چطور کار می‌کنه؟

+
+ {[ + { icon: "👤", title: "ثبت‌نام کن", desc: "یه حساب بساز و وارد شو" }, + { icon: "⚽", title: "تیم بساز", desc: "۱۵ بازیکن با بودجه ۱۰۰ میلیون انتخاب کن" }, + { icon: "📊", title: "امتیاز جمع کن", desc: "بر اساس عملکرد واقعی بازیکنا امتیاز بگیر" }, + ].map((item) => ( +
+
{item.icon}
+

{item.title}

+

{item.desc}

+
+ ))} +
+
+
+
+ ); +} diff --git a/app/players/page.tsx b/app/players/page.tsx new file mode 100644 index 0000000..a93aadd --- /dev/null +++ b/app/players/page.tsx @@ -0,0 +1,72 @@ +import { db } from "@/lib/db"; +import PositionBadge from "@/components/PositionBadge"; + +export default async function PlayersPage({ + searchParams, +}: { + searchParams: { position?: string; country?: string }; +}) { + const players = await db.player.findMany({ + where: { + ...(searchParams.position ? { position: searchParams.position as any } : {}), + ...(searchParams.country ? { countryId: searchParams.country } : {}), + }, + include: { country: true }, + orderBy: { totalPoints: "desc" }, + }); + + const countries = await db.country.findMany({ orderBy: { name: "asc" } }); + + return ( +
+

بازیکنان

+ + {/* فیلترها */} + + + {/* جدول */} +
+ + + + + + + + + + + + + {players.map((p, i) => ( + + + + + + + + + ))} + +
#بازیکنپستتیم ملیقیمتامتیاز
{i + 1}{p.name} + + {p.country.name}{p.price}M{p.totalPoints}
+
+
+ ); +} diff --git a/app/register/page.tsx b/app/register/page.tsx new file mode 100644 index 0000000..0d6805e --- /dev/null +++ b/app/register/page.tsx @@ -0,0 +1,71 @@ +"use client"; + +import { useState } from "react"; +import { useRouter } from "next/navigation"; +import Link from "next/link"; + +export default function RegisterPage() { + const router = useRouter(); + const [form, setForm] = useState({ name: "", email: "", password: "" }); + const [error, setError] = useState(""); + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + const res = await fetch("/api/auth/register", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(form), + }); + const data = await res.json(); + if (!res.ok) { + setError(data.error || "خطا در ثبت‌نام"); + } else { + router.push("/login"); + } + } + + return ( +
+
+

ثبت‌نام

+ {error &&

{error}

} +
+ + setForm({ ...form, name: e.target.value })} + className="w-full border rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-green-500" + required + /> +
+
+ + setForm({ ...form, email: e.target.value })} + className="w-full border rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-green-500" + required + /> +
+
+ + setForm({ ...form, password: e.target.value })} + className="w-full border rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-green-500" + required + /> +
+ +

+ حساب داری؟ ورود +

+
+
+ ); +} diff --git a/app/shop/ShopClient.tsx b/app/shop/ShopClient.tsx new file mode 100644 index 0000000..0490e17 --- /dev/null +++ b/app/shop/ShopClient.tsx @@ -0,0 +1,75 @@ +"use client"; + +import { useState } from "react"; + +type Package = { + id: string; + name: string; + budgetBonus: number; + price: number; + description: string | null; +}; + +const ICONS: Record = { + "pkg-silver": "🥈", + "pkg-gold": "🥇", + "pkg-diamond": "💎", +}; + +export default function ShopClient({ packages }: { packages: Package[] }) { + const [loading, setLoading] = useState(null); + + async function handleBuy(packageId: string) { + setLoading(packageId); + const res = await fetch("/api/payment/request", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ packageId }), + }); + const data = await res.json(); + if (res.ok && data.paymentUrl) { + window.location.href = data.paymentUrl; + } else { + alert(data.error ?? "خطا در اتصال به درگاه"); + setLoading(null); + } + } + + return ( +
+ {packages.map((pkg) => ( +
+ {pkg.id === "pkg-gold" && ( +
+ محبوب‌ترین +
+ )} +
{ICONS[pkg.id] ?? "📦"}
+

{pkg.name}

+

{pkg.description}

+
+
+{pkg.budgetBonus}M
+
افزایش بودجه
+
+
+ {pkg.price.toLocaleString("fa-IR")} تومان +
+ +
+ ))} +
+ ); +} diff --git a/app/shop/page.tsx b/app/shop/page.tsx new file mode 100644 index 0000000..c92c4f9 --- /dev/null +++ b/app/shop/page.tsx @@ -0,0 +1,53 @@ +import { db } from "@/lib/db"; +import { requireAuth } from "@/lib/session"; +import ShopClient from "./ShopClient"; + +export default async function ShopPage({ + searchParams, +}: { + searchParams: { status?: string; refId?: string }; +}) { + const session = await requireAuth(); + const userId = (session.user as any).id; + + const [packages, team] = await Promise.all([ + db.package.findMany({ where: { isActive: true }, orderBy: { price: "asc" } }), + db.team.findUnique({ where: { userId }, select: { budget: true, name: true } }), + ]); + + return ( +
+

فروشگاه

+

بودجه تیمت رو افزایش بده و بازیکنان بهتری بگیر

+ + {searchParams.status === "success" && ( +
+ 🎉 +
+
پرداخت موفق
+
کد پیگیری: {searchParams.refId}
+
+
+ )} + + {(searchParams.status === "failed" || searchParams.status === "cancelled") && ( +
+
پرداخت ناموفق بود
+
لطفاً دوباره امتحان کنید
+
+ )} + + {team && ( +
+
+
بودجه فعلی تیم
+
{team.budget.toFixed(1)}M
+
+
💰
+
+ )} + + +
+ ); +} diff --git a/components/Navbar.tsx b/components/Navbar.tsx new file mode 100644 index 0000000..24188a9 --- /dev/null +++ b/components/Navbar.tsx @@ -0,0 +1,43 @@ +"use client"; + +import Link from "next/link"; +import { useSession, signOut } from "next-auth/react"; + +export default function Navbar() { + const { data: session } = useSession(); + + return ( + + ); +} diff --git a/components/PositionBadge.tsx b/components/PositionBadge.tsx new file mode 100644 index 0000000..a9b39ea --- /dev/null +++ b/components/PositionBadge.tsx @@ -0,0 +1,21 @@ +const colors: Record = { + GK: "bg-yellow-400 text-yellow-900", + DEF: "bg-blue-500 text-white", + MID: "bg-green-500 text-white", + FWD: "bg-red-500 text-white", +}; + +const labels: Record = { + GK: "دروازه‌بان", + DEF: "مدافع", + MID: "هافبک", + FWD: "مهاجم", +}; + +export default function PositionBadge({ position }: { position: string }) { + return ( + + {labels[position] ?? position} + + ); +} diff --git a/components/SessionProvider.tsx b/components/SessionProvider.tsx new file mode 100644 index 0000000..4a9a3fb --- /dev/null +++ b/components/SessionProvider.tsx @@ -0,0 +1,7 @@ +"use client"; + +import { SessionProvider as NextSessionProvider } from "next-auth/react"; + +export default function SessionProvider({ children }: { children: React.ReactNode }) { + return {children}; +} diff --git a/lib/auth.ts b/lib/auth.ts new file mode 100644 index 0000000..83d7499 --- /dev/null +++ b/lib/auth.ts @@ -0,0 +1,52 @@ +import { NextAuthOptions } from "next-auth"; +import { PrismaAdapter } from "@auth/prisma-adapter"; +import CredentialsProvider from "next-auth/providers/credentials"; +import { db } from "@/lib/db"; +import bcrypt from "bcryptjs"; + +export const authOptions: NextAuthOptions = { + adapter: PrismaAdapter(db) as any, + session: { strategy: "jwt" }, + pages: { + signIn: "/login", + }, + providers: [ + CredentialsProvider({ + name: "credentials", + credentials: { + email: { label: "Email", type: "email" }, + password: { label: "Password", type: "password" }, + }, + async authorize(credentials) { + if (!credentials?.email || !credentials?.password) return null; + + const user = await db.user.findUnique({ + where: { email: credentials.email }, + }); + + if (!user || !user.password) return null; + + const isValid = await bcrypt.compare(credentials.password, user.password); + if (!isValid) return null; + + return user; + }, + }), + ], + callbacks: { + async jwt({ token, user }) { + if (user) { + token.role = (user as any).role; + token.id = user.id; + } + return token; + }, + async session({ session, token }) { + if (session.user) { + (session.user as any).role = token.role; + (session.user as any).id = token.id; + } + return session; + }, + }, +}; diff --git a/lib/db.ts b/lib/db.ts new file mode 100644 index 0000000..995b554 --- /dev/null +++ b/lib/db.ts @@ -0,0 +1,13 @@ +import { PrismaClient } from "@prisma/client"; + +const globalForPrisma = globalThis as unknown as { + prisma: PrismaClient | undefined; +}; + +export const db = + globalForPrisma.prisma ?? + new PrismaClient({ + log: process.env.NODE_ENV === "development" ? ["query", "error", "warn"] : ["error"], + }); + +if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = db; diff --git a/lib/points.ts b/lib/points.ts new file mode 100644 index 0000000..fc197cc --- /dev/null +++ b/lib/points.ts @@ -0,0 +1,69 @@ +import type { Position, EventType } from "@prisma/client"; + +export const DEFAULT_RULES: Record>> = { + GK: { + GOAL: 10, ASSIST: 3, YELLOW_CARD: -1, RED_CARD: -3, SECOND_YELLOW: -3, + CLEAN_SHEET: 6, PENALTY_SAVED: 5, PENALTY_MISSED: -2, OWN_GOAL: -2, + MOTM: 3, EXTRA_TIME_BONUS: 1, INJURY_NO_SUB: -1, + }, + DEF: { + GOAL: 8, ASSIST: 3, YELLOW_CARD: -1, RED_CARD: -3, SECOND_YELLOW: -3, + CLEAN_SHEET: 4, PENALTY_SAVED: 0, PENALTY_MISSED: -2, OWN_GOAL: -2, + MOTM: 3, EXTRA_TIME_BONUS: 1, INJURY_NO_SUB: -1, + }, + MID: { + GOAL: 5, ASSIST: 3, YELLOW_CARD: -2, RED_CARD: -3, SECOND_YELLOW: -3, + CLEAN_SHEET: 1, PENALTY_SAVED: 0, PENALTY_MISSED: -2, OWN_GOAL: -2, + MOTM: 3, EXTRA_TIME_BONUS: 1, INJURY_NO_SUB: -1, + }, + FWD: { + GOAL: 4, ASSIST: 3, YELLOW_CARD: -1, RED_CARD: -3, SECOND_YELLOW: -3, + CLEAN_SHEET: 0, PENALTY_SAVED: 0, PENALTY_MISSED: -2, OWN_GOAL: -2, + MOTM: 3, EXTRA_TIME_BONUS: 1, INJURY_NO_SUB: -1, + }, +}; + +export const APPEARANCE_POINTS = { PLAYED_60_PLUS: 2, PLAYED_UNDER_60: 1 }; + +// این تابع db رو lazy import می‌کنه تا در seed مشکل نداشته باشیم +export async function getPointsForEvent(position: Position, eventType: EventType): Promise { + const { db } = await import("@/lib/db"); + const rule = await db.scoringRule.findUnique({ + where: { position_eventType: { position, eventType } }, + }); + if (rule) return rule.points; + return DEFAULT_RULES[position]?.[eventType] ?? 0; +} + +export async function calculateMatchPoints(stat: { + position: Position; + goals: number; + assists: number; + yellowCards: number; + redCards: number; + minutesPlayed: number; + cleanSheet: boolean; + penaltySaved: number; + penaltyMissed: number; + ownGoals: number; + isMotm: boolean; + extraTimeBonus: number; +}): Promise { + let pts = 0; + + if (stat.minutesPlayed >= 60) pts += APPEARANCE_POINTS.PLAYED_60_PLUS; + else if (stat.minutesPlayed > 0) pts += APPEARANCE_POINTS.PLAYED_UNDER_60; + + pts += stat.goals * (await getPointsForEvent(stat.position, "GOAL")); + pts += stat.assists * (await getPointsForEvent(stat.position, "ASSIST")); + pts += stat.yellowCards * (await getPointsForEvent(stat.position, "YELLOW_CARD")); + pts += stat.redCards * (await getPointsForEvent(stat.position, "RED_CARD")); + pts += stat.penaltySaved * (await getPointsForEvent(stat.position, "PENALTY_SAVED")); + pts += stat.penaltyMissed * (await getPointsForEvent(stat.position, "PENALTY_MISSED")); + pts += stat.ownGoals * (await getPointsForEvent(stat.position, "OWN_GOAL")); + pts += stat.extraTimeBonus * (await getPointsForEvent(stat.position, "EXTRA_TIME_BONUS")); + if (stat.cleanSheet) pts += await getPointsForEvent(stat.position, "CLEAN_SHEET"); + if (stat.isMotm) pts += await getPointsForEvent(stat.position, "MOTM"); + + return pts; +} diff --git a/lib/session.ts b/lib/session.ts new file mode 100644 index 0000000..2d46889 --- /dev/null +++ b/lib/session.ts @@ -0,0 +1,15 @@ +import { getServerSession } from "next-auth"; +import { authOptions } from "@/lib/auth"; +import { redirect } from "next/navigation"; + +export async function requireAuth() { + const session = await getServerSession(authOptions); + if (!session) redirect("/login"); + return session; +} + +export async function requireAdmin() { + const session = await getServerSession(authOptions); + if (!session || (session.user as any).role !== "ADMIN") redirect("/"); + return session; +} diff --git a/lib/teamValidation.ts b/lib/teamValidation.ts new file mode 100644 index 0000000..c60f1a6 --- /dev/null +++ b/lib/teamValidation.ts @@ -0,0 +1,86 @@ +export const FORMATIONS: Record = { + "4-3-3": { def: 4, mid: 3, fwd: 3 }, + "4-4-2": { def: 4, mid: 4, fwd: 2 }, + "4-5-1": { def: 4, mid: 5, fwd: 1 }, + "3-5-2": { def: 3, mid: 5, fwd: 2 }, + "3-4-3": { def: 3, mid: 4, fwd: 3 }, + "5-3-2": { def: 5, mid: 3, fwd: 2 }, + "5-4-1": { def: 5, mid: 4, fwd: 1 }, +}; + +export type FormationKey = keyof typeof FORMATIONS; + +// تعداد مورد نیاز هر پست = ترکیب + 1 ذخیره +export function requiredPerPosition(formation: string) { + const fmt = FORMATIONS[formation] ?? FORMATIONS["4-3-3"]; + return { + GK: 2, // 1 اصلی + 1 ذخیره + DEF: fmt.def + 1, + MID: fmt.mid + 1, + FWD: fmt.fwd + 1, + }; +} + +export type PlayerLike = { position: string; isBench: boolean }; + +export function validateTeamComposition( + players: PlayerLike[], + formation: string +): { valid: boolean; errors: string[] } { + const fmt = FORMATIONS[formation] ?? FORMATIONS["4-3-3"]; + const errors: string[] = []; + + const starters = players.filter((p) => !p.isBench); + const bench = players.filter((p) => p.isBench); + + // تعداد کل + if (players.length !== 15) { + errors.push(`تیم باید دقیقاً ۱۵ بازیکن داشته باشد (الان: ${players.length})`); + } + + // ترکیب اصلی + const startersByPos = countByPos(starters); + if (startersByPos.GK !== 1) errors.push("باید دقیقاً ۱ دروازه‌بان اصلی داشته باشی"); + if (startersByPos.DEF !== fmt.def) errors.push(`باید ${fmt.def} مدافع اصلی داشته باشی (الان: ${startersByPos.DEF ?? 0})`); + if (startersByPos.MID !== fmt.mid) errors.push(`باید ${fmt.mid} هافبک اصلی داشته باشی (الان: ${startersByPos.MID ?? 0})`); + if (startersByPos.FWD !== fmt.fwd) errors.push(`باید ${fmt.fwd} مهاجم اصلی داشته باشی (الان: ${startersByPos.FWD ?? 0})`); + + // ذخیره‌ها - هر پست باید ۱ ذخیره داشته باشه + const benchByPos = countByPos(bench); + if ((benchByPos.GK ?? 0) < 1) errors.push("باید ۱ دروازه‌بان ذخیره داشته باشی"); + if ((benchByPos.DEF ?? 0) < 1) errors.push("باید حداقل ۱ مدافع ذخیره داشته باشی"); + if ((benchByPos.MID ?? 0) < 1) errors.push("باید حداقل ۱ هافبک ذخیره داشته باشی"); + if ((benchByPos.FWD ?? 0) < 1) errors.push("باید حداقل ۱ مهاجم ذخیره داشته باشی"); + + return { valid: errors.length === 0, errors }; +} + +function countByPos(players: PlayerLike[]): Record { + return players.reduce((acc, p) => { + acc[p.position] = (acc[p.position] ?? 0) + 1; + return acc; + }, {} as Record); +} + +// وقتی ترکیب عوض میشه، چک کن آیا بازیکنان فعلی با ترکیب جدید سازگارن +export function getFormationChangeIssues( + players: PlayerLike[], + oldFormation: string, + newFormation: string +): string[] { + const oldFmt = FORMATIONS[oldFormation] ?? FORMATIONS["4-3-3"]; + const newFmt = FORMATIONS[newFormation] ?? FORMATIONS["4-3-3"]; + const issues: string[] = []; + + const starters = players.filter((p) => !p.isBench); + const byPos = countByPos(starters); + + if ((byPos.DEF ?? 0) > newFmt.def) + issues.push(`باید ${(byPos.DEF ?? 0) - newFmt.def} مدافع رو به ذخیره ببری یا حذف کنی`); + if ((byPos.MID ?? 0) > newFmt.mid) + issues.push(`باید ${(byPos.MID ?? 0) - newFmt.mid} هافبک رو به ذخیره ببری یا حذف کنی`); + if ((byPos.FWD ?? 0) > newFmt.fwd) + issues.push(`باید ${(byPos.FWD ?? 0) - newFmt.fwd} مهاجم رو به ذخیره ببری یا حذف کنی`); + + return issues; +} diff --git a/lib/zarinpal.ts b/lib/zarinpal.ts new file mode 100644 index 0000000..86234e6 --- /dev/null +++ b/lib/zarinpal.ts @@ -0,0 +1,55 @@ +const ZARINPAL_MERCHANT = process.env.ZARINPAL_MERCHANT_ID ?? "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"; +const IS_SANDBOX = process.env.NODE_ENV !== "production"; + +const BASE_URL = IS_SANDBOX + ? "https://sandbox.zarinpal.com/pg/v4/payment" + : "https://api.zarinpal.com/pg/v4/payment"; + +const GATEWAY = IS_SANDBOX + ? "https://sandbox.zarinpal.com/pg/StartPay" + : "https://www.zarinpal.com/pg/StartPay"; + +export async function requestPayment(amount: number, description: string, callbackUrl: string) { + const res = await fetch(`${BASE_URL}/request.json`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + merchant_id: ZARINPAL_MERCHANT, + amount, // تومان + description, + callback_url: callbackUrl, + }), + }); + + const data = await res.json(); + + if (data.data?.code === 100) { + return { + success: true, + authority: data.data.authority as string, + paymentUrl: `${GATEWAY}/${data.data.authority}`, + }; + } + + return { success: false, error: data.errors?.message ?? "خطا در اتصال به درگاه" }; +} + +export async function verifyPayment(authority: string, amount: number) { + const res = await fetch(`${BASE_URL}/verify.json`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + merchant_id: ZARINPAL_MERCHANT, + amount, + authority, + }), + }); + + const data = await res.json(); + + if (data.data?.code === 100 || data.data?.code === 101) { + return { success: true, refId: String(data.data.ref_id) }; + } + + return { success: false, error: "پرداخت تایید نشد" }; +} diff --git a/next.config.ts b/next.config.ts new file mode 100644 index 0000000..cb651cd --- /dev/null +++ b/next.config.ts @@ -0,0 +1,5 @@ +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = {}; + +export default nextConfig; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..245e27a --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2810 @@ +{ + "name": "football", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "@auth/prisma-adapter": "^2.11.1", + "@prisma/adapter-pg": "^7.6.0", + "@prisma/client": "^6.19.3", + "@tailwindcss/postcss": "^4.2.2", + "@types/node": "^25.5.2", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "autoprefixer": "^10.4.27", + "bcryptjs": "^3.0.3", + "next": "^16.2.2", + "next-auth": "^4.24.13", + "pg": "^8.20.0", + "react": "^19.2.4", + "react-dom": "^19.2.4", + "tailwindcss": "^4.2.2", + "typescript": "^6.0.2" + }, + "devDependencies": { + "@types/bcryptjs": "^2.4.6", + "@types/pg": "^8.20.0", + "prisma": "^6.19.3", + "ts-node": "^10.9.2" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://package-mirror.liara.ir/repository/npm/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@auth/prisma-adapter": { + "version": "2.11.1", + "resolved": "https://package-mirror.liara.ir/repository/npm/@auth/prisma-adapter/-/prisma-adapter-2.11.1.tgz", + "integrity": "sha512-Ke7DXP0Fy0Mlmjz/ZJLXwQash2UkA4621xCM0rMtEczr1kppLc/njCbUkHkIQ/PnmILjqSPEKeTjDPsYruvkug==", + "license": "ISC", + "dependencies": { + "@auth/core": "0.41.1" + }, + "peerDependencies": { + "@prisma/client": ">=2.26.0 || >=3 || >=4 || >=5 || >=6" + } + }, + "node_modules/@auth/prisma-adapter/node_modules/@auth/core": { + "version": "0.41.1", + "resolved": "https://package-mirror.liara.ir/repository/npm/@auth/core/-/core-0.41.1.tgz", + "integrity": "sha512-t9cJ2zNYAdWMacGRMT6+r4xr1uybIdmYa49calBPeTqwgAFPV/88ac9TEvCR85pvATiSPt8VaNf+Gt24JIT/uw==", + "license": "ISC", + "dependencies": { + "@panva/hkdf": "^1.2.1", + "jose": "^6.0.6", + "oauth4webapi": "^3.3.0", + "preact": "10.24.3", + "preact-render-to-string": "6.5.11" + }, + "peerDependencies": { + "@simplewebauthn/browser": "^9.0.1", + "@simplewebauthn/server": "^9.0.2", + "nodemailer": "^7.0.7" + }, + "peerDependenciesMeta": { + "@simplewebauthn/browser": { + "optional": true + }, + "@simplewebauthn/server": { + "optional": true + }, + "nodemailer": { + "optional": true + } + } + }, + "node_modules/@auth/prisma-adapter/node_modules/jose": { + "version": "6.2.2", + "resolved": "https://package-mirror.liara.ir/repository/npm/jose/-/jose-6.2.2.tgz", + "integrity": "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/@auth/prisma-adapter/node_modules/oauth4webapi": { + "version": "3.8.5", + "resolved": "https://package-mirror.liara.ir/repository/npm/oauth4webapi/-/oauth4webapi-3.8.5.tgz", + "integrity": "sha512-A8jmyUckVhRJj5lspguklcl90Ydqk61H3dcU0oLhH3Yv13KpAliKTt5hknpGGPZSSfOwGyraNEFmofDYH+1kSg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/@auth/prisma-adapter/node_modules/preact-render-to-string": { + "version": "6.5.11", + "resolved": "https://package-mirror.liara.ir/repository/npm/preact-render-to-string/-/preact-render-to-string-6.5.11.tgz", + "integrity": "sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw==", + "license": "MIT", + "peerDependencies": { + "preact": ">=10" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://package-mirror.liara.ir/repository/npm/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://package-mirror.liara.ir/repository/npm/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.9.2", + "resolved": "https://package-mirror.liara.ir/repository/npm/@emnapi/runtime/-/runtime-1.9.2.tgz", + "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://package-mirror.liara.ir/repository/npm/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://package-mirror.liara.ir/repository/npm/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://package-mirror.liara.ir/repository/npm/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://package-mirror.liara.ir/repository/npm/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://package-mirror.liara.ir/repository/npm/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://package-mirror.liara.ir/repository/npm/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://package-mirror.liara.ir/repository/npm/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://package-mirror.liara.ir/repository/npm/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://package-mirror.liara.ir/repository/npm/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://package-mirror.liara.ir/repository/npm/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://package-mirror.liara.ir/repository/npm/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://package-mirror.liara.ir/repository/npm/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://package-mirror.liara.ir/repository/npm/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://package-mirror.liara.ir/repository/npm/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://package-mirror.liara.ir/repository/npm/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://package-mirror.liara.ir/repository/npm/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://package-mirror.liara.ir/repository/npm/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://package-mirror.liara.ir/repository/npm/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://package-mirror.liara.ir/repository/npm/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://package-mirror.liara.ir/repository/npm/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://package-mirror.liara.ir/repository/npm/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://package-mirror.liara.ir/repository/npm/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://package-mirror.liara.ir/repository/npm/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://package-mirror.liara.ir/repository/npm/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://package-mirror.liara.ir/repository/npm/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://package-mirror.liara.ir/repository/npm/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/gen-mapping/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://package-mirror.liara.ir/repository/npm/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://package-mirror.liara.ir/repository/npm/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://package-mirror.liara.ir/repository/npm/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://package-mirror.liara.ir/repository/npm/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://package-mirror.liara.ir/repository/npm/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://package-mirror.liara.ir/repository/npm/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@next/env": { + "version": "16.2.2", + "resolved": "https://package-mirror.liara.ir/repository/npm/@next/env/-/env-16.2.2.tgz", + "integrity": "sha512-LqSGz5+xGk9EL/iBDr2yo/CgNQV6cFsNhRR2xhSXYh7B/hb4nePCxlmDvGEKG30NMHDFf0raqSyOZiQrO7BkHQ==", + "license": "MIT" + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "16.2.2", + "resolved": "https://package-mirror.liara.ir/repository/npm/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.2.tgz", + "integrity": "sha512-B92G3ulrwmkDSEJEp9+XzGLex5wC1knrmCSIylyVeiAtCIfvEJYiN3v5kXPlYt5R4RFlsfO/v++aKV63Acrugg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "16.2.2", + "resolved": "https://package-mirror.liara.ir/repository/npm/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.2.tgz", + "integrity": "sha512-7ZwSgNKJNQiwW0CKhNm9B1WS2L1Olc4B2XY0hPYCAL3epFnugMhuw5TMWzMilQ3QCZcCHoYm9NGWTHbr5REFxw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "16.2.2", + "resolved": "https://package-mirror.liara.ir/repository/npm/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.2.tgz", + "integrity": "sha512-c3m8kBHMziMgo2fICOP/cd/5YlrxDU5YYjAJeQLyFsCqVF8xjOTH/QYG4a2u48CvvZZSj1eHQfBCbyh7kBr30Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "16.2.2", + "resolved": "https://package-mirror.liara.ir/repository/npm/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.2.tgz", + "integrity": "sha512-VKLuscm0P/mIfzt+SDdn2+8TNNJ7f0qfEkA+az7OqQbjzKdBxAHs0UvuiVoCtbwX+dqMEL9U54b5wQ/aN3dHeg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "16.2.2", + "resolved": "https://package-mirror.liara.ir/repository/npm/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.2.tgz", + "integrity": "sha512-kU3OPHJq6sBUjOk7wc5zJ7/lipn8yGldMoAv4z67j6ov6Xo/JvzA7L7LCsyzzsXmgLEhk3Qkpwqaq/1+XpNR3g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "16.2.2", + "resolved": "https://package-mirror.liara.ir/repository/npm/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.2.tgz", + "integrity": "sha512-CKXRILyErMtUftp+coGcZ38ZwE/Aqq45VMCcRLr2I4OXKrgxIBDXHnBgeX/UMil0S09i2JXaDL3Q+TN8D/cKmg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "16.2.2", + "resolved": "https://package-mirror.liara.ir/repository/npm/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.2.tgz", + "integrity": "sha512-sS/jSk5VUoShUqINJFvNjVT7JfR5ORYj/+/ZpOYbbIohv/lQfduWnGAycq2wlknbOql2xOR0DoV0s6Xfcy49+g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "16.2.2", + "resolved": "https://package-mirror.liara.ir/repository/npm/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.2.tgz", + "integrity": "sha512-aHaKceJgdySReT7qeck5oShucxWRiiEuwCGK8HHALe6yZga8uyFpLkPgaRw3kkF04U7ROogL/suYCNt/+CuXGA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@panva/hkdf": { + "version": "1.2.1", + "resolved": "https://package-mirror.liara.ir/repository/npm/@panva/hkdf/-/hkdf-1.2.1.tgz", + "integrity": "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/@prisma/adapter-pg": { + "version": "7.6.0", + "resolved": "https://package-mirror.liara.ir/repository/npm/@prisma/adapter-pg/-/adapter-pg-7.6.0.tgz", + "integrity": "sha512-BjHNmJqqa42NqJSDPnXUfwUofWo8LJY7Ui2gqxN4DmAOb+H/gGKv+hln2Xq/1kSJXPW5AXMXuNiPDMpywvyIOw==", + "license": "Apache-2.0", + "dependencies": { + "@prisma/driver-adapter-utils": "7.6.0", + "@types/pg": "^8.16.0", + "pg": "^8.16.3", + "postgres-array": "3.0.4" + } + }, + "node_modules/@prisma/client": { + "version": "6.19.3", + "resolved": "https://package-mirror.liara.ir/repository/npm/@prisma/client/-/client-6.19.3.tgz", + "integrity": "sha512-mKq3jQFhjvko5LTJFHGilsuQs+W+T3Gm451NzuTDGQxwCzwXHYnIu2zGkRoW+Exq3Rob7yp2MfzSrdIiZVhrBg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "peerDependencies": { + "prisma": "*", + "typescript": ">=5.1.0" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@prisma/config": { + "version": "6.19.3", + "resolved": "https://package-mirror.liara.ir/repository/npm/@prisma/config/-/config-6.19.3.tgz", + "integrity": "sha512-CBPT44BjlQxEt8kiMEauji2WHTDoVBOKl7UlewXmUgBPnr/oPRZC3psci5chJnYmH0ivEIog2OU9PGWoki3DLQ==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "c12": "3.1.0", + "deepmerge-ts": "7.1.5", + "effect": "3.21.0", + "empathic": "2.0.0" + } + }, + "node_modules/@prisma/debug": { + "version": "7.6.0", + "resolved": "https://package-mirror.liara.ir/repository/npm/@prisma/debug/-/debug-7.6.0.tgz", + "integrity": "sha512-LpHr3qos4lQZ6sxwjStf59YBht7m9/QF7NSQsMH6qGENWZu2w3UkQUGn1h5iRkDjnWRj3VHykOu9qFhps4ADvA==", + "license": "Apache-2.0" + }, + "node_modules/@prisma/driver-adapter-utils": { + "version": "7.6.0", + "resolved": "https://package-mirror.liara.ir/repository/npm/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.6.0.tgz", + "integrity": "sha512-D8j3p0RnhLuufMaRLX6QqtGgPC5Ao3l5oFP6Q5AL0rTHi4vna+NzGEipwCsfvcSvaGFCbsH3lsTMbb4WvY+ovA==", + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.6.0" + } + }, + "node_modules/@prisma/engines": { + "version": "6.19.3", + "resolved": "https://package-mirror.liara.ir/repository/npm/@prisma/engines/-/engines-6.19.3.tgz", + "integrity": "sha512-RSYxtlYFl5pJ8ZePgMv0lZ9IzVCOdTPOegrs2qcbAEFrBI1G33h6wyC9kjQvo0DnYEhEVY0X4LsuFHXLKQk88g==", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.19.3", + "@prisma/engines-version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7", + "@prisma/fetch-engine": "6.19.3", + "@prisma/get-platform": "6.19.3" + } + }, + "node_modules/@prisma/engines-version": { + "version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7", + "resolved": "https://package-mirror.liara.ir/repository/npm/@prisma/engines-version/-/engines-version-7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7.tgz", + "integrity": "sha512-03bgb1VD5gvuumNf+7fVGBzfpJPjmqV423l/WxsWk2cNQ42JD0/SsFBPhN6z8iAvdHs07/7ei77SKu7aZfq8bA==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/engines/node_modules/@prisma/debug": { + "version": "6.19.3", + "resolved": "https://package-mirror.liara.ir/repository/npm/@prisma/debug/-/debug-6.19.3.tgz", + "integrity": "sha512-ljkJ+SgpXNktLG0Q/n4JGYCkKf0f8oYLyjImS2I8e2q2WCfdRRtWER062ZV/ixaNP2M2VKlWXVJiGzZaUgbKZw==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/fetch-engine": { + "version": "6.19.3", + "resolved": "https://package-mirror.liara.ir/repository/npm/@prisma/fetch-engine/-/fetch-engine-6.19.3.tgz", + "integrity": "sha512-tKtl/qco9Nt7LU5iKhpultD8O4vMCZcU2CHjNTnRrL1QvSUr5W/GcyFPjNL87GtRrwBc7ubXXD9xy4EvLvt8JA==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.19.3", + "@prisma/engines-version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7", + "@prisma/get-platform": "6.19.3" + } + }, + "node_modules/@prisma/fetch-engine/node_modules/@prisma/debug": { + "version": "6.19.3", + "resolved": "https://package-mirror.liara.ir/repository/npm/@prisma/debug/-/debug-6.19.3.tgz", + "integrity": "sha512-ljkJ+SgpXNktLG0Q/n4JGYCkKf0f8oYLyjImS2I8e2q2WCfdRRtWER062ZV/ixaNP2M2VKlWXVJiGzZaUgbKZw==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/get-platform": { + "version": "6.19.3", + "resolved": "https://package-mirror.liara.ir/repository/npm/@prisma/get-platform/-/get-platform-6.19.3.tgz", + "integrity": "sha512-xFj1VcJ1N3MKooOQAGO0W5tsd0W2QzIvW7DD7c/8H14Zmp4jseeWAITm+w2LLoLrlhoHdPPh0NMZ8mfL6puoHA==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.19.3" + } + }, + "node_modules/@prisma/get-platform/node_modules/@prisma/debug": { + "version": "6.19.3", + "resolved": "https://package-mirror.liara.ir/repository/npm/@prisma/debug/-/debug-6.19.3.tgz", + "integrity": "sha512-ljkJ+SgpXNktLG0Q/n4JGYCkKf0f8oYLyjImS2I8e2q2WCfdRRtWER062ZV/ixaNP2M2VKlWXVJiGzZaUgbKZw==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://package-mirror.liara.ir/repository/npm/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://package-mirror.liara.ir/repository/npm/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.2.2", + "resolved": "https://package-mirror.liara.ir/repository/npm/@tailwindcss/node/-/node-4.2.2.tgz", + "integrity": "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.19.0", + "jiti": "^2.6.1", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.2.2" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.2.2", + "resolved": "https://package-mirror.liara.ir/repository/npm/@tailwindcss/oxide/-/oxide-4.2.2.tgz", + "integrity": "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==", + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.2.2", + "@tailwindcss/oxide-darwin-arm64": "4.2.2", + "@tailwindcss/oxide-darwin-x64": "4.2.2", + "@tailwindcss/oxide-freebsd-x64": "4.2.2", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2", + "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2", + "@tailwindcss/oxide-linux-arm64-musl": "4.2.2", + "@tailwindcss/oxide-linux-x64-gnu": "4.2.2", + "@tailwindcss/oxide-linux-x64-musl": "4.2.2", + "@tailwindcss/oxide-wasm32-wasi": "4.2.2", + "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2", + "@tailwindcss/oxide-win32-x64-msvc": "4.2.2" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.2.2", + "resolved": "https://package-mirror.liara.ir/repository/npm/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.2.tgz", + "integrity": "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.2.2", + "resolved": "https://package-mirror.liara.ir/repository/npm/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.2.tgz", + "integrity": "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.2.2", + "resolved": "https://package-mirror.liara.ir/repository/npm/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.2.tgz", + "integrity": "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.2.2", + "resolved": "https://package-mirror.liara.ir/repository/npm/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.2.tgz", + "integrity": "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.2.2", + "resolved": "https://package-mirror.liara.ir/repository/npm/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.2.tgz", + "integrity": "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.2.2", + "resolved": "https://package-mirror.liara.ir/repository/npm/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.2.tgz", + "integrity": "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.2.2", + "resolved": "https://package-mirror.liara.ir/repository/npm/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.2.tgz", + "integrity": "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.2.2", + "resolved": "https://package-mirror.liara.ir/repository/npm/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.2.tgz", + "integrity": "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.2.2", + "resolved": "https://package-mirror.liara.ir/repository/npm/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.2.tgz", + "integrity": "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.2.2", + "resolved": "https://package-mirror.liara.ir/repository/npm/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.2.tgz", + "integrity": "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.8.1", + "@emnapi/runtime": "^1.8.1", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.1.1", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.2.2", + "resolved": "https://package-mirror.liara.ir/repository/npm/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.2.tgz", + "integrity": "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.2.2", + "resolved": "https://package-mirror.liara.ir/repository/npm/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.2.tgz", + "integrity": "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/postcss": { + "version": "4.2.2", + "resolved": "https://package-mirror.liara.ir/repository/npm/@tailwindcss/postcss/-/postcss-4.2.2.tgz", + "integrity": "sha512-n4goKQbW8RVXIbNKRB/45LzyUqN451deQK0nzIeauVEqjlI49slUlgKYJM2QyUzap/PcpnS7kzSUmPb1sCRvYQ==", + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.2.2", + "@tailwindcss/oxide": "4.2.2", + "postcss": "^8.5.6", + "tailwindcss": "4.2.2" + } + }, + "node_modules/@tailwindcss/postcss/node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://package-mirror.liara.ir/repository/npm/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.12", + "resolved": "https://package-mirror.liara.ir/repository/npm/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://package-mirror.liara.ir/repository/npm/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://package-mirror.liara.ir/repository/npm/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://package-mirror.liara.ir/repository/npm/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/bcryptjs": { + "version": "2.4.6", + "resolved": "https://package-mirror.liara.ir/repository/npm/@types/bcryptjs/-/bcryptjs-2.4.6.tgz", + "integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.5.2", + "resolved": "https://package-mirror.liara.ir/repository/npm/@types/node/-/node-25.5.2.tgz", + "integrity": "sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/pg": { + "version": "8.20.0", + "resolved": "https://package-mirror.liara.ir/repository/npm/@types/pg/-/pg-8.20.0.tgz", + "integrity": "sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.14", + "resolved": "https://package-mirror.liara.ir/repository/npm/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://package-mirror.liara.ir/repository/npm/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://package-mirror.liara.ir/repository/npm/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://package-mirror.liara.ir/repository/npm/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://package-mirror.liara.ir/repository/npm/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/autoprefixer": { + "version": "10.4.27", + "resolved": "https://package-mirror.liara.ir/repository/npm/autoprefixer/-/autoprefixer-10.4.27.tgz", + "integrity": "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001774", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.14", + "resolved": "https://package-mirror.liara.ir/repository/npm/baseline-browser-mapping/-/baseline-browser-mapping-2.10.14.tgz", + "integrity": "sha512-fOVLPAsFTsQfuCkvahZkzq6nf8KvGWanlYoTh0SVA0A/PIUxQGU2AOZAoD95n2gFLVDW/jP6sbGLny95nmEuHA==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/bcryptjs": { + "version": "3.0.3", + "resolved": "https://package-mirror.liara.ir/repository/npm/bcryptjs/-/bcryptjs-3.0.3.tgz", + "integrity": "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==", + "license": "BSD-3-Clause", + "bin": { + "bcrypt": "bin/bcrypt" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://package-mirror.liara.ir/repository/npm/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/c12": { + "version": "3.1.0", + "resolved": "https://package-mirror.liara.ir/repository/npm/c12/-/c12-3.1.0.tgz", + "integrity": "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.3", + "confbox": "^0.2.2", + "defu": "^6.1.4", + "dotenv": "^16.6.1", + "exsolve": "^1.0.7", + "giget": "^2.0.0", + "jiti": "^2.4.2", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "perfect-debounce": "^1.0.0", + "pkg-types": "^2.2.0", + "rc9": "^2.1.2" + }, + "peerDependencies": { + "magicast": "^0.3.5" + }, + "peerDependenciesMeta": { + "magicast": { + "optional": true + } + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001785", + "resolved": "https://package-mirror.liara.ir/repository/npm/caniuse-lite/-/caniuse-lite-1.0.30001785.tgz", + "integrity": "sha512-blhOL/WNR+Km1RI/LCVAvA73xplXA7ZbjzI4YkMK9pa6T/P3F2GxjNpEkyw5repTw9IvkyrjyHpwjnhZ5FOvYQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://package-mirror.liara.ir/repository/npm/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/citty": { + "version": "0.1.6", + "resolved": "https://package-mirror.liara.ir/repository/npm/citty/-/citty-0.1.6.tgz", + "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "consola": "^3.2.3" + } + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://package-mirror.liara.ir/repository/npm/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, + "node_modules/confbox": { + "version": "0.2.4", + "resolved": "https://package-mirror.liara.ir/repository/npm/confbox/-/confbox-0.2.4.tgz", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://package-mirror.liara.ir/repository/npm/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://package-mirror.liara.ir/repository/npm/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://package-mirror.liara.ir/repository/npm/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://package-mirror.liara.ir/repository/npm/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/deepmerge-ts": { + "version": "7.1.5", + "resolved": "https://package-mirror.liara.ir/repository/npm/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz", + "integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==", + "devOptional": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/defu": { + "version": "6.1.6", + "resolved": "https://package-mirror.liara.ir/repository/npm/defu/-/defu-6.1.6.tgz", + "integrity": "sha512-f8mefEW4WIVg4LckePx3mALjQSPQgFlg9U8yaPdlsbdYcHQyj9n2zL2LJEA52smeYxOvmd/nB7TpMtHGMTHcug==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://package-mirror.liara.ir/repository/npm/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://package-mirror.liara.ir/repository/npm/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.4", + "resolved": "https://package-mirror.liara.ir/repository/npm/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://package-mirror.liara.ir/repository/npm/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "devOptional": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/effect": { + "version": "3.21.0", + "resolved": "https://package-mirror.liara.ir/repository/npm/effect/-/effect-3.21.0.tgz", + "integrity": "sha512-PPN80qRokCd1f015IANNhrwOnLO7GrrMQfk4/lnZRE/8j7UPWrNNjPV0uBrZutI/nHzernbW+J0hdqQysHiSnQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "fast-check": "^3.23.1" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.331", + "resolved": "https://package-mirror.liara.ir/repository/npm/electron-to-chromium/-/electron-to-chromium-1.5.331.tgz", + "integrity": "sha512-IbxXrsTlD3hRodkLnbxAPP4OuJYdWCeM3IOdT+CpcMoIwIoDfCmRpEtSPfwBXxVkg9xmBeY7Lz2Eo2TDn/HC3Q==", + "license": "ISC" + }, + "node_modules/empathic": { + "version": "2.0.0", + "resolved": "https://package-mirror.liara.ir/repository/npm/empathic/-/empathic-2.0.0.tgz", + "integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.20.1", + "resolved": "https://package-mirror.liara.ir/repository/npm/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz", + "integrity": "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://package-mirror.liara.ir/repository/npm/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/exsolve": { + "version": "1.0.8", + "resolved": "https://package-mirror.liara.ir/repository/npm/exsolve/-/exsolve-1.0.8.tgz", + "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/fast-check": { + "version": "3.23.2", + "resolved": "https://package-mirror.liara.ir/repository/npm/fast-check/-/fast-check-3.23.2.tgz", + "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==", + "devOptional": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "dependencies": { + "pure-rand": "^6.1.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://package-mirror.liara.ir/repository/npm/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/giget": { + "version": "2.0.0", + "resolved": "https://package-mirror.liara.ir/repository/npm/giget/-/giget-2.0.0.tgz", + "integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.4.0", + "defu": "^6.1.4", + "node-fetch-native": "^1.6.6", + "nypm": "^0.6.0", + "pathe": "^2.0.3" + }, + "bin": { + "giget": "dist/cli.mjs" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://package-mirror.liara.ir/repository/npm/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://package-mirror.liara.ir/repository/npm/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/jose": { + "version": "4.15.9", + "resolved": "https://package-mirror.liara.ir/repository/npm/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://package-mirror.liara.ir/repository/npm/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://package-mirror.liara.ir/repository/npm/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://package-mirror.liara.ir/repository/npm/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://package-mirror.liara.ir/repository/npm/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://package-mirror.liara.ir/repository/npm/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://package-mirror.liara.ir/repository/npm/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://package-mirror.liara.ir/repository/npm/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://package-mirror.liara.ir/repository/npm/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://package-mirror.liara.ir/repository/npm/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://package-mirror.liara.ir/repository/npm/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://package-mirror.liara.ir/repository/npm/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://package-mirror.liara.ir/repository/npm/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://package-mirror.liara.ir/repository/npm/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://package-mirror.liara.ir/repository/npm/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://package-mirror.liara.ir/repository/npm/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://package-mirror.liara.ir/repository/npm/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/next": { + "version": "16.2.2", + "resolved": "https://package-mirror.liara.ir/repository/npm/next/-/next-16.2.2.tgz", + "integrity": "sha512-i6AJdyVa4oQjyvX/6GeER8dpY/xlIV+4NMv/svykcLtURJSy/WzDnnUk/TM4d0uewFHK7xSQz4TbIwPgjky+3A==", + "license": "MIT", + "dependencies": { + "@next/env": "16.2.2", + "@swc/helpers": "0.5.15", + "baseline-browser-mapping": "^2.9.19", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.4.31", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=20.9.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "16.2.2", + "@next/swc-darwin-x64": "16.2.2", + "@next/swc-linux-arm64-gnu": "16.2.2", + "@next/swc-linux-arm64-musl": "16.2.2", + "@next/swc-linux-x64-gnu": "16.2.2", + "@next/swc-linux-x64-musl": "16.2.2", + "@next/swc-win32-arm64-msvc": "16.2.2", + "@next/swc-win32-x64-msvc": "16.2.2", + "sharp": "^0.34.5" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.51.1", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/next-auth": { + "version": "4.24.13", + "resolved": "https://package-mirror.liara.ir/repository/npm/next-auth/-/next-auth-4.24.13.tgz", + "integrity": "sha512-sgObCfcfL7BzIK76SS5TnQtc3yo2Oifp/yIpfv6fMfeBOiBJkDWF3A2y9+yqnmJ4JKc2C+nMjSjmgDeTwgN1rQ==", + "license": "ISC", + "dependencies": { + "@babel/runtime": "^7.20.13", + "@panva/hkdf": "^1.0.2", + "cookie": "^0.7.0", + "jose": "^4.15.5", + "oauth": "^0.9.15", + "openid-client": "^5.4.0", + "preact": "^10.6.3", + "preact-render-to-string": "^5.1.19", + "uuid": "^8.3.2" + }, + "peerDependencies": { + "@auth/core": "0.34.3", + "next": "^12.2.5 || ^13 || ^14 || ^15 || ^16", + "nodemailer": "^7.0.7", + "react": "^17.0.2 || ^18 || ^19", + "react-dom": "^17.0.2 || ^18 || ^19" + }, + "peerDependenciesMeta": { + "@auth/core": { + "optional": true + }, + "nodemailer": { + "optional": true + } + } + }, + "node_modules/node-fetch-native": { + "version": "1.6.7", + "resolved": "https://package-mirror.liara.ir/repository/npm/node-fetch-native/-/node-fetch-native-1.6.7.tgz", + "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.37", + "resolved": "https://package-mirror.liara.ir/repository/npm/node-releases/-/node-releases-2.0.37.tgz", + "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==", + "license": "MIT" + }, + "node_modules/nypm": { + "version": "0.6.5", + "resolved": "https://package-mirror.liara.ir/repository/npm/nypm/-/nypm-0.6.5.tgz", + "integrity": "sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "citty": "^0.2.0", + "pathe": "^2.0.3", + "tinyexec": "^1.0.2" + }, + "bin": { + "nypm": "dist/cli.mjs" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/nypm/node_modules/citty": { + "version": "0.2.2", + "resolved": "https://package-mirror.liara.ir/repository/npm/citty/-/citty-0.2.2.tgz", + "integrity": "sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/oauth": { + "version": "0.9.15", + "resolved": "https://package-mirror.liara.ir/repository/npm/oauth/-/oauth-0.9.15.tgz", + "integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==", + "license": "MIT" + }, + "node_modules/object-hash": { + "version": "2.2.0", + "resolved": "https://package-mirror.liara.ir/repository/npm/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://package-mirror.liara.ir/repository/npm/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/oidc-token-hash": { + "version": "5.2.0", + "resolved": "https://package-mirror.liara.ir/repository/npm/oidc-token-hash/-/oidc-token-hash-5.2.0.tgz", + "integrity": "sha512-6gj2m8cJZ+iSW8bm0FXdGF0YhIQbKrfP4yWTNzxc31U6MOjfEmB1rHvlYvxI1B7t7BCi1F2vYTT6YhtQRG4hxw==", + "license": "MIT", + "engines": { + "node": "^10.13.0 || >=12.0.0" + } + }, + "node_modules/openid-client": { + "version": "5.7.1", + "resolved": "https://package-mirror.liara.ir/repository/npm/openid-client/-/openid-client-5.7.1.tgz", + "integrity": "sha512-jDBPgSVfTnkIh71Hg9pRvtJc6wTwqjRkN88+gCFtYWrlP4Yx2Dsrow8uPi3qLr/aeymPF3o2+dS+wOpglK04ew==", + "license": "MIT", + "dependencies": { + "jose": "^4.15.9", + "lru-cache": "^6.0.0", + "object-hash": "^2.2.0", + "oidc-token-hash": "^5.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://package-mirror.liara.ir/repository/npm/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://package-mirror.liara.ir/repository/npm/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/pg": { + "version": "8.20.0", + "resolved": "https://package-mirror.liara.ir/repository/npm/pg/-/pg-8.20.0.tgz", + "integrity": "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==", + "license": "MIT", + "dependencies": { + "pg-connection-string": "^2.12.0", + "pg-pool": "^3.13.0", + "pg-protocol": "^1.13.0", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.3.0" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.3.0", + "resolved": "https://package-mirror.liara.ir/repository/npm/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz", + "integrity": "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==", + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.12.0", + "resolved": "https://package-mirror.liara.ir/repository/npm/pg-connection-string/-/pg-connection-string-2.12.0.tgz", + "integrity": "sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ==", + "license": "MIT" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://package-mirror.liara.ir/repository/npm/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.13.0", + "resolved": "https://package-mirror.liara.ir/repository/npm/pg-pool/-/pg-pool-3.13.0.tgz", + "integrity": "sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA==", + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.13.0", + "resolved": "https://package-mirror.liara.ir/repository/npm/pg-protocol/-/pg-protocol-1.13.0.tgz", + "integrity": "sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://package-mirror.liara.ir/repository/npm/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pg-types/node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://package-mirror.liara.ir/repository/npm/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://package-mirror.liara.ir/repository/npm/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "license": "MIT", + "dependencies": { + "split2": "^4.1.0" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://package-mirror.liara.ir/repository/npm/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/pkg-types": { + "version": "2.3.0", + "resolved": "https://package-mirror.liara.ir/repository/npm/pkg-types/-/pkg-types-2.3.0.tgz", + "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.2.2", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" + } + }, + "node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://package-mirror.liara.ir/repository/npm/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://package-mirror.liara.ir/repository/npm/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, + "node_modules/postgres-array": { + "version": "3.0.4", + "resolved": "https://package-mirror.liara.ir/repository/npm/postgres-array/-/postgres-array-3.0.4.tgz", + "integrity": "sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.1", + "resolved": "https://package-mirror.liara.ir/repository/npm/postgres-bytea/-/postgres-bytea-1.0.1.tgz", + "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://package-mirror.liara.ir/repository/npm/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://package-mirror.liara.ir/repository/npm/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/preact": { + "version": "10.24.3", + "resolved": "https://package-mirror.liara.ir/repository/npm/preact/-/preact-10.24.3.tgz", + "integrity": "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/preact-render-to-string": { + "version": "5.2.6", + "resolved": "https://package-mirror.liara.ir/repository/npm/preact-render-to-string/-/preact-render-to-string-5.2.6.tgz", + "integrity": "sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==", + "license": "MIT", + "dependencies": { + "pretty-format": "^3.8.0" + }, + "peerDependencies": { + "preact": ">=10" + } + }, + "node_modules/pretty-format": { + "version": "3.8.0", + "resolved": "https://package-mirror.liara.ir/repository/npm/pretty-format/-/pretty-format-3.8.0.tgz", + "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==", + "license": "MIT" + }, + "node_modules/prisma": { + "version": "6.19.3", + "resolved": "https://package-mirror.liara.ir/repository/npm/prisma/-/prisma-6.19.3.tgz", + "integrity": "sha512-++ZJ0ijLrDJF6hNB4t4uxg2br3fC4H9Yc9tcbjr2fcNFP3rh/SBNrAgjhsqBU4Ght8JPrVofG/ZkXfnSfnYsFg==", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/config": "6.19.3", + "@prisma/engines": "6.19.3" + }, + "bin": { + "prisma": "build/index.js" + }, + "engines": { + "node": ">=18.18" + }, + "peerDependencies": { + "typescript": ">=5.1.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://package-mirror.liara.ir/repository/npm/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "devOptional": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/rc9": { + "version": "2.1.2", + "resolved": "https://package-mirror.liara.ir/repository/npm/rc9/-/rc9-2.1.2.tgz", + "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "defu": "^6.1.4", + "destr": "^2.0.3" + } + }, + "node_modules/react": { + "version": "19.2.4", + "resolved": "https://package-mirror.liara.ir/repository/npm/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.4", + "resolved": "https://package-mirror.liara.ir/repository/npm/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.4" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://package-mirror.liara.ir/repository/npm/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://package-mirror.liara.ir/repository/npm/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://package-mirror.liara.ir/repository/npm/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://package-mirror.liara.ir/repository/npm/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://package-mirror.liara.ir/repository/npm/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://package-mirror.liara.ir/repository/npm/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.6", + "resolved": "https://package-mirror.liara.ir/repository/npm/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/tailwindcss": { + "version": "4.2.2", + "resolved": "https://package-mirror.liara.ir/repository/npm/tailwindcss/-/tailwindcss-4.2.2.tgz", + "integrity": "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==", + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.2", + "resolved": "https://package-mirror.liara.ir/repository/npm/tapable/-/tapable-2.3.2.tgz", + "integrity": "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tinyexec": { + "version": "1.0.4", + "resolved": "https://package-mirror.liara.ir/repository/npm/tinyexec/-/tinyexec-1.0.4.tgz", + "integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://package-mirror.liara.ir/repository/npm/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://package-mirror.liara.ir/repository/npm/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "6.0.2", + "resolved": "https://package-mirror.liara.ir/repository/npm/typescript/-/typescript-6.0.2.tgz", + "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://package-mirror.liara.ir/repository/npm/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://package-mirror.liara.ir/repository/npm/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://package-mirror.liara.ir/repository/npm/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://package-mirror.liara.ir/repository/npm/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://package-mirror.liara.ir/repository/npm/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://package-mirror.liara.ir/repository/npm/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://package-mirror.liara.ir/repository/npm/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..6145f73 --- /dev/null +++ b/package.json @@ -0,0 +1,34 @@ +{ + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start" + }, + "prisma": { + "seed": "ts-node --compiler-options {\"module\":\"CommonJS\"} prisma/seed.ts" + }, + "dependencies": { + "@auth/prisma-adapter": "^2.11.1", + "@prisma/adapter-pg": "^7.6.0", + "@prisma/client": "^6.19.3", + "@tailwindcss/postcss": "^4.2.2", + "@types/node": "^25.5.2", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "autoprefixer": "^10.4.27", + "bcryptjs": "^3.0.3", + "next": "^16.2.2", + "next-auth": "^4.24.13", + "pg": "^8.20.0", + "react": "^19.2.4", + "react-dom": "^19.2.4", + "tailwindcss": "^4.2.2", + "typescript": "^6.0.2" + }, + "devDependencies": { + "@types/bcryptjs": "^2.4.6", + "@types/pg": "^8.20.0", + "prisma": "^6.19.3", + "ts-node": "^10.9.2" + } +} diff --git a/postcss.config.mjs b/postcss.config.mjs new file mode 100644 index 0000000..61e3684 --- /dev/null +++ b/postcss.config.mjs @@ -0,0 +1,7 @@ +const config = { + plugins: { + "@tailwindcss/postcss": {}, + }, +}; + +export default config; diff --git a/prisma/schema.prisma b/prisma/schema.prisma new file mode 100644 index 0000000..17aa1fc --- /dev/null +++ b/prisma/schema.prisma @@ -0,0 +1,252 @@ +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +enum Role { + USER + ADMIN +} + +enum Position { + GK + DEF + MID + FWD +} + +enum MatchStage { + GROUP + ROUND_OF_16 + QUARTER_FINAL + SEMI_FINAL + THIRD_PLACE + FINAL +} + +enum MatchStatus { + SCHEDULED + LIVE + FINISHED +} + +enum TeamStatus { + ACTIVE + INACTIVE +} + +enum PaymentStatus { + PENDING + SUCCESS + FAILED +} + +enum EventType { + GOAL + ASSIST + YELLOW_CARD + RED_CARD + SECOND_YELLOW + SUBSTITUTION_IN + SUBSTITUTION_OUT + INJURY_NO_SUB + CLEAN_SHEET + PENALTY_SAVED + PENALTY_MISSED + OWN_GOAL + EXTRA_TIME_BONUS + MOTM +} + +model Country { + id String @id @default(cuid()) + name String @unique + code String @unique + flagUrl String? + defaultFormation String @default("4-3-3") + group Group? @relation(fields: [groupId], references: [id]) + groupId String? + isEliminated Boolean @default(false) + players Player[] + homeMatches Match[] @relation("HomeTeam") + awayMatches Match[] @relation("AwayTeam") +} + +model Group { + id String @id @default(cuid()) + name String @unique + countries Country[] +} + +model Player { + id String @id @default(cuid()) + name String + position Position + countryId String + country Country @relation(fields: [countryId], references: [id]) + price Float @default(5.0) + totalPoints Int @default(0) + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + matchStats PlayerMatchStat[] + teamPlayers TeamPlayer[] + events MatchEvent[] +} + +model Match { + id String @id @default(cuid()) + homeTeamId String + awayTeamId String + homeTeam Country @relation("HomeTeam", fields: [homeTeamId], references: [id]) + awayTeam Country @relation("AwayTeam", fields: [awayTeamId], references: [id]) + homeScore Int? + awayScore Int? + stage MatchStage @default(GROUP) + status MatchStatus @default(SCHEDULED) + matchDate DateTime + roundId String? + round Round? @relation(fields: [roundId], references: [id]) + playerStats PlayerMatchStat[] + events MatchEvent[] + lineups MatchLineup[] + createdAt DateTime @default(now()) +} + +model Round { + id String @id @default(cuid()) + number Int @unique + name String + isActive Boolean @default(false) + deadline DateTime + matches Match[] + createdAt DateTime @default(now()) +} + +model MatchEvent { + id String @id @default(cuid()) + matchId String + playerId String + match Match @relation(fields: [matchId], references: [id], onDelete: Cascade) + player Player @relation(fields: [playerId], references: [id], onDelete: Cascade) + type EventType + minute Int? + extraInfo String? + createdAt DateTime @default(now()) +} + +model MatchLineup { + id String @id @default(cuid()) + matchId String + countryId String + match Match @relation(fields: [matchId], references: [id], onDelete: Cascade) + formation String + playerIds String[] +} + +model PlayerMatchStat { + id String @id @default(cuid()) + playerId String + matchId String + player Player @relation(fields: [playerId], references: [id], onDelete: Cascade) + match Match @relation(fields: [matchId], references: [id], onDelete: Cascade) + goals Int @default(0) + assists Int @default(0) + yellowCards Int @default(0) + redCards Int @default(0) + minutesPlayed Int @default(0) + cleanSheet Boolean @default(false) + penaltySaved Int @default(0) + penaltyMissed Int @default(0) + ownGoals Int @default(0) + isMotm Boolean @default(false) + extraTimeBonus Int @default(0) + points Int @default(0) + + @@unique([playerId, matchId]) +} + +model ScoringRule { + id String @id @default(cuid()) + position Position + eventType EventType + points Int + updatedAt DateTime @updatedAt + updatedBy String? + + @@unique([position, eventType]) +} + +model User { + id String @id @default(cuid()) + name String? + email String @unique + password String + role Role @default(USER) + createdAt DateTime @default(now()) + team Team? + sessions Session[] + payments Payment[] +} + +model Session { + id String @id @default(cuid()) + sessionToken String @unique + userId String + expires DateTime + user User @relation(fields: [userId], references: [id], onDelete: Cascade) +} + +model Team { + id String @id @default(cuid()) + name String + userId String @unique + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + budget Float @default(100.0) + totalPoints Int @default(0) + formation String @default("4-3-3") + status TeamStatus @default(INACTIVE) + createdAt DateTime @default(now()) + players TeamPlayer[] +} + +model TeamPlayer { + teamId String + playerId String + isCaptain Boolean @default(false) + isViceCaptain Boolean @default(false) + isBench Boolean @default(false) + positionIndex Int @default(0) + team Team @relation(fields: [teamId], references: [id], onDelete: Cascade) + player Player @relation(fields: [playerId], references: [id], onDelete: Cascade) + + @@id([teamId, playerId]) +} + +model Package { + id String @id @default(cuid()) + name String + budgetBonus Float + price Int + description String? + isActive Boolean @default(true) + payments Payment[] +} + +model Payment { + id String @id @default(cuid()) + userId String + packageId String + user User @relation(fields: [userId], references: [id]) + package Package @relation(fields: [packageId], references: [id]) + amount Int + authority String? @unique + refId String? + status PaymentStatus @default(PENDING) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} diff --git a/prisma/seed.ts b/prisma/seed.ts new file mode 100644 index 0000000..a9f6ad2 --- /dev/null +++ b/prisma/seed.ts @@ -0,0 +1,235 @@ +import { PrismaClient } from "@prisma/client"; +import bcrypt from "bcryptjs"; + +// DEFAULT_RULES رو مستقیم اینجا تعریف می‌کنیم تا از @/ alias استفاده نکنیم +const DEFAULT_RULES = { + GK: { GOAL: 10, ASSIST: 3, YELLOW_CARD: -1, RED_CARD: -3, SECOND_YELLOW: -3, CLEAN_SHEET: 6, PENALTY_SAVED: 5, PENALTY_MISSED: -2, OWN_GOAL: -2, MOTM: 3, EXTRA_TIME_BONUS: 1, INJURY_NO_SUB: -1 }, + DEF: { GOAL: 8, ASSIST: 3, YELLOW_CARD: -1, RED_CARD: -3, SECOND_YELLOW: -3, CLEAN_SHEET: 4, PENALTY_SAVED: 0, PENALTY_MISSED: -2, OWN_GOAL: -2, MOTM: 3, EXTRA_TIME_BONUS: 1, INJURY_NO_SUB: -1 }, + MID: { GOAL: 5, ASSIST: 3, YELLOW_CARD: -2, RED_CARD: -3, SECOND_YELLOW: -3, CLEAN_SHEET: 1, PENALTY_SAVED: 0, PENALTY_MISSED: -2, OWN_GOAL: -2, MOTM: 3, EXTRA_TIME_BONUS: 1, INJURY_NO_SUB: -1 }, + FWD: { GOAL: 4, ASSIST: 3, YELLOW_CARD: -1, RED_CARD: -3, SECOND_YELLOW: -3, CLEAN_SHEET: 0, PENALTY_SAVED: 0, PENALTY_MISSED: -2, OWN_GOAL: -2, MOTM: 3, EXTRA_TIME_BONUS: 1, INJURY_NO_SUB: -1 }, +} as const; + +const prisma = new PrismaClient(); + +// ترکیب پیش‌فرض تیم‌های ملی معروف (بر اساس آخرین اطلاعات) +const COUNTRY_FORMATIONS: Record = { + BRA: "4-3-3", FRA: "4-3-3", ARG: "4-3-3", ENG: "4-3-3", + ESP: "4-3-3", GER: "4-2-3-1", POR: "4-3-3", NED: "4-3-3", + BEL: "4-3-3", CRO: "4-3-3", MAR: "4-3-3", IRN: "4-5-1", + URU: "4-3-3", SEN: "4-3-3", KOR: "4-3-3", JPN: "4-3-3", + MEX: "4-3-3", USA: "4-3-3", CAN: "4-3-3", AUS: "4-3-3", + POL: "4-3-3", DEN: "4-3-3", SUI: "4-2-3-1", SRB: "3-4-3", + WAL: "5-3-2", TUN: "4-3-3", CMR: "4-3-3", GHA: "4-2-3-1", + ECU: "4-3-3", QAT: "5-3-2", KSA: "4-3-3", CRC: "5-4-1", +}; + +const COUNTRIES = [ + { name: "برزیل", code: "BRA", flag: "🇧🇷", group: "G" }, + { name: "سربیا", code: "SRB", flag: "🇷🇸", group: "G" }, + { name: "سوئیس", code: "SUI", flag: "🇨🇭", group: "G" }, + { name: "کامرون", code: "CMR", flag: "🇨🇲", group: "G" }, + { name: "فرانسه", code: "FRA", flag: "🇫🇷", group: "D" }, + { name: "استرالیا", code: "AUS", flag: "🇦🇺", group: "D" }, + { name: "دانمارک", code: "DEN", flag: "🇩🇰", group: "D" }, + { name: "تونس", code: "TUN", flag: "🇹🇳", group: "D" }, + { name: "آرژانتین", code: "ARG", flag: "🇦🇷", group: "C" }, + { name: "عربستان", code: "KSA", flag: "🇸🇦", group: "C" }, + { name: "مکزیک", code: "MEX", flag: "🇲🇽", group: "C" }, + { name: "لهستان", code: "POL", flag: "🇵🇱", group: "C" }, + { name: "ایران", code: "IRN", flag: "🇮🇷", group: "B" }, + { name: "انگلیس", code: "ENG", flag: "🏴󠁧󠁢󠁥󠁮󠁧󠁿", group: "B" }, + { name: "آمریکا", code: "USA", flag: "🇺🇸", group: "B" }, + { name: "ولز", code: "WAL", flag: "🏴󠁧󠁢󠁷󠁬󠁳󠁿", group: "B" }, + { name: "آلمان", code: "GER", flag: "🇩🇪", group: "E" }, + { name: "ژاپن", code: "JPN", flag: "🇯🇵", group: "E" }, + { name: "اسپانیا", code: "ESP", flag: "🇪🇸", group: "E" }, + { name: "کاستاریکا", code: "CRC", flag: "🇨🇷", group: "E" }, + { name: "بلژیک", code: "BEL", flag: "🇧🇪", group: "F" }, + { name: "کانادا", code: "CAN", flag: "🇨🇦", group: "F" }, + { name: "مراکش", code: "MAR", flag: "🇲🇦", group: "F" }, + { name: "کرواسی", code: "CRO", flag: "🇭🇷", group: "F" }, + { name: "پرتغال", code: "POR", flag: "🇵🇹", group: "H" }, + { name: "غنا", code: "GHA", flag: "🇬🇭", group: "H" }, + { name: "اروگوئه", code: "URU", flag: "🇺🇾", group: "H" }, + { name: "کره جنوبی", code: "KOR", flag: "🇰🇷", group: "H" }, + { name: "هلند", code: "NED", flag: "🇳🇱", group: "A" }, + { name: "سنگال", code: "SEN", flag: "🇸🇳", group: "A" }, + { name: "اکوادور", code: "ECU", flag: "🇪🇨", group: "A" }, + { name: "قطر", code: "QAT", flag: "🇶🇦", group: "A" }, +]; + +const PLAYERS_DATA = [ + { name: "آلیسون بکر", pos: "GK", code: "BRA", price: 6.0, pts: 42 }, + { name: "تیاگو سیلوا", pos: "DEF", code: "BRA", price: 6.5, pts: 38 }, + { name: "مارکینیوس", pos: "DEF", code: "BRA", price: 6.0, pts: 35 }, + { name: "کاسمیرو", pos: "MID", code: "BRA", price: 8.0, pts: 52 }, + { name: "نیمار", pos: "FWD", code: "BRA", price: 11.5, pts: 68 }, + { name: "وینیسیوس جونیور", pos: "FWD", code: "BRA", price: 10.5, pts: 72 }, + { name: "ریچارلیسون", pos: "FWD", code: "BRA", price: 8.5, pts: 55 }, + { name: "هوگو لوریس", pos: "GK", code: "FRA", price: 6.0, pts: 40 }, + { name: "رافائل واران", pos: "DEF", code: "FRA", price: 6.5, pts: 36 }, + { name: "کیلیان امباپه", pos: "FWD", code: "FRA", price: 12.5, pts: 88 }, + { name: "آنتوان گریزمان", pos: "MID", code: "FRA", price: 9.5, pts: 62 }, + { name: "اولیویه ژیرو", pos: "FWD", code: "FRA", price: 7.5, pts: 48 }, + { name: "اوسمان دمبله", pos: "FWD", code: "FRA", price: 8.0, pts: 44 }, + { name: "امیلیانو مارتینز", pos: "GK", code: "ARG", price: 6.5, pts: 55 }, + { name: "لیونل مسی", pos: "FWD", code: "ARG", price: 12.5, pts: 92 }, + { name: "خولیان آلوارز", pos: "FWD", code: "ARG", price: 8.5, pts: 60 }, + { name: "رودریگو دپاول", pos: "MID", code: "ARG", price: 7.5, pts: 45 }, + { name: "نیکولاس اوتامندی", pos: "DEF", code: "ARG", price: 5.5, pts: 32 }, + { name: "جوردن پیکفورد", pos: "GK", code: "ENG", price: 5.5, pts: 38 }, + { name: "جود بلینگهام", pos: "MID", code: "ENG", price: 10.5, pts: 75 }, + { name: "هری کین", pos: "FWD", code: "ENG", price: 11.0, pts: 70 }, + { name: "بوکایو ساکا", pos: "MID", code: "ENG", price: 8.5, pts: 58 }, + { name: "فیل فودن", pos: "MID", code: "ENG", price: 9.0, pts: 62 }, + { name: "جان استونز", pos: "DEF", code: "ENG", price: 6.0, pts: 34 }, + { name: "اونای سیمون", pos: "GK", code: "ESP", price: 5.5, pts: 36 }, + { name: "پدری", pos: "MID", code: "ESP", price: 9.0, pts: 60 }, + { name: "گاوی", pos: "MID", code: "ESP", price: 8.5, pts: 55 }, + { name: "آلوارو موراتا", pos: "FWD", code: "ESP", price: 7.5, pts: 46 }, + { name: "دنی اولمو", pos: "MID", code: "ESP", price: 7.0, pts: 42 }, + { name: "مانوئل نویر", pos: "GK", code: "GER", price: 6.0, pts: 38 }, + { name: "توماس مولر", pos: "MID", code: "GER", price: 8.0, pts: 50 }, + { name: "کای هاورتز", pos: "MID", code: "GER", price: 8.5, pts: 52 }, + { name: "یامله موسیالا", pos: "MID", code: "GER", price: 9.0, pts: 65 }, + { name: "آنتونیو رودیگر", pos: "DEF", code: "GER", price: 5.5, pts: 30 }, + { name: "دیوگو کوستا", pos: "GK", code: "POR", price: 5.5, pts: 35 }, + { name: "کریستیانو رونالدو", pos: "FWD", code: "POR", price: 11.0, pts: 65 }, + { name: "برونو فرناندز", pos: "MID", code: "POR", price: 10.0, pts: 70 }, + { name: "رافائل لئائو", pos: "FWD", code: "POR", price: 8.5, pts: 55 }, + { name: "روبن دیاز", pos: "DEF", code: "POR", price: 6.0, pts: 36 }, + { name: "علیرضا بیرانوند", pos: "GK", code: "IRN", price: 5.0, pts: 28 }, + { name: "مهدی طارمی", pos: "FWD", code: "IRN", price: 8.0, pts: 50 }, + { name: "سردار آزمون", pos: "FWD", code: "IRN", price: 7.5, pts: 44 }, + { name: "علی کریمی", pos: "MID", code: "IRN", price: 6.0, pts: 32 }, + { name: "رامین رضاییان", pos: "DEF", code: "IRN", price: 5.0, pts: 22 }, + { name: "یاسین بونو", pos: "GK", code: "MAR", price: 6.5, pts: 58 }, + { name: "اشرف حکیمی", pos: "DEF", code: "MAR", price: 8.0, pts: 62 }, + { name: "حکیم زیاش", pos: "MID", code: "MAR", price: 7.5, pts: 48 }, + { name: "یوسف النصیری", pos: "FWD", code: "MAR", price: 7.0, pts: 45 }, + { name: "دومینیک لیواکوویچ", pos: "GK", code: "CRO", price: 6.0, pts: 50 }, + { name: "لوکا مودریچ", pos: "MID", code: "CRO", price: 9.5, pts: 68 }, + { name: "ایوان پریشیچ", pos: "MID", code: "CRO", price: 7.5, pts: 48 }, + { name: "آندره کراماریچ", pos: "FWD", code: "CRO", price: 7.0, pts: 44 }, + { name: "آندریس نوپرت", pos: "GK", code: "NED", price: 5.5, pts: 36 }, + { name: "ویرخیل فان دایک", pos: "DEF", code: "NED", price: 7.0, pts: 48 }, + { name: "دنزل دامفریس", pos: "DEF", code: "NED", price: 7.5, pts: 52 }, + { name: "کودی گاکپو", pos: "FWD", code: "NED", price: 8.0, pts: 58 }, + { name: "تیبو کورتوا", pos: "GK", code: "BEL", price: 6.5, pts: 44 }, + { name: "کوین دبروینه", pos: "MID", code: "BEL", price: 11.0, pts: 72 }, + { name: "رومله لوکاکو", pos: "FWD", code: "BEL", price: 9.5, pts: 55 }, +]; + +async function main() { + console.log("🌱 Seeding..."); + + // ─── ادمین و کاربران ───────────────────────────────── + const adminPwd = await bcrypt.hash("admin123", 10); + await prisma.user.upsert({ where: { email: "admin@worldcup.com" }, update: {}, + create: { email: "admin@worldcup.com", name: "ادمین", password: adminPwd, role: "ADMIN" } }); + + const userPwd = await bcrypt.hash("user123", 10); + const users = []; + for (const u of [{ email: "ali@test.com", name: "علی احمدی" }, { email: "sara@test.com", name: "سارا رضایی" }, { email: "reza@test.com", name: "رضا محمدی" }]) { + const user = await prisma.user.upsert({ where: { email: u.email }, update: {}, create: { ...u, password: userPwd } }); + users.push(user); + } + + // ─── گروه‌ها ────────────────────────────────────────── + const groupMap: Record = {}; + for (const name of ["A","B","C","D","E","F","G","H"]) { + const g = await prisma.group.upsert({ where: { name }, update: {}, create: { name } }); + groupMap[name] = g.id; + } + + // ─── تیم‌های ملی ───────────────────────────────────── + const countryMap: Record = {}; + for (const c of COUNTRIES) { + const country = await prisma.country.upsert({ where: { code: c.code }, update: { flagUrl: c.flag, defaultFormation: COUNTRY_FORMATIONS[c.code] ?? "4-3-3" }, + create: { name: c.name, code: c.code, flagUrl: c.flag, groupId: groupMap[c.group], defaultFormation: COUNTRY_FORMATIONS[c.code] ?? "4-3-3" } }); + countryMap[c.code] = country.id; + } + + // ─── بازیکنان ───────────────────────────────────────── + for (const p of PLAYERS_DATA) { + const countryId = countryMap[p.code]; + if (!countryId) continue; + await prisma.player.create({ data: { name: p.name, position: p.pos as any, countryId, price: p.price, totalPoints: p.pts } }); + } + + // ─── قوانین امتیازدهی پیش‌فرض ──────────────────────── + const positions = ["GK", "DEF", "MID", "FWD"] as const; + for (const pos of positions) { + const rules = DEFAULT_RULES[pos]; + for (const [eventType, points] of Object.entries(rules)) { + await prisma.scoringRule.upsert({ + where: { position_eventType: { position: pos, eventType: eventType as any } }, + update: { points: points as number }, + create: { position: pos, eventType: eventType as any, points: points as number }, + }); + } + } + + // ─── دورها ──────────────────────────────────────────── + const round1 = await prisma.round.upsert({ where: { number: 1 }, update: {}, + create: { number: 1, name: "دور اول - مرحله گروهی", isActive: true, deadline: new Date("2026-06-15T10:00:00Z") } }); + const round2 = await prisma.round.upsert({ where: { number: 2 }, update: {}, + create: { number: 2, name: "دور دوم - مرحله گروهی", deadline: new Date("2026-06-22T10:00:00Z") } }); + const round3 = await prisma.round.upsert({ where: { number: 3 }, update: {}, + create: { number: 3, name: "دور سوم - مرحله گروهی", deadline: new Date("2026-06-29T10:00:00Z") } }); + const round4 = await prisma.round.upsert({ where: { number: 4 }, update: {}, + create: { number: 4, name: "دور چهارم - یک‌هشتم نهایی", deadline: new Date("2026-07-05T10:00:00Z") } }); + + // ─── بازی‌ها ────────────────────────────────────────── + const matchesData = [ + // دور اول - بازی اول هر تیم + { home: "QAT", away: "ECU", hS: 0, aS: 2, st: "FINISHED", date: "2026-06-20T16:00:00Z", rId: round1.id }, + { home: "ENG", away: "IRN", hS: 6, aS: 2, st: "FINISHED", date: "2026-06-21T13:00:00Z", rId: round1.id }, + { home: "ARG", away: "KSA", hS: 1, aS: 2, st: "FINISHED", date: "2026-06-22T10:00:00Z", rId: round1.id }, + { home: "FRA", away: "AUS", hS: 4, aS: 1, st: "FINISHED", date: "2026-06-22T19:00:00Z", rId: round1.id }, + { home: "GER", away: "JPN", hS: 1, aS: 2, st: "FINISHED", date: "2026-06-23T13:00:00Z", rId: round1.id }, + { home: "ESP", away: "CRC", hS: 7, aS: 0, st: "FINISHED", date: "2026-06-23T19:00:00Z", rId: round1.id }, + { home: "BEL", away: "CAN", hS: 1, aS: 0, st: "FINISHED", date: "2026-06-23T16:00:00Z", rId: round1.id }, + { home: "BRA", away: "SRB", hS: 2, aS: 0, st: "FINISHED", date: "2026-06-24T19:00:00Z", rId: round1.id }, + { home: "POR", away: "GHA", hS: 3, aS: 2, st: "FINISHED", date: "2026-06-24T16:00:00Z", rId: round1.id }, + { home: "MAR", away: "CRO", hS: 0, aS: 0, st: "FINISHED", date: "2026-06-23T10:00:00Z", rId: round1.id }, + { home: "NED", away: "SEN", hS: 2, aS: 0, st: "FINISHED", date: "2026-06-21T16:00:00Z", rId: round1.id }, + // دور دوم - بازی دوم هر تیم + { home: "IRN", away: "WAL", hS: 2, aS: 0, st: "FINISHED", date: "2026-06-25T13:00:00Z", rId: round2.id }, + { home: "FRA", away: "DEN", hS: 2, aS: 1, st: "FINISHED", date: "2026-06-26T19:00:00Z", rId: round2.id }, + { home: "ARG", away: "MEX", hS: 2, aS: 0, st: "FINISHED", date: "2026-06-26T22:00:00Z", rId: round2.id }, + { home: "BRA", away: "SUI", hS: 1, aS: 0, st: "FINISHED", date: "2026-06-28T19:00:00Z", rId: round2.id }, + { home: "ENG", away: "USA", hS: 0, aS: 0, st: "FINISHED", date: "2026-06-25T19:00:00Z", rId: round2.id }, + // دور سوم - بازی سوم گروهی + { home: "BRA", away: "CMR", hS: 1, aS: 0, st: "SCHEDULED", date: "2026-07-02T19:00:00Z", rId: round3.id }, + { home: "FRA", away: "TUN", hS: null, aS: null, st: "SCHEDULED", date: "2026-07-01T16:00:00Z", rId: round3.id }, + // دور چهارم - یک‌هشتم + { home: "NED", away: "ARG", hS: 2, aS: 2, st: "FINISHED", date: "2026-12-09T19:00:00Z", rId: round4.id, stage: "ROUND_OF_16" }, + { home: "FRA", away: "ENG", hS: 2, aS: 1, st: "FINISHED", date: "2026-12-10T19:00:00Z", rId: round4.id, stage: "ROUND_OF_16" }, + { home: "ARG", away: "FRA", hS: 3, aS: 3, st: "FINISHED", date: "2026-12-18T15:00:00Z", rId: round4.id, stage: "FINAL" }, + ]; + + for (const m of matchesData) { + const homeId = countryMap[m.home], awayId = countryMap[m.away]; + if (!homeId || !awayId) continue; + await prisma.match.create({ data: { + homeTeamId: homeId, awayTeamId: awayId, + homeScore: m.hS ?? null, awayScore: m.aS ?? null, + status: m.st as any, stage: (m.stage ?? "GROUP") as any, + matchDate: new Date(m.date), roundId: m.rId, + }}); + } + + // ─── پکیج‌ها ────────────────────────────────────────── + for (const pkg of [ + { id: "pkg-silver", name: "پکیج نقره‌ای", budgetBonus: 10, price: 50000, description: "۱۰ میلیون به بودجه اضافه کن" }, + { id: "pkg-gold", name: "پکیج طلایی", budgetBonus: 20, price: 90000, description: "۲۰ میلیون به بودجه اضافه کن" }, + { id: "pkg-diamond", name: "پکیج الماس", budgetBonus: 30, price: 120000, description: "۳۰ میلیون به بودجه اضافه کن" }, + ]) { + await prisma.package.upsert({ where: { id: pkg.id }, update: {}, create: pkg }); + } + + console.log("✅ Seed done! admin@worldcup.com / admin123 | ali@test.com / user123"); +} + +main().catch(console.error).finally(() => prisma.$disconnect()); diff --git a/proxy.ts b/proxy.ts new file mode 100644 index 0000000..3fc6d70 --- /dev/null +++ b/proxy.ts @@ -0,0 +1,21 @@ +import { withAuth } from "next-auth/middleware"; +import { NextResponse } from "next/server"; + +export default withAuth( + function middleware(req) { + const token = req.nextauth.token; + const isAdminRoute = req.nextUrl.pathname.startsWith("/admin"); + if (isAdminRoute && token?.role !== "ADMIN") { + return NextResponse.redirect(new URL("/", req.url)); + } + }, + { + callbacks: { + authorized: ({ token }) => !!token, + }, + } +); + +export const config = { + matcher: ["/admin/:path*", "/team/:path*"], +}; diff --git a/public/fonts/lahze.woff b/public/fonts/lahze.woff new file mode 100644 index 0000000000000000000000000000000000000000..aca5d8cfbcda3ed3901c6419d1b3e12e6379e6ab GIT binary patch literal 48696 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 literal 0 HcmV?d00001 diff --git a/public/fonts/lahze.woff2 b/public/fonts/lahze.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..d04cc2846931ee6ddc5d57931e41704bcf8e8ec5 GIT binary patch literal 37744 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