275 lines
8.4 KiB
TypeScript
275 lines
8.4 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardDescription,
|
|
CardHeader,
|
|
CardTitle,
|
|
} from "@/components/ui/card";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { Search, User, CheckCircle, Coins } from "lucide-react";
|
|
|
|
interface UserResult {
|
|
_id: string;
|
|
name: string;
|
|
username: string;
|
|
email: string;
|
|
role: string;
|
|
referent?: string;
|
|
prenom?: string;
|
|
nom?: string;
|
|
createdAt: string;
|
|
}
|
|
|
|
interface BalanceInfo {
|
|
tokenCredits: number;
|
|
lastRefill?: string;
|
|
}
|
|
|
|
export default function AddCreditsSingleUser() {
|
|
const [searchTerm, setSearchTerm] = useState("");
|
|
const [searchResults, setSearchResults] = useState<UserResult[]>([]);
|
|
const [selectedUser, setSelectedUser] = useState<UserResult | null>(null);
|
|
const [userBalance, setUserBalance] = useState<BalanceInfo | null>(null);
|
|
const [searching, setSearching] = useState(false);
|
|
const [loadingBalance, setLoadingBalance] = useState(false);
|
|
const [adding, setAdding] = useState(false);
|
|
const [success, setSuccess] = useState<{ newBalance: number } | null>(null);
|
|
|
|
const searchUsers = async () => {
|
|
if (!searchTerm.trim()) return;
|
|
|
|
setSearching(true);
|
|
setSelectedUser(null);
|
|
setUserBalance(null);
|
|
setSuccess(null);
|
|
|
|
try {
|
|
const response = await fetch(
|
|
`/api/collections/users?search=${encodeURIComponent(searchTerm)}&limit=10`
|
|
);
|
|
const data = await response.json();
|
|
|
|
if (data.data) {
|
|
setSearchResults(data.data);
|
|
}
|
|
} catch (error) {
|
|
console.error("Erreur lors de la recherche:", error);
|
|
} finally {
|
|
setSearching(false);
|
|
}
|
|
};
|
|
|
|
const selectUser = async (user: UserResult) => {
|
|
setSelectedUser(user);
|
|
setSearchResults([]);
|
|
setSuccess(null);
|
|
setLoadingBalance(true);
|
|
|
|
try {
|
|
// Récupérer la balance de l'utilisateur
|
|
const response = await fetch(
|
|
`/api/collections/balances?search=${user._id}&limit=1`
|
|
);
|
|
const data = await response.json();
|
|
|
|
if (data.data && data.data.length > 0) {
|
|
setUserBalance({
|
|
tokenCredits: data.data[0].tokenCredits || 0,
|
|
lastRefill: data.data[0].lastRefill,
|
|
});
|
|
} else {
|
|
setUserBalance({ tokenCredits: 0 });
|
|
}
|
|
} catch (error) {
|
|
console.error("Erreur lors de la récupération de la balance:", error);
|
|
setUserBalance({ tokenCredits: 0 });
|
|
} finally {
|
|
setLoadingBalance(false);
|
|
}
|
|
};
|
|
|
|
const addCredits = async () => {
|
|
if (!selectedUser) return;
|
|
|
|
if (
|
|
!confirm(
|
|
`Êtes-vous sûr de vouloir ajouter 3 millions de crédits à ${selectedUser.name || selectedUser.email} ?`
|
|
)
|
|
) {
|
|
return;
|
|
}
|
|
|
|
setAdding(true);
|
|
|
|
try {
|
|
const response = await fetch("/api/add-credits-single", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({ userId: selectedUser._id }),
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
setSuccess({ newBalance: data.newBalance });
|
|
setUserBalance({ tokenCredits: data.newBalance });
|
|
} else {
|
|
alert("Erreur: " + (data.error || data.message));
|
|
}
|
|
} catch (error) {
|
|
console.error("Erreur lors de l'ajout des crédits:", error);
|
|
alert("Erreur lors de l'ajout des crédits");
|
|
} finally {
|
|
setAdding(false);
|
|
}
|
|
};
|
|
|
|
const reset = () => {
|
|
setSearchTerm("");
|
|
setSearchResults([]);
|
|
setSelectedUser(null);
|
|
setUserBalance(null);
|
|
setSuccess(null);
|
|
};
|
|
|
|
return (
|
|
<Card className="w-full">
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<Coins className="h-5 w-5" />
|
|
Ajouter des Crédits à un Utilisateur
|
|
</CardTitle>
|
|
<CardDescription>
|
|
Rechercher un utilisateur et lui ajouter 3 millions de tokens
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
{/* Barre de recherche */}
|
|
<div className="flex gap-2">
|
|
<Input
|
|
placeholder="Rechercher par nom, email ou username..."
|
|
value={searchTerm}
|
|
onChange={(e) => setSearchTerm(e.target.value)}
|
|
onKeyDown={(e) => e.key === "Enter" && searchUsers()}
|
|
/>
|
|
<Button onClick={searchUsers} disabled={searching}>
|
|
<Search className="h-4 w-4 mr-2" />
|
|
{searching ? "..." : "Rechercher"}
|
|
</Button>
|
|
</div>
|
|
|
|
{/* Résultats de recherche */}
|
|
{searchResults.length > 0 && (
|
|
<div className="border rounded-lg divide-y">
|
|
{searchResults.map((user) => (
|
|
<div
|
|
key={user._id}
|
|
className="p-3 hover:bg-gray-50 cursor-pointer flex items-center justify-between"
|
|
onClick={() => selectUser(user)}
|
|
>
|
|
<div className="flex items-center gap-3">
|
|
<User className="h-5 w-5 text-gray-400" />
|
|
<div>
|
|
<p className="font-medium">{user.name || user.username}</p>
|
|
<p className="text-sm text-gray-500">{user.email}</p>
|
|
</div>
|
|
</div>
|
|
<Badge variant="outline">{user.role}</Badge>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{/* Utilisateur sélectionné */}
|
|
{selectedUser && (
|
|
<div className="border rounded-lg p-4 bg-blue-50 space-y-3">
|
|
<h4 className="font-semibold text-blue-800 flex items-center gap-2">
|
|
<CheckCircle className="h-5 w-5" />
|
|
Utilisateur sélectionné
|
|
</h4>
|
|
|
|
<div className="grid grid-cols-2 gap-3 text-sm">
|
|
<div>
|
|
<span className="text-gray-600">ID:</span>
|
|
<p className="font-mono text-xs bg-white p-1 rounded mt-1">
|
|
{selectedUser._id}
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<span className="text-gray-600">Nom:</span>
|
|
<p className="font-medium">
|
|
{selectedUser.name || selectedUser.username}
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<span className="text-gray-600">Email:</span>
|
|
<p className="font-medium">{selectedUser.email}</p>
|
|
</div>
|
|
<div>
|
|
<span className="text-gray-600">Rôle:</span>
|
|
<Badge variant="secondary" className="ml-2">
|
|
{selectedUser.role}
|
|
</Badge>
|
|
</div>
|
|
{selectedUser.referent && (
|
|
<div>
|
|
<span className="text-gray-600">Référent:</span>
|
|
<p className="font-medium">{selectedUser.referent}</p>
|
|
</div>
|
|
)}
|
|
<div>
|
|
<span className="text-gray-600">Crédits actuels:</span>
|
|
{loadingBalance ? (
|
|
<p className="text-gray-500">Chargement...</p>
|
|
) : (
|
|
<p className="font-bold text-lg text-green-600">
|
|
{userBalance?.tokenCredits.toLocaleString() || "0"}
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Boutons d'action */}
|
|
<div className="flex gap-2 pt-2 border-t">
|
|
<Button
|
|
onClick={addCredits}
|
|
disabled={adding || loadingBalance}
|
|
className="flex-1 bg-green-600 hover:bg-green-700"
|
|
>
|
|
{adding ? "Ajout en cours..." : "Ajouter 3M tokens"}
|
|
</Button>
|
|
<Button onClick={reset} variant="outline">
|
|
Annuler
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Message de succès */}
|
|
{success && (
|
|
<div className="border rounded-lg p-4 bg-green-50">
|
|
<h4 className="font-semibold text-green-800 flex items-center gap-2">
|
|
<CheckCircle className="h-5 w-5" />
|
|
Crédits ajoutés avec succès !
|
|
</h4>
|
|
<p className="text-green-700 mt-2">
|
|
Nouveau solde:{" "}
|
|
<span className="font-bold">
|
|
{success.newBalance.toLocaleString()}
|
|
</span>{" "}
|
|
tokens
|
|
</p>
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|