fix:admin & user match table sort #98
@@ -13,17 +13,26 @@ class MatchController extends Controller
|
|||||||
{
|
{
|
||||||
public function index(Request $request)
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
$query = MatchGame::query()->with(['winner', 'player1', 'player2']);
|
$query = MatchGame::query()->with(["winner", "player1", "player2"]);
|
||||||
|
|
||||||
if ($request->has('type')) {
|
if ($request->has("type")) {
|
||||||
$query->where('type', $request->type);
|
$query->where("type", $request->type);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->has('status')) {
|
if ($request->has("status")) {
|
||||||
$query->where('status', $request->status);
|
$query->where("status", $request->status);
|
||||||
}
|
}
|
||||||
|
|
||||||
$query->orderBy('began_at', 'desc');
|
if ($request->has("pot") && in_array($request->pot, ["asc", "desc"])) {
|
||||||
|
$query->orderBy("stake", $request->pot);
|
||||||
|
} elseif (
|
||||||
|
$request->has("sort_direction") &&
|
||||||
|
in_array($request->sort_direction, ["asc", "desc"])
|
||||||
|
) {
|
||||||
|
$query->orderBy("began_at", $request->sort_direction);
|
||||||
|
} else {
|
||||||
|
$query->orderBy("began_at", "desc");
|
||||||
|
}
|
||||||
|
|
||||||
return response()->json($query->paginate(15));
|
return response()->json($query->paginate(15));
|
||||||
}
|
}
|
||||||
@@ -32,63 +41,78 @@ class MatchController extends Controller
|
|||||||
{
|
{
|
||||||
$validated = $request->validated();
|
$validated = $request->validated();
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
$stake = $validated['stake'];
|
$stake = $validated["stake"];
|
||||||
|
|
||||||
if ($user->coins_balance < $stake) {
|
if ($user->coins_balance < $stake) {
|
||||||
return response()->json(['message' => 'Insufficient coin balance'], 400);
|
return response()->json(
|
||||||
|
["message" => "Insufficient coin balance"],
|
||||||
|
400,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$activeMatch = MatchGame::where(function ($q) use ($user) {
|
$activeMatch = MatchGame::where(function ($q) use ($user) {
|
||||||
$q->where('player1_user_id', $user->id)
|
$q->where("player1_user_id", $user->id)->orWhere(
|
||||||
->orWhere('player2_user_id', $user->id);
|
"player2_user_id",
|
||||||
})->whereIn('status', ['Pending', 'Playing'])->exists();
|
$user->id,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
->whereIn("status", ["Pending", "Playing"])
|
||||||
|
->exists();
|
||||||
|
|
||||||
if ($activeMatch) {
|
if ($activeMatch) {
|
||||||
return response()->json(['message' => 'You already have an active match.'], 400);
|
return response()->json(
|
||||||
|
["message" => "You already have an active match."],
|
||||||
|
400,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return DB::transaction(function () use ($validated, $user, $stake) {
|
return DB::transaction(function () use ($validated, $user, $stake) {
|
||||||
$GHOST_ID = 1;
|
$GHOST_ID = 1;
|
||||||
|
|
||||||
$user->decrement('coins_balance', $stake);
|
$user->decrement("coins_balance", $stake);
|
||||||
|
|
||||||
$stakeType = CoinTransactionType::firstOrCreate(
|
$stakeType = CoinTransactionType::firstOrCreate(
|
||||||
['name' => 'Match stake'],
|
["name" => "Match stake"],
|
||||||
['type' => 'D'] // Debit
|
["type" => "D"], // Debit
|
||||||
);
|
);
|
||||||
|
|
||||||
$match = MatchGame::create([
|
$match = MatchGame::create([
|
||||||
'type' => $validated['type'],
|
"type" => $validated["type"],
|
||||||
'status' => 'Pending',
|
"status" => "Pending",
|
||||||
'player1_user_id' => $user->id,
|
"player1_user_id" => $user->id,
|
||||||
'player2_user_id' => $GHOST_ID,
|
"player2_user_id" => $GHOST_ID,
|
||||||
'winner_user_id' => $GHOST_ID,
|
"winner_user_id" => $GHOST_ID,
|
||||||
'loser_user_id' => $GHOST_ID,
|
"loser_user_id" => $GHOST_ID,
|
||||||
'stake' => $stake,
|
"stake" => $stake,
|
||||||
'began_at' => now(),
|
"began_at" => now(),
|
||||||
'player1_marks' => 0, 'player2_marks' => 0,
|
"player1_marks" => 0,
|
||||||
'player1_points' => 0, 'player2_points' => 0,
|
"player2_marks" => 0,
|
||||||
|
"player1_points" => 0,
|
||||||
|
"player2_points" => 0,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
CoinTransaction::create([
|
CoinTransaction::create([
|
||||||
'user_id' => $user->id,
|
"user_id" => $user->id,
|
||||||
'match_id' => $match->id,
|
"match_id" => $match->id,
|
||||||
'coin_transaction_type_id' => $stakeType->id,
|
"coin_transaction_type_id" => $stakeType->id,
|
||||||
'transaction_datetime' => now(),
|
"transaction_datetime" => now(),
|
||||||
'coins' => -$stake,
|
"coins" => -$stake,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return response()->json([
|
return response()->json(
|
||||||
'message' => 'Match created successfully',
|
[
|
||||||
'match' => $match,
|
"message" => "Match created successfully",
|
||||||
'balance' => $user->coins_balance
|
"match" => $match,
|
||||||
], 201);
|
"balance" => $user->coins_balance,
|
||||||
|
],
|
||||||
|
201,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function show(MatchGame $match)
|
public function show(MatchGame $match)
|
||||||
{
|
{
|
||||||
$match->load(['player1', 'player2', 'winner']);
|
$match->load(["player1", "player2", "winner"]);
|
||||||
return response()->json($match);
|
return response()->json($match);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,98 +121,115 @@ class MatchController extends Controller
|
|||||||
$user = request()->user();
|
$user = request()->user();
|
||||||
|
|
||||||
if ($match->player1_user_id == $user->id) {
|
if ($match->player1_user_id == $user->id) {
|
||||||
return response()->json(['message' => 'You cannot join your own match'], 400);
|
return response()->json(
|
||||||
|
["message" => "You cannot join your own match"],
|
||||||
|
400,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if ($match->status !== 'Pending') {
|
if ($match->status !== "Pending") {
|
||||||
return response()->json(['message' => 'Match is full or started'], 400);
|
return response()->json(
|
||||||
|
["message" => "Match is full or started"],
|
||||||
|
400,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($user->coins_balance < $match->stake) {
|
if ($user->coins_balance < $match->stake) {
|
||||||
return response()->json(['message' => 'Insufficient coin balance to join this match'], 400);
|
return response()->json(
|
||||||
|
["message" => "Insufficient coin balance to join this match"],
|
||||||
|
400,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$activeMatch = MatchGame::where(function ($q) use ($user) {
|
$activeMatch = MatchGame::where(function ($q) use ($user) {
|
||||||
$q->where('player1_user_id', $user->id)
|
$q->where("player1_user_id", $user->id)->orWhere(
|
||||||
->orWhere('player2_user_id', $user->id);
|
"player2_user_id",
|
||||||
})->whereIn('status', ['Pending', 'Playing'])->exists();
|
$user->id,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
->whereIn("status", ["Pending", "Playing"])
|
||||||
|
->exists();
|
||||||
|
|
||||||
if ($activeMatch) {
|
if ($activeMatch) {
|
||||||
return response()->json(['message' => 'You are already in another active match.'], 400);
|
return response()->json(
|
||||||
|
["message" => "You are already in another active match."],
|
||||||
|
400,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return DB::transaction(function () use ($match, $user) {
|
return DB::transaction(function () use ($match, $user) {
|
||||||
$user->decrement('coins_balance', $match->stake);
|
$user->decrement("coins_balance", $match->stake);
|
||||||
|
|
||||||
$stakeType = CoinTransactionType::firstOrCreate(
|
$stakeType = CoinTransactionType::firstOrCreate(
|
||||||
['name' => 'Match stake'],
|
["name" => "Match stake"],
|
||||||
['type' => 'D']
|
["type" => "D"],
|
||||||
);
|
);
|
||||||
|
|
||||||
CoinTransaction::create([
|
CoinTransaction::create([
|
||||||
'user_id' => $user->id,
|
"user_id" => $user->id,
|
||||||
'match_id' => $match->id,
|
"match_id" => $match->id,
|
||||||
'coin_transaction_type_id' => $stakeType->id,
|
"coin_transaction_type_id" => $stakeType->id,
|
||||||
'transaction_datetime' => now(),
|
"transaction_datetime" => now(),
|
||||||
'coins' => -$match->stake,
|
"coins" => -$match->stake,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$match->update([
|
$match->update([
|
||||||
'status' => 'Playing',
|
"status" => "Playing",
|
||||||
'player2_user_id' => $user->id
|
"player2_user_id" => $user->id,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => 'Joined match successfully!',
|
"message" => "Joined match successfully!",
|
||||||
'match' => $match,
|
"match" => $match,
|
||||||
'balance' => $user->coins_balance
|
"balance" => $user->coins_balance,
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function update(Request $request, MatchGame $match)
|
public function update(Request $request, MatchGame $match)
|
||||||
{
|
{
|
||||||
if ($match->status == 'Ended') {
|
if ($match->status == "Ended") {
|
||||||
return response()->json(['message' => 'Match already ended'], 400);
|
return response()->json(["message" => "Match already ended"], 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = $request->validate([
|
$data = $request->validate([
|
||||||
'status' => 'in:Playing,Ended,Interrupted',
|
"status" => "in:Playing,Ended,Interrupted",
|
||||||
'winner_user_id' => 'exists:users,id',
|
"winner_user_id" => "exists:users,id",
|
||||||
'loser_user_id' => 'exists:users,id',
|
"loser_user_id" => "exists:users,id",
|
||||||
'player1_marks' => 'integer',
|
"player1_marks" => "integer",
|
||||||
'player2_marks' => 'integer',
|
"player2_marks" => "integer",
|
||||||
'player1_points' => 'integer',
|
"player1_points" => "integer",
|
||||||
'player2_points' => 'integer',
|
"player2_points" => "integer",
|
||||||
'total_time' => 'numeric'
|
"total_time" => "numeric",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return DB::transaction(function () use ($match, $data) {
|
return DB::transaction(function () use ($match, $data) {
|
||||||
|
if (isset($data["status"]) && $data["status"] === "Ended") {
|
||||||
if (isset($data['status']) && $data['status'] === 'Ended') {
|
$data["ended_at"] = now();
|
||||||
$data['ended_at'] = now();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$match->update($data);
|
$match->update($data);
|
||||||
|
|
||||||
if ($match->status === 'Ended' && $match->stake > 0 && $match->winner_user_id) {
|
if (
|
||||||
|
$match->status === "Ended" &&
|
||||||
|
$match->stake > 0 &&
|
||||||
|
$match->winner_user_id
|
||||||
|
) {
|
||||||
$payoutType = CoinTransactionType::firstOrCreate(
|
$payoutType = CoinTransactionType::firstOrCreate(
|
||||||
['name' => 'Match payout'],
|
["name" => "Match payout"],
|
||||||
['type' => 'C'] // Credit
|
["type" => "C"], // Credit
|
||||||
);
|
);
|
||||||
|
|
||||||
$totalPrize = $match->stake * 2;
|
$totalPrize = $match->stake * 2;
|
||||||
|
|
||||||
CoinTransaction::create([
|
CoinTransaction::create([
|
||||||
'user_id' => $match->winner_user_id,
|
"user_id" => $match->winner_user_id,
|
||||||
'match_id' => $match->id,
|
"match_id" => $match->id,
|
||||||
'coin_transaction_type_id' => $payoutType->id,
|
"coin_transaction_type_id" => $payoutType->id,
|
||||||
'transaction_datetime' => now(),
|
"transaction_datetime" => now(),
|
||||||
'coins' => $totalPrize,
|
"coins" => $totalPrize,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$match->winner->increment("coins_balance", $totalPrize);
|
||||||
$match->winner->increment('coins_balance', $totalPrize);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json($match);
|
return response()->json($match);
|
||||||
@@ -198,55 +239,67 @@ class MatchController extends Controller
|
|||||||
public function host(Request $request)
|
public function host(Request $request)
|
||||||
{
|
{
|
||||||
$request->validate([
|
$request->validate([
|
||||||
'type' => 'required|in:3,9',
|
"type" => "required|in:3,9",
|
||||||
'stake' => 'required|integer|min:0'
|
"stake" => "required|integer|min:0",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
$stake = $request->stake;
|
$stake = $request->stake;
|
||||||
|
|
||||||
if ($user->coins_balance < $stake) {
|
if ($user->coins_balance < $stake) {
|
||||||
return response()->json(['message' => 'Insufficient coin balance'], 400);
|
return response()->json(
|
||||||
|
["message" => "Insufficient coin balance"],
|
||||||
|
400,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$activeMatch = MatchGame::where(function ($q) use ($user) {
|
$activeMatch = MatchGame::where(function ($q) use ($user) {
|
||||||
$q->where('player1_user_id', $user->id)
|
$q->where("player1_user_id", $user->id)->orWhere(
|
||||||
->orWhere('player2_user_id', $user->id);
|
"player2_user_id",
|
||||||
})->whereIn('status', ['Pending', 'Playing'])->exists();
|
$user->id,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
->whereIn("status", ["Pending", "Playing"])
|
||||||
|
->exists();
|
||||||
|
|
||||||
if ($activeMatch) {
|
if ($activeMatch) {
|
||||||
return response()->json(['message' => 'You already have an active match.'], 400);
|
return response()->json(
|
||||||
|
["message" => "You already have an active match."],
|
||||||
|
400,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return DB::transaction(function () use ($request, $user, $stake) {
|
return DB::transaction(function () use ($request, $user, $stake) {
|
||||||
$GHOST_ID = 1;
|
$GHOST_ID = 1;
|
||||||
|
|
||||||
$user->decrement('coins_balance', $stake);
|
$user->decrement("coins_balance", $stake);
|
||||||
|
|
||||||
$stakeType = CoinTransactionType::firstOrCreate(
|
$stakeType = CoinTransactionType::firstOrCreate(
|
||||||
['name' => 'Match stake'],
|
["name" => "Match stake"],
|
||||||
['type' => 'D'] // Debit
|
["type" => "D"], // Debit
|
||||||
);
|
);
|
||||||
|
|
||||||
$match = MatchGame::create([
|
$match = MatchGame::create([
|
||||||
'type' => $request->type,
|
"type" => $request->type,
|
||||||
'status' => 'Pending',
|
"status" => "Pending",
|
||||||
'player1_user_id' => $user->id,
|
"player1_user_id" => $user->id,
|
||||||
'player2_user_id' => $GHOST_ID,
|
"player2_user_id" => $GHOST_ID,
|
||||||
'winner_user_id' => null,
|
"winner_user_id" => null,
|
||||||
'loser_user_id' => null,
|
"loser_user_id" => null,
|
||||||
'stake' => $stake,
|
"stake" => $stake,
|
||||||
'began_at' => now(),
|
"began_at" => now(),
|
||||||
'player1_marks' => 0, 'player2_marks' => 0,
|
"player1_marks" => 0,
|
||||||
'player1_points' => 0, 'player2_points' => 0,
|
"player2_marks" => 0,
|
||||||
|
"player1_points" => 0,
|
||||||
|
"player2_points" => 0,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
CoinTransaction::create([
|
CoinTransaction::create([
|
||||||
'user_id' => $user->id,
|
"user_id" => $user->id,
|
||||||
'match_id' => $match->id,
|
"match_id" => $match->id,
|
||||||
'coin_transaction_type_id' => $stakeType->id,
|
"coin_transaction_type_id" => $stakeType->id,
|
||||||
'transaction_datetime' => now(),
|
"transaction_datetime" => now(),
|
||||||
'coins' => -$stake,
|
"coins" => -$stake,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return response()->json($match, 201);
|
return response()->json($match, 201);
|
||||||
@@ -255,14 +308,14 @@ class MatchController extends Controller
|
|||||||
|
|
||||||
public function open(Request $request)
|
public function open(Request $request)
|
||||||
{
|
{
|
||||||
$type = $request->query('type', '9');
|
$type = $request->query("type", "9");
|
||||||
$GHOST_ID = 1;
|
$GHOST_ID = 1;
|
||||||
|
|
||||||
$matches = MatchGame::where('status', 'Pending')
|
$matches = MatchGame::where("status", "Pending")
|
||||||
->where('player2_user_id', $GHOST_ID)
|
->where("player2_user_id", $GHOST_ID)
|
||||||
->where('type', $type)
|
->where("type", $type)
|
||||||
->with('player1')
|
->with("player1")
|
||||||
->orderBy('began_at', 'asc')
|
->orderBy("began_at", "asc")
|
||||||
->paginate(10);
|
->paginate(10);
|
||||||
|
|
||||||
return response()->json($matches);
|
return response()->json($matches);
|
||||||
|
|||||||
@@ -18,23 +18,23 @@ class UserController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function index(Request $request)
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
$this->authorize('viewAny', User::class);
|
$this->authorize("viewAny", User::class);
|
||||||
|
|
||||||
$query = User::query();
|
$query = User::query();
|
||||||
|
|
||||||
if ($request->has('type')) {
|
if ($request->has("type")) {
|
||||||
$query->where('type', $request->type);
|
$query->where("type", $request->type);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->has('blocked')) {
|
if ($request->has("blocked")) {
|
||||||
$query->where('blocked', $request->type);
|
$query->where("blocked", $request->type);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->has('search')) {
|
if ($request->has("search")) {
|
||||||
$query->where(function ($q) use ($request) {
|
$query->where(function ($q) use ($request) {
|
||||||
$q->where('name', 'like', '%'.$request->search.'%')
|
$q->where("name", "like", "%" . $request->search . "%")
|
||||||
->orWhere('email', 'like', '%'.$request->search.'%')
|
->orWhere("email", "like", "%" . $request->search . "%")
|
||||||
->orWhere('nickname', 'like', '%'.$request->search.'%');
|
->orWhere("nickname", "like", "%" . $request->search . "%");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,16 +47,16 @@ class UserController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function store(Request $request)
|
public function store(Request $request)
|
||||||
{
|
{
|
||||||
$this->authorize('create', User::class);
|
$this->authorize("create", User::class);
|
||||||
|
|
||||||
$validated = $request->validate([
|
$validated = $request->validate([
|
||||||
'name' => 'required|string|max:255',
|
"name" => "required|string|max:255",
|
||||||
'email' => 'required|email|unique:users,email',
|
"email" => "required|email|unique:users,email",
|
||||||
'password' => 'required|string|min:3',
|
"password" => "required|string|min:3",
|
||||||
'type' => 'required|in:A,U',
|
"type" => "required|in:A,U",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$validated['password'] = Hash::make($validated['password']);
|
$validated["password"] = Hash::make($validated["password"]);
|
||||||
|
|
||||||
$user = User::create($validated);
|
$user = User::create($validated);
|
||||||
|
|
||||||
@@ -82,15 +82,15 @@ class UserController extends Controller
|
|||||||
$currentUser = Auth::user();
|
$currentUser = Auth::user();
|
||||||
|
|
||||||
// Admins and owners get full profile, others get limited view
|
// Admins and owners get full profile, others get limited view
|
||||||
if ($currentUser->type === 'A' || $currentUser->id === $user->id) {
|
if ($currentUser->type === "A" || $currentUser->id === $user->id) {
|
||||||
return response()->json($user);
|
return response()->json($user);
|
||||||
} else {
|
} else {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'id' => $user->id,
|
"id" => $user->id,
|
||||||
'name' => $user->name,
|
"name" => $user->name,
|
||||||
'nickname' => $user->nickname,
|
"nickname" => $user->nickname,
|
||||||
'photo_avatar_filename' => $user->photo_avatar_filename,
|
"photo_avatar_filename" => $user->photo_avatar_filename,
|
||||||
'type' => $user->type,
|
"type" => $user->type,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -101,22 +101,22 @@ class UserController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function update(Request $request, User $user)
|
public function update(Request $request, User $user)
|
||||||
{
|
{
|
||||||
$this->authorize('update', $user);
|
$this->authorize("update", $user);
|
||||||
|
|
||||||
$validated = $request->validate([
|
$validated = $request->validate([
|
||||||
'name' => 'sometimes|string|max:255',
|
"name" => "sometimes|string|max:255",
|
||||||
'nickname' => [
|
"nickname" => [
|
||||||
'sometimes',
|
"sometimes",
|
||||||
'string',
|
"string",
|
||||||
'max:20',
|
"max:20",
|
||||||
Rule::unique('users')->ignore($user->id),
|
Rule::unique("users")->ignore($user->id),
|
||||||
],
|
],
|
||||||
'email' => [
|
"email" => [
|
||||||
'sometimes',
|
"sometimes",
|
||||||
'email',
|
"email",
|
||||||
Rule::unique('users')->ignore($user->id),
|
Rule::unique("users")->ignore($user->id),
|
||||||
],
|
],
|
||||||
'password' => 'sometimes|string|min:3',
|
"password" => "sometimes|string|min:3",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Lógica de Upload de Foto (Exemplo Básico)
|
// Lógica de Upload de Foto (Exemplo Básico)
|
||||||
@@ -130,26 +130,28 @@ class UserController extends Controller
|
|||||||
return response()->json($user);
|
return response()->json($user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function updatePassword(Request $request, User $user)
|
public function updatePassword(Request $request, User $user)
|
||||||
{
|
{
|
||||||
$this->authorize('update', $user);
|
$this->authorize("update", $user);
|
||||||
|
|
||||||
$validated = $request->validate([
|
$validated = $request->validate([
|
||||||
'current_password' => 'required|string',
|
"current_password" => "required|string",
|
||||||
'new_password' => 'required|string|min:3|confirmed',
|
"new_password" => "required|string|min:3|confirmed",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Verificar se a password atual está correta
|
// Verificar se a password atual está correta
|
||||||
if (!Hash::check($validated['current_password'], $user->password)) {
|
if (!Hash::check($validated["current_password"], $user->password)) {
|
||||||
return response()->json(['message' => 'Current password is incorrect'], 403);
|
return response()->json(
|
||||||
|
["message" => "Current password is incorrect"],
|
||||||
|
403,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Atualizar para a nova password
|
// Atualizar para a nova password
|
||||||
$user->password = Hash::make($validated['new_password']);
|
$user->password = Hash::make($validated["new_password"]);
|
||||||
$user->save();
|
$user->save();
|
||||||
|
|
||||||
return response()->json(['message' => 'Password updated successfully']);
|
return response()->json(["message" => "Password updated successfully"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -158,24 +160,27 @@ class UserController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function destroy(User $user)
|
public function destroy(User $user)
|
||||||
{
|
{
|
||||||
$this->authorize('delete', $user);
|
$this->authorize("delete", $user);
|
||||||
|
|
||||||
// Check if user has transactions or games - if so, soft delete
|
// Check if user has transactions or games - if so, soft delete
|
||||||
$hasTransactions = $user->transactions()->exists();
|
$hasTransactions = $user->transactions()->exists();
|
||||||
$hasGames = Game::where(function ($q) use ($user) {
|
$hasGames = Game::where(function ($q) use ($user) {
|
||||||
$q->where('player1_user_id', $user->id)
|
$q->where("player1_user_id", $user->id)->orWhere(
|
||||||
->orWhere('player2_user_id', $user->id);
|
"player2_user_id",
|
||||||
|
$user->id,
|
||||||
|
);
|
||||||
})->exists();
|
})->exists();
|
||||||
|
|
||||||
if ($hasTransactions || $hasGames) {
|
if ($hasTransactions || $hasGames) {
|
||||||
$user->delete(); // Soft delete
|
$user->delete(); // Soft delete
|
||||||
$message = 'User account deactivated (soft-deleted due to transaction/game history)';
|
$message =
|
||||||
|
"User account deactivated (soft-deleted due to transaction/game history)";
|
||||||
} else {
|
} else {
|
||||||
$user->forceDelete(); // Hard delete
|
$user->forceDelete(); // Hard delete
|
||||||
$message = 'User permanently deleted';
|
$message = "User permanently deleted";
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json(['message' => $message]);
|
return response()->json(["message" => $message]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -184,37 +189,61 @@ class UserController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function getGames(Request $request, User $user)
|
public function getGames(Request $request, User $user)
|
||||||
{
|
{
|
||||||
$this->authorize('view', $user);
|
$this->authorize("view", $user);
|
||||||
|
|
||||||
// Define a query base para GAMES
|
// Define a query base para GAMES
|
||||||
$query = \App\Models\Game::query()
|
$query = \App\Models\Game::query()
|
||||||
->where(function ($q) use ($user) {
|
->where(function ($q) use ($user) {
|
||||||
$q->where('player1_user_id', $user->id)
|
$q->where("player1_user_id", $user->id)->orWhere(
|
||||||
->orWhere('player2_user_id', $user->id);
|
"player2_user_id",
|
||||||
|
$user->id,
|
||||||
|
);
|
||||||
})
|
})
|
||||||
->with(['winner', 'player1', 'player2']);
|
->with(["winner", "player1", "player2"]);
|
||||||
|
|
||||||
if ($request->has('type')) {
|
if ($request->has("type")) {
|
||||||
$query->where('type', $request->type);
|
$query->where("type", $request->type);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->has('status')) {
|
if ($request->has("status")) {
|
||||||
$query->where('status', $request->status);
|
$query->where("status", $request->status);
|
||||||
}
|
}
|
||||||
|
|
||||||
$sortDirection = $request->get('sort_direction', 'desc');
|
// Date filtering
|
||||||
$query->orderBy('began_at', $sortDirection);
|
if ($request->has("date_from")) {
|
||||||
|
$query->whereDate("began_at", ">=", $request->date_from);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->has("date_to")) {
|
||||||
|
$query->whereDate("began_at", "<=", $request->date_to);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flexible sorting
|
||||||
|
$sortColumn = $request->get("sort_by", "began_at");
|
||||||
|
$sortDirection = $request->get("sort_direction", "desc");
|
||||||
|
|
||||||
|
// Whitelist sortable columns for security
|
||||||
|
$allowedColumns = [
|
||||||
|
"began_at",
|
||||||
|
"ended_at",
|
||||||
|
"total_time",
|
||||||
|
"total_points",
|
||||||
|
];
|
||||||
|
if (in_array($sortColumn, $allowedColumns)) {
|
||||||
|
$query->orderBy($sortColumn, $sortDirection);
|
||||||
|
} else {
|
||||||
|
$query->orderBy("began_at", $sortDirection);
|
||||||
|
}
|
||||||
|
|
||||||
$games = $query->paginate(10);
|
$games = $query->paginate(10);
|
||||||
|
|
||||||
$games->getCollection()->transform(function ($game) use ($user) {
|
$games->getCollection()->transform(function ($game) use ($user) {
|
||||||
if ($game->winner_user_id == $user->id) {
|
if ($game->winner_user_id == $user->id) {
|
||||||
$game->outcome = 'win';
|
$game->outcome = "win";
|
||||||
} elseif ($game->is_draw) {
|
} elseif ($game->is_draw) {
|
||||||
$game->outcome = 'draw';
|
$game->outcome = "draw";
|
||||||
} else {
|
} else {
|
||||||
$game->outcome = 'loss';
|
$game->outcome = "loss";
|
||||||
}
|
}
|
||||||
return $game;
|
return $game;
|
||||||
});
|
});
|
||||||
@@ -228,35 +257,58 @@ class UserController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function getMatches(Request $request, User $user)
|
public function getMatches(Request $request, User $user)
|
||||||
{
|
{
|
||||||
$this->authorize('view', $user);
|
$this->authorize("view", $user);
|
||||||
|
|
||||||
$query = MatchGame::query()
|
$query = MatchGame::query()
|
||||||
->where(function ($q) use ($user) {
|
->where(function ($q) use ($user) {
|
||||||
$q->where('player1_user_id', $user->id)
|
$q->where("player1_user_id", $user->id)->orWhere(
|
||||||
->orWhere('player2_user_id', $user->id);
|
"player2_user_id",
|
||||||
|
$user->id,
|
||||||
|
);
|
||||||
})
|
})
|
||||||
->with(['winner', 'player1', 'player2']);
|
->with(["winner", "player1", "player2"]);
|
||||||
|
|
||||||
if ($request->has('type')) {
|
if ($request->has("type")) {
|
||||||
$query->where('type', $request->type);
|
$query->where("type", $request->type);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->has('status')) {
|
if ($request->has("status")) {
|
||||||
$query->where('status', $request->status);
|
$query->where("status", $request->status);
|
||||||
}
|
}
|
||||||
|
|
||||||
$sortDirection = $request->get('sort_direction', 'desc');
|
// Date filtering
|
||||||
$query->orderBy('began_at', $sortDirection);
|
if ($request->has("date_from")) {
|
||||||
|
$query->whereDate("began_at", ">=", $request->date_from);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->has("date_to")) {
|
||||||
|
$query->whereDate("began_at", "<=", $request->date_to);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flexible sorting
|
||||||
|
$sortColumn = $request->get("sort_by", "began_at");
|
||||||
|
$sortDirection = $request->get("sort_direction", "desc");
|
||||||
|
|
||||||
|
// Whitelist sortable columns for security
|
||||||
|
$allowedColumns = ["began_at", "ended_at", "stake", "total_time"];
|
||||||
|
if (in_array($sortColumn, $allowedColumns)) {
|
||||||
|
$query->orderBy($sortColumn, $sortDirection);
|
||||||
|
} else {
|
||||||
|
$query->orderBy("began_at", $sortDirection);
|
||||||
|
}
|
||||||
|
|
||||||
$matches = $query->paginate(10);
|
$matches = $query->paginate(10);
|
||||||
|
|
||||||
$matches->getCollection()->transform(function ($match) use ($user) {
|
$matches->getCollection()->transform(function ($match) use ($user) {
|
||||||
if ($match->winner_user_id == $user->id) {
|
if ($match->winner_user_id == $user->id) {
|
||||||
$match->outcome = 'win';
|
$match->outcome = "win";
|
||||||
} elseif ($match->winner_user_id && $match->winner_user_id != $user->id) {
|
} elseif (
|
||||||
$match->outcome = 'loss';
|
$match->winner_user_id &&
|
||||||
|
$match->winner_user_id != $user->id
|
||||||
|
) {
|
||||||
|
$match->outcome = "loss";
|
||||||
} else {
|
} else {
|
||||||
$match->outcome = 'interrupted'; // Matches geralmente não empatam (alguém chega a 4 marcas)
|
$match->outcome = "interrupted"; // Matches geralmente não empatam (alguém chega a 4 marcas)
|
||||||
}
|
}
|
||||||
return $match;
|
return $match;
|
||||||
});
|
});
|
||||||
@@ -266,7 +318,7 @@ class UserController extends Controller
|
|||||||
|
|
||||||
public function block(User $user)
|
public function block(User $user)
|
||||||
{
|
{
|
||||||
$this->authorize('block', $user);
|
$this->authorize("block", $user);
|
||||||
|
|
||||||
$user->blocked = true;
|
$user->blocked = true;
|
||||||
$user->save();
|
$user->save();
|
||||||
@@ -274,42 +326,50 @@ class UserController extends Controller
|
|||||||
// Revoke all active tokens for immediate effect
|
// Revoke all active tokens for immediate effect
|
||||||
$user->tokens()->delete();
|
$user->tokens()->delete();
|
||||||
|
|
||||||
return response()->json(['message' => 'User blocked successfully']);
|
return response()->json(["message" => "User blocked successfully"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function unblock(User $user)
|
public function unblock(User $user)
|
||||||
{
|
{
|
||||||
$this->authorize('unblock', $user);
|
$this->authorize("unblock", $user);
|
||||||
|
|
||||||
$user->blocked = false;
|
$user->blocked = false;
|
||||||
$user->save();
|
$user->save();
|
||||||
|
|
||||||
return response()->json(['message' => 'User unblocked successfully']);
|
return response()->json(["message" => "User unblocked successfully"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTransactions(Request $request, User $user)
|
public function getTransactions(Request $request, User $user)
|
||||||
{
|
{
|
||||||
if ($request->user()->id !== $user->id && $request->user()->type !== 'A') {
|
if (
|
||||||
return response()->json(['message' => 'Forbidden'], 403);
|
$request->user()->id !== $user->id &&
|
||||||
|
$request->user()->type !== "A"
|
||||||
|
) {
|
||||||
|
return response()->json(["message" => "Forbidden"], 403);
|
||||||
}
|
}
|
||||||
|
|
||||||
$query = $user->transactions()->with('type');
|
$query = $user->transactions()->with("type");
|
||||||
|
|
||||||
if ($request->has('type')) {
|
if ($request->has("type")) {
|
||||||
$query->whereHas('type', function ($q) use ($request) {
|
$query->whereHas("type", function ($q) use ($request) {
|
||||||
$q->where('type', $request->type);
|
$q->where("type", $request->type);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->has('date_from')) {
|
if ($request->has("date_from")) {
|
||||||
$query->whereDate('transaction_datetime', '>=', $request->date_from);
|
$query->whereDate(
|
||||||
|
"transaction_datetime",
|
||||||
|
">=",
|
||||||
|
$request->date_from,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->has('date_to')) {
|
if ($request->has("date_to")) {
|
||||||
$query->whereDate('transaction_datetime', '<=', $request->date_to);
|
$query->whereDate("transaction_datetime", "<=", $request->date_to);
|
||||||
}
|
}
|
||||||
|
|
||||||
$transactions = $query->orderBy('transaction_datetime', 'desc')
|
$transactions = $query
|
||||||
|
->orderBy("transaction_datetime", "desc")
|
||||||
->paginate(10);
|
->paginate(10);
|
||||||
|
|
||||||
return response()->json($transactions);
|
return response()->json($transactions);
|
||||||
|
|||||||
@@ -2,20 +2,6 @@
|
|||||||
<div>
|
<div>
|
||||||
<NavigationMenu>
|
<NavigationMenu>
|
||||||
<NavigationMenuList class="justify-around gap-20">
|
<NavigationMenuList class="justify-around gap-20">
|
||||||
<NavigationMenuItem>
|
|
||||||
<NavigationMenuTrigger>Testing</NavigationMenuTrigger>
|
|
||||||
<NavigationMenuContent>
|
|
||||||
<li>
|
|
||||||
<NavigationMenuLink as-child>
|
|
||||||
<RouterLink to="/testing/laravel">Laravel</RouterLink>
|
|
||||||
</NavigationMenuLink>
|
|
||||||
<NavigationMenuLink as-child>
|
|
||||||
<RouterLink to="/testing/websockets">Web Sockets</RouterLink>
|
|
||||||
</NavigationMenuLink>
|
|
||||||
</li>
|
|
||||||
</NavigationMenuContent>
|
|
||||||
</NavigationMenuItem>
|
|
||||||
|
|
||||||
<NavigationMenuItem v-if="!userLoggedIn">
|
<NavigationMenuItem v-if="!userLoggedIn">
|
||||||
<NavigationMenuLink>
|
<NavigationMenuLink>
|
||||||
<RouterLink to="/login">Login</RouterLink>
|
<RouterLink to="/login">Login</RouterLink>
|
||||||
@@ -23,9 +9,14 @@
|
|||||||
</NavigationMenuItem>
|
</NavigationMenuItem>
|
||||||
|
|
||||||
<NavigationMenuItem v-else class="flex flex-row gap-4">
|
<NavigationMenuItem v-else class="flex flex-row gap-4">
|
||||||
<NavigationMenuLink v-if="userStore.coins > 0" href="/coins-purchase"
|
<NavigationMenuLink
|
||||||
class="flex flex-row items-center gap-1">
|
v-if="userStore.coins > 0"
|
||||||
<div class="flex items-center justify-center w-5 h-5 bg-purple-400 rounded-full shadow-sm">
|
href="/coins-purchase"
|
||||||
|
class="flex flex-row items-center gap-1"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="flex items-center justify-center w-5 h-5 bg-purple-400 rounded-full shadow-sm"
|
||||||
|
>
|
||||||
<span class="text-[10px] font-bold text-purple-900">$</span>
|
<span class="text-[10px] font-bold text-purple-900">$</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -55,10 +46,9 @@ import {
|
|||||||
NavigationMenuList,
|
NavigationMenuList,
|
||||||
NavigationMenuTrigger,
|
NavigationMenuTrigger,
|
||||||
} from '@/components/ui/navigation-menu'
|
} from '@/components/ui/navigation-menu'
|
||||||
import router from '@/router';
|
import router from '@/router'
|
||||||
import { watch } from 'vue';
|
import { watch } from 'vue'
|
||||||
import { useUserStore } from '@/stores/user';
|
import { useUserStore } from '@/stores/user'
|
||||||
|
|
||||||
|
|
||||||
const emits = defineEmits(['logout'])
|
const emits = defineEmits(['logout'])
|
||||||
const { userLoggedIn } = defineProps(['userLoggedIn'])
|
const { userLoggedIn } = defineProps(['userLoggedIn'])
|
||||||
@@ -68,7 +58,7 @@ const biscaStore = useBiscaStore()
|
|||||||
const logoutClickHandler = () => {
|
const logoutClickHandler = () => {
|
||||||
if (biscaStore.isGameRunning) {
|
if (biscaStore.isGameRunning) {
|
||||||
const confirmLogout = window.confirm(
|
const confirmLogout = window.confirm(
|
||||||
'If you leave now this game will count as "FORFEIT". Are you sure you want to logout?'
|
'If you leave now this game will count as "FORFEIT". Are you sure you want to logout?',
|
||||||
)
|
)
|
||||||
if (!confirmLogout) return
|
if (!confirmLogout) return
|
||||||
|
|
||||||
@@ -85,11 +75,15 @@ const logoutClickHandler = () => {
|
|||||||
|
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
|
|
||||||
watch(() => userLoggedIn, async (isLoggedIn) => {
|
watch(
|
||||||
|
() => userLoggedIn,
|
||||||
|
async (isLoggedIn) => {
|
||||||
if (isLoggedIn) {
|
if (isLoggedIn) {
|
||||||
await userStore.fetchCoins()
|
await userStore.fetchCoins()
|
||||||
} else {
|
} else {
|
||||||
userStore.coins = 0
|
userStore.coins = 0
|
||||||
}
|
}
|
||||||
}, { immediate: true })
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
@@ -1,342 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="min-h-screen bg-gradient-to-br from-green-700 via-green-800 to-green-900 p-8">
|
|
||||||
<div class="max-w-6xl mx-auto">
|
|
||||||
<!-- Title -->
|
|
||||||
<h1 class="text-white text-3xl font-bold mb-8 text-center">
|
|
||||||
All Animations Test Page
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<!-- Control Panel -->
|
|
||||||
<div class="bg-gray-900/80 rounded-lg p-6 mb-8">
|
|
||||||
<h2 class="text-white text-xl font-semibold mb-4">Controls</h2>
|
|
||||||
|
|
||||||
<!-- Score Animation Tests -->
|
|
||||||
<div class="mb-6">
|
|
||||||
<h3 class="text-emerald-400 font-semibold mb-3">Score Count-Up Animation</h3>
|
|
||||||
<div class="flex flex-wrap gap-3">
|
|
||||||
<button
|
|
||||||
@click="increasePlayerScore"
|
|
||||||
class="px-4 py-2 bg-green-600 hover:bg-green-700 text-white rounded-lg font-medium transition-colors"
|
|
||||||
>
|
|
||||||
+10 Player Score
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
@click="increaseOpponentScore"
|
|
||||||
class="px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded-lg font-medium transition-colors"
|
|
||||||
>
|
|
||||||
+10 Opponent Score
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
@click="resetScores"
|
|
||||||
class="px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white rounded-lg font-medium transition-colors"
|
|
||||||
>
|
|
||||||
Reset Scores
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Turn Transition Tests -->
|
|
||||||
<div class="mb-6">
|
|
||||||
<h3 class="text-blue-400 font-semibold mb-3">Turn Transition Animation</h3>
|
|
||||||
<div class="flex flex-wrap gap-3">
|
|
||||||
<button
|
|
||||||
@click="toggleTurn"
|
|
||||||
class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg font-medium transition-colors"
|
|
||||||
>
|
|
||||||
Switch Turn
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
@click="currentTurn = 'player'"
|
|
||||||
class="px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-white rounded-lg font-medium transition-colors"
|
|
||||||
>
|
|
||||||
Your Turn
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
@click="currentTurn = 'opponent'"
|
|
||||||
class="px-4 py-2 bg-rose-600 hover:bg-rose-700 text-white rounded-lg font-medium transition-colors"
|
|
||||||
>
|
|
||||||
Bot's Turn
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Trump Reveal Tests -->
|
|
||||||
<div class="mb-6">
|
|
||||||
<h3 class="text-amber-400 font-semibold mb-3">Trump Reveal Animation</h3>
|
|
||||||
<div class="flex flex-wrap gap-3">
|
|
||||||
<button
|
|
||||||
@click="triggerTrumpReveal"
|
|
||||||
class="px-4 py-2 bg-amber-600 hover:bg-amber-700 text-white rounded-lg font-medium transition-colors"
|
|
||||||
>
|
|
||||||
Reveal Trump
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Game Over Tests -->
|
|
||||||
<div class="mb-6">
|
|
||||||
<h3 class="text-purple-400 font-semibold mb-3">Game Over Screen</h3>
|
|
||||||
<div class="flex flex-wrap gap-3">
|
|
||||||
<button
|
|
||||||
@click="showGameOverPlayerWin"
|
|
||||||
class="px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-white rounded-lg font-medium transition-colors"
|
|
||||||
>
|
|
||||||
Player Wins
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
@click="showGameOverOpponentWin"
|
|
||||||
class="px-4 py-2 bg-rose-600 hover:bg-rose-700 text-white rounded-lg font-medium transition-colors"
|
|
||||||
>
|
|
||||||
Opponent Wins
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
@click="showGameOverDraw"
|
|
||||||
class="px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white rounded-lg font-medium transition-colors"
|
|
||||||
>
|
|
||||||
Draw
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Full Sequence -->
|
|
||||||
<div>
|
|
||||||
<h3 class="text-pink-400 font-semibold mb-3">Complete Sequence</h3>
|
|
||||||
<div class="flex flex-wrap gap-3">
|
|
||||||
<button
|
|
||||||
@click="runFullSequence"
|
|
||||||
:disabled="isRunningSequence"
|
|
||||||
class="px-4 py-2 bg-pink-600 hover:bg-pink-700 disabled:bg-gray-600 disabled:cursor-not-allowed text-white rounded-lg font-medium transition-colors"
|
|
||||||
>
|
|
||||||
Run Full Animation Sequence
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Game Board Preview -->
|
|
||||||
<div class="grid grid-rows-[auto_1fr] gap-8">
|
|
||||||
<!-- Score Display -->
|
|
||||||
<div class="flex justify-center">
|
|
||||||
<ScoreDisplay
|
|
||||||
:player-score="playerScore"
|
|
||||||
:opponent-score="opponentScore"
|
|
||||||
:current-turn="currentTurn"
|
|
||||||
:round-number="1"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Deck Area (with trump) -->
|
|
||||||
<div class="flex justify-center">
|
|
||||||
<div class="bg-gray-800/30 rounded-lg p-6">
|
|
||||||
<h3 class="text-white text-sm font-semibold mb-4 text-center">Trump Card</h3>
|
|
||||||
<DeckArea
|
|
||||||
:trump-card="trumpCard"
|
|
||||||
:cards-remaining="cardsRemaining"
|
|
||||||
:is-empty="false"
|
|
||||||
:reveal-trump="revealTrump"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Instructions -->
|
|
||||||
<div class="bg-gray-900/80 rounded-lg p-6 mt-8 text-gray-300">
|
|
||||||
<h3 class="text-white font-semibold mb-2">Instructions:</h3>
|
|
||||||
<ul class="list-disc list-inside space-y-1 text-sm">
|
|
||||||
<li>
|
|
||||||
<strong>Score Count-Up:</strong> Click "+10" buttons to see scores animate smoothly
|
|
||||||
from old to new value
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Turn Transition:</strong> Switch turns to see the indicator pulse and glow with
|
|
||||||
color change
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Trump Reveal:</strong> Triggers a 3-second pulse/glow animation on the trump
|
|
||||||
card with label
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Game Over:</strong> Shows victory/defeat modal with score count-up and confetti
|
|
||||||
(on win)
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Full Sequence:</strong> Runs all animations in order: trump reveal → turn
|
|
||||||
changes → score updates → game over
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Game Over Modal -->
|
|
||||||
<GameOver
|
|
||||||
:is-visible="gameOverVisible"
|
|
||||||
:winner="gameOverWinner"
|
|
||||||
:player-score="playerScore"
|
|
||||||
:opponent-score="opponentScore"
|
|
||||||
:stats="gameOverStats"
|
|
||||||
@close="gameOverVisible = false"
|
|
||||||
@play-again="handlePlayAgain"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref } from 'vue'
|
|
||||||
import ScoreDisplay from '@/components/game/ScoreDisplay.vue'
|
|
||||||
import DeckArea from '@/components/game/DeckArea.vue'
|
|
||||||
import GameOver from '@/components/game/GameOver.vue'
|
|
||||||
|
|
||||||
// Game state
|
|
||||||
const playerScore = ref(23)
|
|
||||||
const opponentScore = ref(15)
|
|
||||||
const currentTurn = ref('player')
|
|
||||||
const cardsRemaining = ref(30)
|
|
||||||
const revealTrump = ref(false)
|
|
||||||
const isRunningSequence = ref(false)
|
|
||||||
|
|
||||||
// Trump card
|
|
||||||
const trumpCard = ref({ suit: 'o', rank: 7 })
|
|
||||||
|
|
||||||
// Game Over state
|
|
||||||
const gameOverVisible = ref(false)
|
|
||||||
const gameOverWinner = ref('player')
|
|
||||||
const gameOverStats = ref({
|
|
||||||
playerTricks: 8,
|
|
||||||
opponentTricks: 6,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Score manipulation
|
|
||||||
const increasePlayerScore = () => {
|
|
||||||
playerScore.value += 10
|
|
||||||
}
|
|
||||||
|
|
||||||
const increaseOpponentScore = () => {
|
|
||||||
opponentScore.value += 10
|
|
||||||
}
|
|
||||||
|
|
||||||
const resetScores = () => {
|
|
||||||
playerScore.value = 0
|
|
||||||
opponentScore.value = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Turn control
|
|
||||||
const toggleTurn = () => {
|
|
||||||
currentTurn.value = currentTurn.value === 'player' ? 'opponent' : 'player'
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trump reveal
|
|
||||||
const triggerTrumpReveal = () => {
|
|
||||||
revealTrump.value = true
|
|
||||||
setTimeout(() => {
|
|
||||||
revealTrump.value = false
|
|
||||||
}, 100)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Game Over screens
|
|
||||||
const showGameOverPlayerWin = () => {
|
|
||||||
playerScore.value = 67
|
|
||||||
opponentScore.value = 53
|
|
||||||
gameOverWinner.value = 'player'
|
|
||||||
gameOverStats.value = {
|
|
||||||
playerTricks: 11,
|
|
||||||
opponentTricks: 9,
|
|
||||||
}
|
|
||||||
gameOverVisible.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const showGameOverOpponentWin = () => {
|
|
||||||
playerScore.value = 45
|
|
||||||
opponentScore.value = 75
|
|
||||||
gameOverWinner.value = 'opponent'
|
|
||||||
gameOverStats.value = {
|
|
||||||
playerTricks: 7,
|
|
||||||
opponentTricks: 13,
|
|
||||||
}
|
|
||||||
gameOverVisible.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const showGameOverDraw = () => {
|
|
||||||
playerScore.value = 60
|
|
||||||
opponentScore.value = 60
|
|
||||||
gameOverWinner.value = 'draw'
|
|
||||||
gameOverStats.value = {
|
|
||||||
playerTricks: 10,
|
|
||||||
opponentTricks: 10,
|
|
||||||
}
|
|
||||||
gameOverVisible.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const handlePlayAgain = () => {
|
|
||||||
gameOverVisible.value = false
|
|
||||||
resetScores()
|
|
||||||
currentTurn.value = 'player'
|
|
||||||
cardsRemaining.value = 40
|
|
||||||
}
|
|
||||||
|
|
||||||
// Full animation sequence
|
|
||||||
const runFullSequence = async () => {
|
|
||||||
if (isRunningSequence.value) return
|
|
||||||
isRunningSequence.value = true
|
|
||||||
|
|
||||||
// Reset everything
|
|
||||||
resetScores()
|
|
||||||
currentTurn.value = 'player'
|
|
||||||
gameOverVisible.value = false
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 500))
|
|
||||||
|
|
||||||
// 1. Trump Reveal
|
|
||||||
console.log('1. Trump Reveal')
|
|
||||||
triggerTrumpReveal()
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 3500))
|
|
||||||
|
|
||||||
// 2. First turn
|
|
||||||
console.log('2. Player turn')
|
|
||||||
currentTurn.value = 'player'
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1500))
|
|
||||||
|
|
||||||
// 3. Player scores
|
|
||||||
console.log('3. Player scores')
|
|
||||||
playerScore.value = 11
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 2000))
|
|
||||||
|
|
||||||
// 4. Switch turn
|
|
||||||
console.log('4. Switch to opponent turn')
|
|
||||||
currentTurn.value = 'opponent'
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1500))
|
|
||||||
|
|
||||||
// 5. Opponent scores
|
|
||||||
console.log('5. Opponent scores')
|
|
||||||
opponentScore.value = 10
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 2000))
|
|
||||||
|
|
||||||
// 6. Back to player
|
|
||||||
console.log('6. Back to player')
|
|
||||||
currentTurn.value = 'player'
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
||||||
|
|
||||||
// 7. More scoring
|
|
||||||
console.log('7. More scoring')
|
|
||||||
playerScore.value = 23
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1500))
|
|
||||||
opponentScore.value = 19
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1500))
|
|
||||||
playerScore.value = 34
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1500))
|
|
||||||
|
|
||||||
// 8. Final scores and game over
|
|
||||||
console.log('8. Game over')
|
|
||||||
playerScore.value = 67
|
|
||||||
opponentScore.value = 53
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 2000))
|
|
||||||
|
|
||||||
// Show game over
|
|
||||||
gameOverWinner.value = 'player'
|
|
||||||
gameOverStats.value = {
|
|
||||||
playerTricks: 11,
|
|
||||||
opponentTricks: 9,
|
|
||||||
}
|
|
||||||
gameOverVisible.value = true
|
|
||||||
|
|
||||||
isRunningSequence.value = false
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,252 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="min-h-screen bg-gradient-to-br from-green-700 via-green-800 to-green-900 p-8">
|
|
||||||
<div class="max-w-4xl mx-auto">
|
|
||||||
<!-- Title -->
|
|
||||||
<h1 class="text-white text-3xl font-bold mb-8 text-center">Animation Test Page</h1>
|
|
||||||
|
|
||||||
<!-- Control Panel -->
|
|
||||||
<div class="bg-gray-900/80 rounded-lg p-6 mb-8">
|
|
||||||
<h2 class="text-white text-xl font-semibold mb-4">Controls</h2>
|
|
||||||
<div class="flex flex-wrap gap-3">
|
|
||||||
<button
|
|
||||||
@click="playPlayerCard"
|
|
||||||
class="px-4 py-2 bg-green-600 hover:bg-green-700 text-white rounded-lg font-medium transition-colors"
|
|
||||||
>
|
|
||||||
Play Player Card
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
@click="playOpponentCard"
|
|
||||||
class="px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded-lg font-medium transition-colors"
|
|
||||||
>
|
|
||||||
Play Opponent Card
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
@click="playBothCards"
|
|
||||||
class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg font-medium transition-colors"
|
|
||||||
>
|
|
||||||
Play Both Cards
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
@click="clearCards"
|
|
||||||
class="px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white rounded-lg font-medium transition-colors"
|
|
||||||
>
|
|
||||||
Clear Cards
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
@click="setPlayerWinner"
|
|
||||||
class="px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-white rounded-lg font-medium transition-colors"
|
|
||||||
>
|
|
||||||
Player Wins
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
@click="setOpponentWinner"
|
|
||||||
class="px-4 py-2 bg-rose-600 hover:bg-rose-700 text-white rounded-lg font-medium transition-colors"
|
|
||||||
>
|
|
||||||
Opponent Wins
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
@click="fullSequence"
|
|
||||||
class="px-4 py-2 bg-purple-600 hover:bg-purple-700 text-white rounded-lg font-medium transition-colors"
|
|
||||||
>
|
|
||||||
Full Trick Sequence
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
@click="collectTrickPlayerWins"
|
|
||||||
:disabled="!playerCard || !opponentCard"
|
|
||||||
class="px-4 py-2 bg-cyan-600 hover:bg-cyan-700 disabled:bg-gray-600 disabled:cursor-not-allowed text-white rounded-lg font-medium transition-colors"
|
|
||||||
>
|
|
||||||
Collect Trick (Player Wins)
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
@click="collectTrickOpponentWins"
|
|
||||||
:disabled="!playerCard || !opponentCard"
|
|
||||||
class="px-4 py-2 bg-orange-600 hover:bg-orange-700 disabled:bg-gray-600 disabled:cursor-not-allowed text-white rounded-lg font-medium transition-colors"
|
|
||||||
>
|
|
||||||
Collect Trick (Opponent Wins)
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Current State Display -->
|
|
||||||
<div class="mt-4 text-gray-300 text-sm">
|
|
||||||
<p>
|
|
||||||
<strong>Player Card:</strong>
|
|
||||||
{{ playerCard ? `${playerCard.suit}${playerCard.rank}` : 'None' }}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<strong>Opponent Card:</strong>
|
|
||||||
{{ opponentCard ? `${opponentCard.suit}${opponentCard.rank}` : 'None' }}
|
|
||||||
</p>
|
|
||||||
<p><strong>Winner:</strong> {{ winner || 'None' }}</p>
|
|
||||||
<p><strong>First Player:</strong> {{ firstPlayer || 'None' }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- PlayArea Component -->
|
|
||||||
<div class="bg-gray-800/50 rounded-lg p-8">
|
|
||||||
<h2 class="text-white text-xl font-semibold mb-4 text-center">Play Area</h2>
|
|
||||||
<PlayArea
|
|
||||||
:player-card="playerCard"
|
|
||||||
:opponent-card="opponentCard"
|
|
||||||
:winner="winner"
|
|
||||||
:first-player="firstPlayer"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Instructions -->
|
|
||||||
<div class="bg-gray-900/80 rounded-lg p-6 mt-8 text-gray-300">
|
|
||||||
<h3 class="text-white font-semibold mb-2">Instructions:</h3>
|
|
||||||
<ul class="list-disc list-inside space-y-1 text-sm">
|
|
||||||
<li><strong>Play Player Card:</strong> Animates a card from bottom (player's hand)</li>
|
|
||||||
<li><strong>Play Opponent Card:</strong> Animates a card from top (opponent's hand)</li>
|
|
||||||
<li><strong>Play Both Cards:</strong> Both cards animate in simultaneously</li>
|
|
||||||
<li><strong>Clear Cards:</strong> Triggers exit animations (cards slide out)</li>
|
|
||||||
<li>
|
|
||||||
<strong>Winner Buttons:</strong> Add glowing ring effect to winning card (play cards
|
|
||||||
first)
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Full Trick Sequence:</strong> Plays both cards → shows winner → clears (2 second
|
|
||||||
delay)
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Collect Trick (Player Wins):</strong> Sets player as winner, waits 1.5s, then
|
|
||||||
cards slide DOWN toward player area
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Collect Trick (Opponent Wins):</strong> Sets opponent as winner, waits 1.5s,
|
|
||||||
then cards slide UP toward opponent area
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref } from 'vue'
|
|
||||||
import PlayArea from '@/components/game/PlayArea.vue'
|
|
||||||
|
|
||||||
// Reactive state
|
|
||||||
const playerCard = ref(null)
|
|
||||||
const opponentCard = ref(null)
|
|
||||||
const winner = ref(null)
|
|
||||||
const firstPlayer = ref(null)
|
|
||||||
|
|
||||||
// Sample cards to test with
|
|
||||||
const sampleCards = [
|
|
||||||
{ suit: 'c', rank: 1 },
|
|
||||||
{ suit: 'c', rank: 7 },
|
|
||||||
{ suit: 'c', rank: 13 },
|
|
||||||
{ suit: 'e', rank: 11 },
|
|
||||||
{ suit: 'e', rank: 12 },
|
|
||||||
{ suit: 'o', rank: 2 },
|
|
||||||
{ suit: 'o', rank: 3 },
|
|
||||||
{ suit: 'p', rank: 4 },
|
|
||||||
{ suit: 'p', rank: 7 },
|
|
||||||
]
|
|
||||||
|
|
||||||
// Get random card
|
|
||||||
const getRandomCard = () => {
|
|
||||||
return sampleCards[Math.floor(Math.random() * sampleCards.length)]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Control functions
|
|
||||||
const playPlayerCard = () => {
|
|
||||||
if (!playerCard.value && !opponentCard.value) {
|
|
||||||
firstPlayer.value = 'player'
|
|
||||||
}
|
|
||||||
playerCard.value = getRandomCard()
|
|
||||||
winner.value = null
|
|
||||||
}
|
|
||||||
|
|
||||||
const playOpponentCard = () => {
|
|
||||||
if (!playerCard.value && !opponentCard.value) {
|
|
||||||
firstPlayer.value = 'opponent'
|
|
||||||
}
|
|
||||||
opponentCard.value = getRandomCard()
|
|
||||||
winner.value = null
|
|
||||||
}
|
|
||||||
|
|
||||||
const playBothCards = () => {
|
|
||||||
firstPlayer.value = 'player' // Player plays first in this scenario
|
|
||||||
playerCard.value = getRandomCard()
|
|
||||||
opponentCard.value = getRandomCard()
|
|
||||||
winner.value = null
|
|
||||||
}
|
|
||||||
|
|
||||||
const clearCards = () => {
|
|
||||||
playerCard.value = null
|
|
||||||
opponentCard.value = null
|
|
||||||
winner.value = null
|
|
||||||
firstPlayer.value = null
|
|
||||||
}
|
|
||||||
|
|
||||||
const setPlayerWinner = () => {
|
|
||||||
if (playerCard.value) {
|
|
||||||
winner.value = 'player'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const setOpponentWinner = () => {
|
|
||||||
if (opponentCard.value) {
|
|
||||||
winner.value = 'opponent'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const fullSequence = async () => {
|
|
||||||
// Clear first
|
|
||||||
clearCards()
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 500))
|
|
||||||
|
|
||||||
// Play both cards
|
|
||||||
playBothCards()
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
||||||
|
|
||||||
// Show winner (randomly)
|
|
||||||
winner.value = Math.random() > 0.5 ? 'player' : 'opponent'
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 2000))
|
|
||||||
|
|
||||||
// Clear cards
|
|
||||||
clearCards()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collect trick with player winning
|
|
||||||
const collectTrickPlayerWins = async () => {
|
|
||||||
if (!playerCard.value || !opponentCard.value) return
|
|
||||||
|
|
||||||
// Set player as winner
|
|
||||||
winner.value = 'player'
|
|
||||||
|
|
||||||
// Wait 1.5 seconds to show winner glow
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1500))
|
|
||||||
|
|
||||||
// Clear cards (triggers exit animation toward player)
|
|
||||||
playerCard.value = null
|
|
||||||
opponentCard.value = null
|
|
||||||
|
|
||||||
// Reset after animation completes
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 600))
|
|
||||||
winner.value = null
|
|
||||||
firstPlayer.value = null
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collect trick with opponent winning
|
|
||||||
const collectTrickOpponentWins = async () => {
|
|
||||||
if (!playerCard.value || !opponentCard.value) return
|
|
||||||
|
|
||||||
// Set opponent as winner
|
|
||||||
winner.value = 'opponent'
|
|
||||||
|
|
||||||
// Wait 1.5 seconds to show winner glow
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1500))
|
|
||||||
|
|
||||||
// Clear cards (triggers exit animation toward opponent)
|
|
||||||
playerCard.value = null
|
|
||||||
opponentCard.value = null
|
|
||||||
|
|
||||||
// Reset after animation completes
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 600))
|
|
||||||
winner.value = null
|
|
||||||
firstPlayer.value = null
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,275 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="min-h-screen bg-gradient-to-br from-green-700 via-green-800 to-green-900 p-8">
|
|
||||||
<div class="max-w-6xl mx-auto">
|
|
||||||
<!-- Title -->
|
|
||||||
<h1 class="text-white text-3xl font-bold mb-8 text-center">Card Dealing Test Page</h1>
|
|
||||||
|
|
||||||
<!-- Control Panel -->
|
|
||||||
<div class="bg-gray-900/80 rounded-lg p-6 mb-8">
|
|
||||||
<h2 class="text-white text-xl font-semibold mb-4">Controls</h2>
|
|
||||||
<div class="flex flex-wrap gap-3 mb-4">
|
|
||||||
<button
|
|
||||||
@click="startDealing"
|
|
||||||
:disabled="isDealing"
|
|
||||||
class="px-4 py-2 bg-green-600 hover:bg-green-700 disabled:bg-gray-600 disabled:cursor-not-allowed text-white rounded-lg font-medium transition-colors"
|
|
||||||
>
|
|
||||||
Start Dealing
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
@click="dealSingleCard"
|
|
||||||
:disabled="isDealing || cardsRemaining === 0"
|
|
||||||
class="px-4 py-2 bg-blue-600 hover:bg-blue-700 disabled:bg-gray-600 disabled:cursor-not-allowed text-white rounded-lg font-medium transition-colors"
|
|
||||||
>
|
|
||||||
Deal One Card
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
@click="reset"
|
|
||||||
class="px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded-lg font-medium transition-colors"
|
|
||||||
>
|
|
||||||
Reset
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Current State Display -->
|
|
||||||
<div class="grid grid-cols-3 gap-4 text-gray-300 text-sm">
|
|
||||||
<div>
|
|
||||||
<p><strong>Player Hand:</strong> {{ playerHand.length }} cards</p>
|
|
||||||
<p><strong>Opponent Hand:</strong> {{ opponentHand.length }} cards</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p><strong>Cards Remaining:</strong> {{ cardsRemaining }}</p>
|
|
||||||
<p><strong>Dealing:</strong> {{ isDealing ? 'Yes' : 'No' }}</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p><strong>Next Recipient:</strong> {{ nextRecipient }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Game Board Layout -->
|
|
||||||
<div class="grid grid-rows-[auto_1fr_auto] gap-8">
|
|
||||||
<!-- Opponent Hand (Top) -->
|
|
||||||
<div class="flex justify-center">
|
|
||||||
<div class="bg-gray-800/30 rounded-lg p-4">
|
|
||||||
<h3 class="text-white text-sm font-semibold mb-2 text-center">Opponent Hand</h3>
|
|
||||||
<PlayerHand
|
|
||||||
ref="opponentHandRef"
|
|
||||||
:cards="opponentHand"
|
|
||||||
:face-down="true"
|
|
||||||
:max-cards="3"
|
|
||||||
:playable-cards="[]"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Center Area (Deck) -->
|
|
||||||
<div class="flex items-center justify-center">
|
|
||||||
<div class="bg-gray-800/30 rounded-lg p-6">
|
|
||||||
<h3 class="text-white text-sm font-semibold mb-4 text-center">Deck</h3>
|
|
||||||
<DeckArea
|
|
||||||
ref="deckRef"
|
|
||||||
:trump-card="trumpCard"
|
|
||||||
:cards-remaining="cardsRemaining"
|
|
||||||
:is-empty="cardsRemaining === 0"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Player Hand (Bottom) -->
|
|
||||||
<div class="flex justify-center">
|
|
||||||
<div class="bg-gray-800/30 rounded-lg p-4">
|
|
||||||
<h3 class="text-white text-sm font-semibold mb-2 text-center">Your Hand</h3>
|
|
||||||
<PlayerHand
|
|
||||||
ref="playerHandRef"
|
|
||||||
:cards="playerHand"
|
|
||||||
:face-down="false"
|
|
||||||
:max-cards="3"
|
|
||||||
:playable-cards="[]"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Instructions -->
|
|
||||||
<div class="bg-gray-900/80 rounded-lg p-6 mt-8 text-gray-300">
|
|
||||||
<h3 class="text-white font-semibold mb-2">Instructions:</h3>
|
|
||||||
<ul class="list-disc list-inside space-y-1 text-sm">
|
|
||||||
<li><strong>Start Dealing:</strong> Automatically deals 6 cards (3 to each player) with 200ms delay</li>
|
|
||||||
<li><strong>Deal One Card:</strong> Manually deal a single card to the next recipient</li>
|
|
||||||
<li><strong>Reset:</strong> Clear all hands and reset the deck to 40 cards</li>
|
|
||||||
<li>Watch the FlyingCard animation as cards move from deck to hands</li>
|
|
||||||
<li>Notice the deck counter decreasing and the pulse effect</li>
|
|
||||||
<li>Cards appear in hands with a fade-in animation</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Flying Cards Container -->
|
|
||||||
<FlyingCard
|
|
||||||
v-for="flyingCard in flyingCards"
|
|
||||||
:key="flyingCard.id"
|
|
||||||
:card="flyingCard.card"
|
|
||||||
:start-position="flyingCard.startPosition"
|
|
||||||
:end-position="flyingCard.endPosition"
|
|
||||||
:duration="500"
|
|
||||||
:face-down="flyingCard.faceDown"
|
|
||||||
:delay="flyingCard.delay"
|
|
||||||
@complete="onFlyingCardComplete(flyingCard)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref, computed, nextTick } from 'vue'
|
|
||||||
import PlayerHand from '@/components/game/PlayerHand.vue'
|
|
||||||
import DeckArea from '@/components/game/DeckArea.vue'
|
|
||||||
import FlyingCard from '@/components/game/FlyingCard.vue'
|
|
||||||
|
|
||||||
// Refs for components
|
|
||||||
const playerHandRef = ref(null)
|
|
||||||
const opponentHandRef = ref(null)
|
|
||||||
const deckRef = ref(null)
|
|
||||||
|
|
||||||
// Game state
|
|
||||||
const playerHand = ref([])
|
|
||||||
const opponentHand = ref([])
|
|
||||||
const cardsRemaining = ref(40)
|
|
||||||
const isDealing = ref(false)
|
|
||||||
const flyingCards = ref([])
|
|
||||||
let flyingCardIdCounter = 0
|
|
||||||
|
|
||||||
// Trump card (fixed for testing)
|
|
||||||
const trumpCard = ref({ suit: 'o', rank: 7 })
|
|
||||||
|
|
||||||
// Sample deck
|
|
||||||
const sampleCards = [
|
|
||||||
{ suit: 'c', rank: 1 },
|
|
||||||
{ suit: 'c', rank: 2 },
|
|
||||||
{ suit: 'c', rank: 3 },
|
|
||||||
{ suit: 'c', rank: 4 },
|
|
||||||
{ suit: 'c', rank: 5 },
|
|
||||||
{ suit: 'c', rank: 6 },
|
|
||||||
{ suit: 'c', rank: 7 },
|
|
||||||
{ suit: 'c', rank: 11 },
|
|
||||||
{ suit: 'c', rank: 12 },
|
|
||||||
{ suit: 'c', rank: 13 },
|
|
||||||
{ suit: 'e', rank: 1 },
|
|
||||||
{ suit: 'e', rank: 2 },
|
|
||||||
{ suit: 'e', rank: 3 },
|
|
||||||
{ suit: 'e', rank: 4 },
|
|
||||||
{ suit: 'e', rank: 5 },
|
|
||||||
{ suit: 'e', rank: 6 },
|
|
||||||
{ suit: 'e', rank: 7 },
|
|
||||||
{ suit: 'e', rank: 11 },
|
|
||||||
{ suit: 'e', rank: 12 },
|
|
||||||
{ suit: 'e', rank: 13 },
|
|
||||||
{ suit: 'o', rank: 1 },
|
|
||||||
{ suit: 'o', rank: 2 },
|
|
||||||
{ suit: 'o', rank: 3 },
|
|
||||||
{ suit: 'o', rank: 4 },
|
|
||||||
{ suit: 'o', rank: 5 },
|
|
||||||
{ suit: 'o', rank: 6 },
|
|
||||||
{ suit: 'p', rank: 1 },
|
|
||||||
{ suit: 'p', rank: 2 },
|
|
||||||
{ suit: 'p', rank: 3 },
|
|
||||||
{ suit: 'p', rank: 4 },
|
|
||||||
{ suit: 'p', rank: 5 },
|
|
||||||
{ suit: 'p', rank: 6 },
|
|
||||||
{ suit: 'p', rank: 7 },
|
|
||||||
{ suit: 'p', rank: 11 },
|
|
||||||
{ suit: 'p', rank: 12 },
|
|
||||||
{ suit: 'p', rank: 13 },
|
|
||||||
]
|
|
||||||
|
|
||||||
// Computed
|
|
||||||
const nextRecipient = computed(() => {
|
|
||||||
const totalDealt = playerHand.value.length + opponentHand.value.length
|
|
||||||
return totalDealt % 2 === 0 ? 'Player' : 'Opponent'
|
|
||||||
})
|
|
||||||
|
|
||||||
// Get random card
|
|
||||||
const getRandomCard = () => {
|
|
||||||
return sampleCards[Math.floor(Math.random() * sampleCards.length)]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get position of an element
|
|
||||||
const getElementPosition = (el) => {
|
|
||||||
if (!el) return { x: 0, y: 0 }
|
|
||||||
const rect = el.$el?.getBoundingClientRect() || el.getBoundingClientRect()
|
|
||||||
return {
|
|
||||||
x: rect.left + rect.width / 2 - 64, // Center, minus half card width
|
|
||||||
y: rect.top + rect.height / 2 - 89, // Center, minus half card height
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deal a single card
|
|
||||||
const dealSingleCard = async () => {
|
|
||||||
if (cardsRemaining.value === 0) return
|
|
||||||
|
|
||||||
await nextTick()
|
|
||||||
|
|
||||||
// Determine recipient
|
|
||||||
const totalDealt = playerHand.value.length + opponentHand.value.length
|
|
||||||
const isPlayer = totalDealt % 2 === 0
|
|
||||||
|
|
||||||
// Get positions
|
|
||||||
const startPos = getElementPosition(deckRef.value)
|
|
||||||
const endPos = getElementPosition(isPlayer ? playerHandRef.value : opponentHandRef.value)
|
|
||||||
|
|
||||||
// Create flying card
|
|
||||||
const card = getRandomCard()
|
|
||||||
const flyingCard = {
|
|
||||||
id: flyingCardIdCounter++,
|
|
||||||
card,
|
|
||||||
startPosition: startPos,
|
|
||||||
endPosition: endPos,
|
|
||||||
faceDown: !isPlayer, // Face down for opponent
|
|
||||||
delay: 0,
|
|
||||||
recipient: isPlayer ? 'player' : 'opponent',
|
|
||||||
}
|
|
||||||
|
|
||||||
flyingCards.value.push(flyingCard)
|
|
||||||
cardsRemaining.value--
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flying card animation complete
|
|
||||||
const onFlyingCardComplete = (flyingCard) => {
|
|
||||||
// Add card to appropriate hand
|
|
||||||
if (flyingCard.recipient === 'player') {
|
|
||||||
playerHand.value.push(flyingCard.card)
|
|
||||||
} else {
|
|
||||||
opponentHand.value.push(flyingCard.card)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove flying card
|
|
||||||
flyingCards.value = flyingCards.value.filter((fc) => fc.id !== flyingCard.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start automatic dealing sequence
|
|
||||||
const startDealing = async () => {
|
|
||||||
if (isDealing.value) return
|
|
||||||
|
|
||||||
isDealing.value = true
|
|
||||||
|
|
||||||
// Deal 6 cards (3 to each player)
|
|
||||||
for (let i = 0; i < 6; i++) {
|
|
||||||
if (cardsRemaining.value === 0) break
|
|
||||||
|
|
||||||
await dealSingleCard()
|
|
||||||
|
|
||||||
// Wait 200ms before next card
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 700))
|
|
||||||
}
|
|
||||||
|
|
||||||
isDealing.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset everything
|
|
||||||
const reset = () => {
|
|
||||||
playerHand.value = []
|
|
||||||
opponentHand.value = []
|
|
||||||
cardsRemaining.value = 40
|
|
||||||
flyingCards.value = []
|
|
||||||
isDealing.value = false
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,444 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="min-h-screen bg-gradient-to-br from-green-700 via-green-800 to-green-900">
|
|
||||||
<!-- Control Panel (Floating) -->
|
|
||||||
<div class="fixed top-4 left-4 bg-gray-900/95 rounded-lg p-4 max-w-xs z-50 shadow-2xl">
|
|
||||||
<h3 class="text-white font-bold mb-3 text-sm">Test Controls</h3>
|
|
||||||
<div class="space-y-2">
|
|
||||||
<button
|
|
||||||
@click="dealInitialCards"
|
|
||||||
:disabled="isDealing"
|
|
||||||
class="w-full px-3 py-2 bg-blue-600 hover:bg-blue-700 disabled:bg-gray-600 text-white rounded text-xs font-medium transition-colors"
|
|
||||||
>
|
|
||||||
Deal Cards
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
@click="playRandomCards"
|
|
||||||
:disabled="playerHand.length === 0"
|
|
||||||
class="w-full px-3 py-2 bg-green-600 hover:bg-green-700 disabled:bg-gray-600 text-white rounded text-xs font-medium transition-colors"
|
|
||||||
>
|
|
||||||
Play Both Cards
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
@click="collectTrick"
|
|
||||||
:disabled="!currentTrick.playerCard || !currentTrick.opponentCard"
|
|
||||||
class="w-full px-3 py-2 bg-purple-600 hover:bg-purple-700 disabled:bg-gray-600 text-white rounded text-xs font-medium transition-colors"
|
|
||||||
>
|
|
||||||
Collect Trick
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
@click="revealTrump = !revealTrump"
|
|
||||||
class="w-full px-3 py-2 bg-amber-600 hover:bg-amber-700 text-white rounded text-xs font-medium transition-colors"
|
|
||||||
>
|
|
||||||
{{ revealTrump ? 'Hide' : 'Reveal' }} Trump
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
@click="showGameOver"
|
|
||||||
class="w-full px-3 py-2 bg-rose-600 hover:bg-rose-700 text-white rounded text-xs font-medium transition-colors"
|
|
||||||
>
|
|
||||||
Show Game Over
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
@click="resetGame"
|
|
||||||
class="w-full px-3 py-2 bg-gray-600 hover:bg-gray-700 text-white rounded text-xs font-medium transition-colors"
|
|
||||||
>
|
|
||||||
Reset Game
|
|
||||||
</button>
|
|
||||||
<div class="pt-2 border-t border-gray-700 mt-2 text-gray-400 text-xs">
|
|
||||||
<p><strong>Turn:</strong> {{ currentTurn === 'player' ? 'You' : 'Bot' }}</p>
|
|
||||||
<p><strong>Deck:</strong> {{ cardsRemaining }} cards</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Main Game Board -->
|
|
||||||
<div
|
|
||||||
class="grid grid-rows-[auto_auto_1fr_auto] grid-cols-[1fr_3fr_1fr] gap-6 p-8 min-h-screen"
|
|
||||||
>
|
|
||||||
<!-- Score Display (Top Left) -->
|
|
||||||
<div class="col-start-1 col-end-4 row-start-1 flex justify-start items-start">
|
|
||||||
<ScoreDisplay
|
|
||||||
:player-score="playerScore"
|
|
||||||
:opponent-score="opponentScore"
|
|
||||||
:current-turn="currentTurn"
|
|
||||||
:round-number="1"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Opponent Hand -->
|
|
||||||
<div class="col-start-2 col-end-3 row-start-2 flex justify-center">
|
|
||||||
<PlayerHand
|
|
||||||
ref="opponentHandRef"
|
|
||||||
:cards="opponentHand"
|
|
||||||
:face-down="true"
|
|
||||||
:max-cards="3"
|
|
||||||
:playable-cards="[]"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Play Area -->
|
|
||||||
<div class="col-start-2 col-end-3 row-start-3 flex items-center justify-center">
|
|
||||||
<PlayArea
|
|
||||||
:player-card="currentTrick.playerCard"
|
|
||||||
:opponent-card="currentTrick.opponentCard"
|
|
||||||
:winner="currentTrick.winner"
|
|
||||||
:first-player="currentTrick.firstPlayer"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Deck Area -->
|
|
||||||
<div class="col-start-3 col-end-4 row-start-3 flex items-center justify-center">
|
|
||||||
<DeckArea
|
|
||||||
ref="deckRef"
|
|
||||||
:trump-card="trumpCard"
|
|
||||||
:cards-remaining="cardsRemaining"
|
|
||||||
:is-empty="cardsRemaining === 0"
|
|
||||||
:reveal-trump="revealTrump"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Player Hand -->
|
|
||||||
<div class="col-start-2 col-end-3 row-start-4 flex justify-center">
|
|
||||||
<PlayerHand
|
|
||||||
ref="playerHandRef"
|
|
||||||
:cards="playerHand"
|
|
||||||
:face-down="false"
|
|
||||||
:max-cards="3"
|
|
||||||
:playable-cards="playableCards"
|
|
||||||
@card-clicked="handleCardClick"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Flying Cards Container -->
|
|
||||||
<FlyingCard
|
|
||||||
v-for="flyingCard in flyingCards"
|
|
||||||
:key="flyingCard.id"
|
|
||||||
:card="flyingCard.card"
|
|
||||||
:start-position="flyingCard.startPosition"
|
|
||||||
:end-position="flyingCard.endPosition"
|
|
||||||
:duration="500"
|
|
||||||
:face-down="flyingCard.faceDown"
|
|
||||||
:delay="flyingCard.delay"
|
|
||||||
@complete="onFlyingCardComplete(flyingCard)"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- Game Over Modal -->
|
|
||||||
<GameOver
|
|
||||||
:is-visible="gameOverVisible"
|
|
||||||
:winner="gameOverWinner"
|
|
||||||
:player-score="playerScore"
|
|
||||||
:opponent-score="opponentScore"
|
|
||||||
:stats="gameOverStats"
|
|
||||||
@close="gameOverVisible = false"
|
|
||||||
@play-again="resetGame"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref, computed, nextTick } from 'vue'
|
|
||||||
import GameBoard from '@/components/game/GameBoard.vue'
|
|
||||||
import ScoreDisplay from '@/components/game/ScoreDisplay.vue'
|
|
||||||
import PlayerHand from '@/components/game/PlayerHand.vue'
|
|
||||||
import PlayArea from '@/components/game/PlayArea.vue'
|
|
||||||
import DeckArea from '@/components/game/DeckArea.vue'
|
|
||||||
import FlyingCard from '@/components/game/FlyingCard.vue'
|
|
||||||
import GameOver from '@/components/game/GameOver.vue'
|
|
||||||
|
|
||||||
// Component refs
|
|
||||||
const playerHandRef = ref(null)
|
|
||||||
const opponentHandRef = ref(null)
|
|
||||||
const deckRef = ref(null)
|
|
||||||
|
|
||||||
// Game state
|
|
||||||
const playerHand = ref([])
|
|
||||||
const opponentHand = ref([])
|
|
||||||
const playerScore = ref(0)
|
|
||||||
const opponentScore = ref(0)
|
|
||||||
const currentTurn = ref('player')
|
|
||||||
const cardsRemaining = ref(40)
|
|
||||||
const isDealing = ref(false)
|
|
||||||
const revealTrump = ref(false)
|
|
||||||
|
|
||||||
// Trump card
|
|
||||||
const trumpCard = ref({ suit: 'o', rank: 7 })
|
|
||||||
|
|
||||||
// Current trick
|
|
||||||
const currentTrick = ref({
|
|
||||||
playerCard: null,
|
|
||||||
opponentCard: null,
|
|
||||||
winner: null,
|
|
||||||
firstPlayer: null,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Flying cards for dealing animation
|
|
||||||
const flyingCards = ref([])
|
|
||||||
let flyingCardIdCounter = 0
|
|
||||||
|
|
||||||
// Game Over
|
|
||||||
const gameOverVisible = ref(false)
|
|
||||||
const gameOverWinner = ref('player')
|
|
||||||
const gameOverStats = ref({
|
|
||||||
playerTricks: 0,
|
|
||||||
opponentTricks: 0,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Sample deck
|
|
||||||
const sampleCards = [
|
|
||||||
{ suit: 'c', rank: 1 },
|
|
||||||
{ suit: 'c', rank: 2 },
|
|
||||||
{ suit: 'c', rank: 3 },
|
|
||||||
{ suit: 'c', rank: 4 },
|
|
||||||
{ suit: 'c', rank: 5 },
|
|
||||||
{ suit: 'c', rank: 6 },
|
|
||||||
{ suit: 'c', rank: 7 },
|
|
||||||
{ suit: 'c', rank: 11 },
|
|
||||||
{ suit: 'c', rank: 12 },
|
|
||||||
{ suit: 'c', rank: 13 },
|
|
||||||
{ suit: 'e', rank: 1 },
|
|
||||||
{ suit: 'e', rank: 2 },
|
|
||||||
{ suit: 'e', rank: 3 },
|
|
||||||
{ suit: 'e', rank: 4 },
|
|
||||||
{ suit: 'e', rank: 5 },
|
|
||||||
{ suit: 'e', rank: 6 },
|
|
||||||
{ suit: 'e', rank: 7 },
|
|
||||||
{ suit: 'e', rank: 11 },
|
|
||||||
{ suit: 'e', rank: 12 },
|
|
||||||
{ suit: 'e', rank: 13 },
|
|
||||||
{ suit: 'o', rank: 1 },
|
|
||||||
{ suit: 'o', rank: 2 },
|
|
||||||
{ suit: 'o', rank: 3 },
|
|
||||||
{ suit: 'o', rank: 4 },
|
|
||||||
{ suit: 'o', rank: 5 },
|
|
||||||
{ suit: 'o', rank: 6 },
|
|
||||||
{ suit: 'p', rank: 1 },
|
|
||||||
{ suit: 'p', rank: 2 },
|
|
||||||
{ suit: 'p', rank: 3 },
|
|
||||||
{ suit: 'p', rank: 4 },
|
|
||||||
{ suit: 'p', rank: 5 },
|
|
||||||
{ suit: 'p', rank: 6 },
|
|
||||||
{ suit: 'p', rank: 7 },
|
|
||||||
{ suit: 'p', rank: 11 },
|
|
||||||
{ suit: 'p', rank: 12 },
|
|
||||||
{ suit: 'p', rank: 13 },
|
|
||||||
]
|
|
||||||
|
|
||||||
// Playable cards (all cards in hand for testing)
|
|
||||||
const playableCards = computed(() => {
|
|
||||||
return playerHand.value.map((_, index) => index)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Get random card
|
|
||||||
const getRandomCard = () => {
|
|
||||||
return sampleCards[Math.floor(Math.random() * sampleCards.length)]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get element position
|
|
||||||
const getElementPosition = (el) => {
|
|
||||||
if (!el) return { x: 0, y: 0 }
|
|
||||||
const rect = el.$el?.getBoundingClientRect() || el.getBoundingClientRect()
|
|
||||||
return {
|
|
||||||
x: rect.left + rect.width / 2 - 64,
|
|
||||||
y: rect.top + rect.height / 2 - 89,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deal a single card
|
|
||||||
const dealSingleCard = async (isPlayer) => {
|
|
||||||
if (cardsRemaining.value === 0) return
|
|
||||||
|
|
||||||
await nextTick()
|
|
||||||
|
|
||||||
const startPos = getElementPosition(deckRef.value)
|
|
||||||
const endPos = getElementPosition(isPlayer ? playerHandRef.value : opponentHandRef.value)
|
|
||||||
|
|
||||||
const card = getRandomCard()
|
|
||||||
const flyingCard = {
|
|
||||||
id: flyingCardIdCounter++,
|
|
||||||
card,
|
|
||||||
startPosition: startPos,
|
|
||||||
endPosition: endPos,
|
|
||||||
faceDown: !isPlayer,
|
|
||||||
delay: 0,
|
|
||||||
recipient: isPlayer ? 'player' : 'opponent',
|
|
||||||
}
|
|
||||||
|
|
||||||
flyingCards.value.push(flyingCard)
|
|
||||||
cardsRemaining.value--
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flying card complete
|
|
||||||
const onFlyingCardComplete = (flyingCard) => {
|
|
||||||
if (flyingCard.recipient === 'player') {
|
|
||||||
playerHand.value.push(flyingCard.card)
|
|
||||||
} else {
|
|
||||||
opponentHand.value.push(flyingCard.card)
|
|
||||||
}
|
|
||||||
flyingCards.value = flyingCards.value.filter((fc) => fc.id !== flyingCard.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deal initial cards
|
|
||||||
const dealInitialCards = async () => {
|
|
||||||
if (isDealing.value) return
|
|
||||||
isDealing.value = true
|
|
||||||
|
|
||||||
// Reveal trump at start
|
|
||||||
revealTrump.value = true
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
||||||
revealTrump.value = false
|
|
||||||
|
|
||||||
// Deal 6 cards (3 to each player)
|
|
||||||
for (let i = 0; i < 6; i++) {
|
|
||||||
if (cardsRemaining.value === 0) break
|
|
||||||
const isPlayer = i % 2 === 0
|
|
||||||
await dealSingleCard(isPlayer)
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 700))
|
|
||||||
}
|
|
||||||
|
|
||||||
isDealing.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle card click from player hand
|
|
||||||
const handleCardClick = ({ card, index }) => {
|
|
||||||
if (currentTurn.value !== 'player') return
|
|
||||||
if (currentTrick.value.playerCard) return
|
|
||||||
|
|
||||||
// Set first player if no cards played yet
|
|
||||||
if (!currentTrick.value.opponentCard) {
|
|
||||||
currentTrick.value.firstPlayer = 'player'
|
|
||||||
}
|
|
||||||
|
|
||||||
// Play the card
|
|
||||||
currentTrick.value.playerCard = card
|
|
||||||
playerHand.value.splice(index, 1)
|
|
||||||
|
|
||||||
// Switch turn
|
|
||||||
currentTurn.value = 'opponent'
|
|
||||||
|
|
||||||
// Auto-play opponent card after delay
|
|
||||||
setTimeout(() => {
|
|
||||||
if (opponentHand.value.length > 0 && !currentTrick.value.opponentCard) {
|
|
||||||
playOpponentCard()
|
|
||||||
}
|
|
||||||
}, 1000)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Play opponent card
|
|
||||||
const playOpponentCard = () => {
|
|
||||||
if (opponentHand.value.length === 0) return
|
|
||||||
|
|
||||||
// Set first player if no cards played yet
|
|
||||||
if (!currentTrick.value.playerCard) {
|
|
||||||
currentTrick.value.firstPlayer = 'opponent'
|
|
||||||
}
|
|
||||||
|
|
||||||
// Play random card
|
|
||||||
const randomIndex = Math.floor(Math.random() * opponentHand.value.length)
|
|
||||||
const card = opponentHand.value[randomIndex]
|
|
||||||
currentTrick.value.opponentCard = card
|
|
||||||
opponentHand.value.splice(randomIndex, 1)
|
|
||||||
|
|
||||||
// Switch turn
|
|
||||||
currentTurn.value = 'player'
|
|
||||||
}
|
|
||||||
|
|
||||||
// Play random cards from both players
|
|
||||||
const playRandomCards = () => {
|
|
||||||
if (playerHand.value.length === 0) return
|
|
||||||
|
|
||||||
// Player plays first
|
|
||||||
currentTrick.value.firstPlayer = 'player'
|
|
||||||
const playerCard = playerHand.value[0]
|
|
||||||
currentTrick.value.playerCard = playerCard
|
|
||||||
playerHand.value.shift()
|
|
||||||
|
|
||||||
// Opponent plays after delay
|
|
||||||
setTimeout(() => {
|
|
||||||
if (opponentHand.value.length > 0) {
|
|
||||||
const opponentCard = opponentHand.value[0]
|
|
||||||
currentTrick.value.opponentCard = opponentCard
|
|
||||||
opponentHand.value.shift()
|
|
||||||
}
|
|
||||||
}, 600)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collect trick
|
|
||||||
const collectTrick = async () => {
|
|
||||||
if (!currentTrick.value.playerCard || !currentTrick.value.opponentCard) return
|
|
||||||
|
|
||||||
// Randomly determine winner
|
|
||||||
const winner = Math.random() > 0.5 ? 'player' : 'opponent'
|
|
||||||
currentTrick.value.winner = winner
|
|
||||||
|
|
||||||
// Wait to show winner glow
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1500))
|
|
||||||
|
|
||||||
// Update score (random points)
|
|
||||||
const points = Math.floor(Math.random() * 15) + 5
|
|
||||||
if (winner === 'player') {
|
|
||||||
playerScore.value += points
|
|
||||||
gameOverStats.value.playerTricks++
|
|
||||||
} else {
|
|
||||||
opponentScore.value += points
|
|
||||||
gameOverStats.value.opponentTricks++
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear cards (triggers collection animation)
|
|
||||||
currentTrick.value.playerCard = null
|
|
||||||
currentTrick.value.opponentCard = null
|
|
||||||
|
|
||||||
// Reset after animation
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 600))
|
|
||||||
currentTrick.value.winner = null
|
|
||||||
currentTrick.value.firstPlayer = null
|
|
||||||
|
|
||||||
// Deal new cards if deck has cards
|
|
||||||
if (cardsRemaining.value >= 2 && playerHand.value.length < 3) {
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 500))
|
|
||||||
await dealSingleCard(winner === 'player')
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 700))
|
|
||||||
await dealSingleCard(winner === 'opponent')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Switch turn to winner
|
|
||||||
currentTurn.value = winner
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show game over
|
|
||||||
const showGameOver = () => {
|
|
||||||
// Make player win for demo
|
|
||||||
if (playerScore.value <= opponentScore.value) {
|
|
||||||
playerScore.value = opponentScore.value + 10
|
|
||||||
}
|
|
||||||
gameOverWinner.value =
|
|
||||||
playerScore.value > opponentScore.value
|
|
||||||
? 'player'
|
|
||||||
: opponentScore.value > playerScore.value
|
|
||||||
? 'opponent'
|
|
||||||
: 'draw'
|
|
||||||
gameOverVisible.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset game
|
|
||||||
const resetGame = () => {
|
|
||||||
playerHand.value = []
|
|
||||||
opponentHand.value = []
|
|
||||||
playerScore.value = 0
|
|
||||||
opponentScore.value = 0
|
|
||||||
currentTurn.value = 'player'
|
|
||||||
cardsRemaining.value = 40
|
|
||||||
currentTrick.value = {
|
|
||||||
playerCard: null,
|
|
||||||
opponentCard: null,
|
|
||||||
winner: null,
|
|
||||||
firstPlayer: null,
|
|
||||||
}
|
|
||||||
flyingCards.value = []
|
|
||||||
gameOverVisible.value = false
|
|
||||||
gameOverStats.value = {
|
|
||||||
playerTricks: 0,
|
|
||||||
opponentTricks: 0,
|
|
||||||
}
|
|
||||||
revealTrump.value = false
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -603,65 +603,6 @@
|
|||||||
v-else-if="activeTab === 'matches'"
|
v-else-if="activeTab === 'matches'"
|
||||||
class="animate-in fade-in slide-in-from-bottom-2 duration-300"
|
class="animate-in fade-in slide-in-from-bottom-2 duration-300"
|
||||||
>
|
>
|
||||||
<div class="p-4 bg-gray-50 border-b border-gray-300 flex flex-wrap gap-4 items-end">
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<label class="text-[10px] font-bold uppercase text-gray-400 tracking-widest"
|
|
||||||
>From</label
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="date"
|
|
||||||
v-model="filters.match_date_from"
|
|
||||||
@change="resetAndFetch"
|
|
||||||
class="bg-white border border-gray-300 p-2 text-xs font-bold uppercase outline-none focus:border-black"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<label class="text-[10px] font-bold uppercase text-gray-400 tracking-widest"
|
|
||||||
>To</label
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="date"
|
|
||||||
v-model="filters.match_date_to"
|
|
||||||
@change="resetAndFetch"
|
|
||||||
class="bg-white border border-gray-300 p-2 text-xs font-bold uppercase outline-none focus:border-black"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<label class="text-[10px] font-bold uppercase text-gray-400 tracking-widest"
|
|
||||||
>Variant</label
|
|
||||||
>
|
|
||||||
<select
|
|
||||||
v-model="filters.match_type"
|
|
||||||
@change="resetAndFetch"
|
|
||||||
class="bg-white border border-gray-300 p-2 text-xs font-bold uppercase outline-none focus:border-black min-w-[100px]"
|
|
||||||
>
|
|
||||||
<option :value="null">All</option>
|
|
||||||
<option value="3">Bisca 3</option>
|
|
||||||
<option value="9">Bisca 9</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<label class="text-[10px] font-bold uppercase text-gray-400 tracking-widest"
|
|
||||||
>Outcome</label
|
|
||||||
>
|
|
||||||
<select
|
|
||||||
v-model="filters.match_outcome"
|
|
||||||
@change="resetAndFetch"
|
|
||||||
class="bg-white border border-gray-300 p-2 text-xs font-bold uppercase outline-none focus:border-black min-w-[100px]"
|
|
||||||
>
|
|
||||||
<option :value="null">All</option>
|
|
||||||
<option value="win">Win</option>
|
|
||||||
<option value="draw">Draw</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
@click="clearMatchFilters"
|
|
||||||
class="text-[10px] font-bold uppercase underline hover:text-gray-600 mb-2 ml-auto"
|
|
||||||
>
|
|
||||||
Clear
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
<div
|
<div
|
||||||
id="table-scroll-container"
|
id="table-scroll-container"
|
||||||
@@ -670,20 +611,34 @@
|
|||||||
<table class="w-full text-left border-collapse sticky-header">
|
<table class="w-full text-left border-collapse sticky-header">
|
||||||
<thead class="bg-gray-50 border-b border-gray-300">
|
<thead class="bg-gray-50 border-b border-gray-300">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="p-4 text-[10px] font-bold uppercase tracking-widest text-gray-500">
|
<th
|
||||||
|
class="p-4 text-[10px] font-bold uppercase tracking-widest text-gray-500"
|
||||||
|
@click="handleSort('date')"
|
||||||
|
>
|
||||||
Match Date
|
Match Date
|
||||||
|
<span>{{ filters.match_sort_direction === 'desc' ? ' ↓' : ' ↑' }}</span>
|
||||||
</th>
|
</th>
|
||||||
<th class="p-4 text-[10px] font-bold uppercase tracking-widest text-gray-500">
|
<th class="p-4 text-[10px] font-bold uppercase tracking-widest text-gray-500">
|
||||||
Matchup (P1 vs P2)
|
Matchup (P1 vs P2)
|
||||||
</th>
|
</th>
|
||||||
<th class="p-4 text-[10px] font-bold uppercase tracking-widest text-gray-500">
|
<th
|
||||||
|
class="p-4 text-[10px] font-bold uppercase tracking-widest text-gray-500"
|
||||||
|
@click="handleSort('type')"
|
||||||
|
>
|
||||||
Variant
|
Variant
|
||||||
|
<span v-if="filters.match_type"> (Bisca {{ filters.match_type }})</span>
|
||||||
|
<span v-else> ↕</span>
|
||||||
</th>
|
</th>
|
||||||
<th class="p-4 text-[10px] font-bold uppercase tracking-widest text-gray-500">
|
<th class="p-4 text-[10px] font-bold uppercase tracking-widest text-gray-500">
|
||||||
Outcome
|
Outcome
|
||||||
</th>
|
</th>
|
||||||
<th class="p-4 text-[10px] font-bold uppercase tracking-widest text-gray-500">
|
<th
|
||||||
|
class="p-4 text-[10px] font-bold uppercase tracking-widest text-gray-500"
|
||||||
|
@click="handleSort('pot')"
|
||||||
|
>
|
||||||
Total Pot
|
Total Pot
|
||||||
|
<span v-if="filters.pot"> ({{ filters.pot }})</span>
|
||||||
|
<span v-else> ↕</span>
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -882,6 +837,8 @@ const filters = reactive({
|
|||||||
match_outcome: null,
|
match_outcome: null,
|
||||||
match_date_from: '',
|
match_date_from: '',
|
||||||
match_date_to: '',
|
match_date_to: '',
|
||||||
|
match_sort_direction: null,
|
||||||
|
pot: null,
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(activeTab, async () => {
|
watch(activeTab, async () => {
|
||||||
@@ -987,19 +944,6 @@ const filteredUsers = computed(() => {
|
|||||||
return users.value.filter((user) => user.id !== useAuthStore().currentUserID)
|
return users.value.filter((user) => user.id !== useAuthStore().currentUserID)
|
||||||
})
|
})
|
||||||
|
|
||||||
const resetAndFetch = async () => {
|
|
||||||
resetPagination()
|
|
||||||
await fetchData()
|
|
||||||
}
|
|
||||||
|
|
||||||
const clearMatchFilters = async () => {
|
|
||||||
filters.match_type = null
|
|
||||||
filters.match_outcome = null
|
|
||||||
filters.match_date_from = ''
|
|
||||||
filters.match_date_to = ''
|
|
||||||
await resetAndFetch()
|
|
||||||
}
|
|
||||||
|
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
if (isLoading.value) return
|
if (isLoading.value) return
|
||||||
const currentTab = activeTab.value
|
const currentTab = activeTab.value
|
||||||
@@ -1039,10 +983,9 @@ const fetchData = async () => {
|
|||||||
} else if (currentTab === 'matches') {
|
} else if (currentTab === 'matches') {
|
||||||
const params = {
|
const params = {
|
||||||
page: pagination[currentTab].page,
|
page: pagination[currentTab].page,
|
||||||
|
sort_direction: filters.match_sort_direction,
|
||||||
type: filters.match_type,
|
type: filters.match_type,
|
||||||
outcome: filters.match_outcome,
|
pot: filters.pot,
|
||||||
date_from: filters.match_date_from || undefined,
|
|
||||||
date_to: filters.match_date_to || undefined,
|
|
||||||
}
|
}
|
||||||
response = await apiStore.getAdminMatches(params)
|
response = await apiStore.getAdminMatches(params)
|
||||||
matches.value.push(...response.data.data)
|
matches.value.push(...response.data.data)
|
||||||
@@ -1080,6 +1023,19 @@ const handleSort = async (column) => {
|
|||||||
else if (filters.game_type === '9') filters.game_type = '3'
|
else if (filters.game_type === '9') filters.game_type = '3'
|
||||||
else filters.game_type = null
|
else filters.game_type = null
|
||||||
}
|
}
|
||||||
|
} else if (activeTab.value === 'matches') {
|
||||||
|
if (column === 'type') {
|
||||||
|
filters.match_type =
|
||||||
|
filters.match_type === null ? '9' : filters.match_type === '9' ? '3' : null
|
||||||
|
} else if (column === 'date') {
|
||||||
|
filters.pot = null // Reset pot sort when sorting by date
|
||||||
|
filters.match_sort_direction = filters.match_sort_direction === 'desc' ? 'asc' : 'desc'
|
||||||
|
} else if (column === 'pot') {
|
||||||
|
filters.match_sort_direction = null // Reset date sort when sorting by pot
|
||||||
|
if (filters.pot === null) filters.pot = 'desc'
|
||||||
|
else if (filters.pot === 'desc') filters.pot = 'asc'
|
||||||
|
else filters.pot = 'desc'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resetPagination()
|
resetPagination()
|
||||||
|
|||||||
@@ -526,21 +526,6 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<label class="text-xs font-medium text-muted-foreground">Outcome</label>
|
|
||||||
<select
|
|
||||||
v-model="filters.outcome"
|
|
||||||
@change="resetAndFetch"
|
|
||||||
class="bg-background border rounded-md px-3 py-2 text-sm outline-none"
|
|
||||||
>
|
|
||||||
<option value="all">All Results</option>
|
|
||||||
<option value="win">Win</option>
|
|
||||||
<option value="loss">Loss</option>
|
|
||||||
<option value="draw">Draw</option>
|
|
||||||
<option value="forfeit">Forfeit</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<label class="text-xs font-medium text-muted-foreground">From</label>
|
<label class="text-xs font-medium text-muted-foreground">From</label>
|
||||||
<input
|
<input
|
||||||
@@ -577,7 +562,6 @@
|
|||||||
class="bg-background border rounded-md px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-purple-500"
|
class="bg-background border rounded-md px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-purple-500"
|
||||||
>
|
>
|
||||||
<option value="began_at">Date</option>
|
<option value="began_at">Date</option>
|
||||||
<option value="outcome">Outcome</option>
|
|
||||||
<option value="total_time">Duration</option>
|
<option value="total_time">Duration</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
@@ -666,21 +650,6 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<label class="text-xs font-medium text-muted-foreground">Outcome</label>
|
|
||||||
<select
|
|
||||||
v-model="filters.outcome"
|
|
||||||
@change="resetAndFetch"
|
|
||||||
class="bg-background border rounded-md px-3 py-2 text-sm outline-none"
|
|
||||||
>
|
|
||||||
<option value="all">All Results</option>
|
|
||||||
<option value="win">Win</option>
|
|
||||||
<option value="loss">Loss</option>
|
|
||||||
<option value="draw">Draw</option>
|
|
||||||
<option value="forfeit">Forfeit</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<label class="text-xs font-medium text-muted-foreground">From</label>
|
<label class="text-xs font-medium text-muted-foreground">From</label>
|
||||||
<input
|
<input
|
||||||
@@ -717,7 +686,6 @@
|
|||||||
class="bg-background border rounded-md px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-purple-500"
|
class="bg-background border rounded-md px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-purple-500"
|
||||||
>
|
>
|
||||||
<option value="began_at">Date</option>
|
<option value="began_at">Date</option>
|
||||||
<option value="outcome">Outcome</option>
|
|
||||||
<option value="total_time">Duration</option>
|
<option value="total_time">Duration</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
@@ -1083,7 +1051,6 @@ const pagination = ref({
|
|||||||
const filters = ref({
|
const filters = ref({
|
||||||
type: 'all',
|
type: 'all',
|
||||||
variant: 'all',
|
variant: 'all',
|
||||||
outcome: 'all',
|
|
||||||
startDate: '',
|
startDate: '',
|
||||||
endDate: '',
|
endDate: '',
|
||||||
sort: {
|
sort: {
|
||||||
@@ -1164,7 +1131,6 @@ const fetchData = async () => {
|
|||||||
const gameMatchParams = {
|
const gameMatchParams = {
|
||||||
page: state.page,
|
page: state.page,
|
||||||
type: filters.value.variant !== 'all' ? filters.value.variant : undefined,
|
type: filters.value.variant !== 'all' ? filters.value.variant : undefined,
|
||||||
outcome: filters.value.outcome !== 'all' ? filters.value.outcome : undefined,
|
|
||||||
status: 'Ended',
|
status: 'Ended',
|
||||||
date_from: filters.value.startDate || undefined,
|
date_from: filters.value.startDate || undefined,
|
||||||
date_to: filters.value.endDate || undefined,
|
date_to: filters.value.endDate || undefined,
|
||||||
@@ -1324,7 +1290,6 @@ const clearFilters = () => {
|
|||||||
|
|
||||||
if (isGameOrMatch) {
|
if (isGameOrMatch) {
|
||||||
filters.value.variant = 'all'
|
filters.value.variant = 'all'
|
||||||
filters.value.outcome = 'all'
|
|
||||||
} else {
|
} else {
|
||||||
filters.value.type = 'all'
|
filters.value.type = 'all'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,7 @@
|
|||||||
import HomePage from '@/pages/home/HomePage.vue'
|
import HomePage from '@/pages/home/HomePage.vue'
|
||||||
import LoginPage from '@/pages/login/LoginPage.vue'
|
import LoginPage from '@/pages/login/LoginPage.vue'
|
||||||
import LaravelPage from '@/pages/testing/LaravelPage.vue'
|
|
||||||
import WebsocketsPage from '@/pages/testing/WebsocketsPage.vue'
|
|
||||||
import UserPage from '@/pages/user/UserPage.vue'
|
import UserPage from '@/pages/user/UserPage.vue'
|
||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import TestAnimations from '@/pages/TestAnimations.vue'
|
|
||||||
import TestDealing from '@/pages/TestDealing.vue'
|
|
||||||
import TestAllAnimations from '@/pages/TestAllAnimations.vue'
|
|
||||||
import TestGameBoard from '@/pages/TestGameBoard.vue'
|
|
||||||
import SinglePlayerGamePage from '@/pages/game/SinglePlayerGamePage.vue'
|
import SinglePlayerGamePage from '@/pages/game/SinglePlayerGamePage.vue'
|
||||||
import MultiplayerGamePage from '@/pages/game/MultiplayerGamePage.vue'
|
import MultiplayerGamePage from '@/pages/game/MultiplayerGamePage.vue'
|
||||||
import RegisterPage from '@/pages/register/RegisterPage.vue'
|
import RegisterPage from '@/pages/register/RegisterPage.vue'
|
||||||
@@ -22,7 +16,7 @@ const router = createRouter({
|
|||||||
path: '/',
|
path: '/',
|
||||||
name: 'home',
|
name: 'home',
|
||||||
component: HomePage,
|
component: HomePage,
|
||||||
meta: { requiresAdmin: false }
|
meta: { requiresAdmin: false },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/login',
|
path: '/login',
|
||||||
@@ -52,7 +46,7 @@ const router = createRouter({
|
|||||||
path: '/game/multiplayer/:id',
|
path: '/game/multiplayer/:id',
|
||||||
name: 'multiplayer-game',
|
name: 'multiplayer-game',
|
||||||
component: MultiplayerGamePage,
|
component: MultiplayerGamePage,
|
||||||
meta: { requiresAuth: true }
|
meta: { requiresAuth: true },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/user',
|
path: '/user',
|
||||||
@@ -70,35 +64,6 @@ const router = createRouter({
|
|||||||
component: AdminPage,
|
component: AdminPage,
|
||||||
meta: { requiresAuth: true, requiresAdmin: true },
|
meta: { requiresAuth: true, requiresAdmin: true },
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: '/testing',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: 'laravel',
|
|
||||||
component: LaravelPage,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'websockets',
|
|
||||||
component: WebsocketsPage,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'animations',
|
|
||||||
component: TestAnimations,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'dealing',
|
|
||||||
component: TestDealing,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'all-animations',
|
|
||||||
component: TestAllAnimations,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'gameboard',
|
|
||||||
component: TestGameBoard,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -181,12 +181,9 @@ export const useAPIStore = defineStore('api', () => {
|
|||||||
const getAdminMatches = (params = {}) => {
|
const getAdminMatches = (params = {}) => {
|
||||||
const queryParams = new URLSearchParams({
|
const queryParams = new URLSearchParams({
|
||||||
page: params.page || 1,
|
page: params.page || 1,
|
||||||
|
...(params.sort_direction == 'asc' && { sort_direction: 'asc' }),
|
||||||
...(params.type && { type: params.type }),
|
...(params.type && { type: params.type }),
|
||||||
...(params.outcome && { outcome: params.outcome }),
|
...(params.pot && { pot: params.pot }),
|
||||||
...(params.date_from && { date_from: params.date_from }),
|
|
||||||
...(params.date_to && { date_to: params.date_to }),
|
|
||||||
sort_by: params.sort_by || 'began_at',
|
|
||||||
sort_direction: params.sort_direction || 'desc',
|
|
||||||
}).toString()
|
}).toString()
|
||||||
|
|
||||||
return axios.get(`${API_BASE_URL}/matches?${queryParams}`)
|
return axios.get(`${API_BASE_URL}/matches?${queryParams}`)
|
||||||
|
|||||||
Reference in New Issue
Block a user