Feat: Add creating decks

This commit is contained in:
globuzma 2024-12-27 16:41:39 +01:00
parent 7145906862
commit aaa0bee853
26 changed files with 1279 additions and 180 deletions

View file

@ -0,0 +1,265 @@
'use client'
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { useEffect, useState } from "react"
import { useRouter } from 'next/navigation'
import { getCookie } from "@/lib/utils"
import { Textarea } from "@/components/ui/textarea"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover"
import type { deck, bset } from "@prisma/client";
interface bsetJson extends bset {
set_codes: string[],
icons: string[]
}
interface cardEntryAPIProps {
amount: number,
sanitized_name: string,
set: string
}
interface deckAPIProps {
name: string,
selected_bset: string,
commander_name: string,
cards: cardEntryAPIProps[]
}
export default function Signin() {
const [deckName, setDeckName] = useState("")
const [deckCommanderName, setDeckCommanderName] = useState("")
const [deckImporter, setDeckImporter] = useState("")
const [selectedBset, setSelectedBset] = useState("")
const [decks, setDecks] = useState<deck[]>([])
const [bsets, setBsets] = useState<bsetJson[]>([])
const [openSelectBset, setOpenSelectBset] = useState(false)
const router = useRouter()
const token = getCookie('JWT')
useEffect(() => {
if(getCookie('JWT') == "") {
router.refresh()
router.push('/')
}
fetch('http://localhost:3000/api/account/decks/', {
method: "GET",
headers: {Authorization: 'Bearer ' + token},
}).then((res) => {
if(res.status == 200) {
res.json().then((apiData) => {
setDecks(apiData.data)
})
}
})
fetch('http://localhost:8072/misc/bsets.json').then((res) => {
if(res.status == 200) {
res.json().then((data) => {
setBsets(data)
})
}
})
},[])
function getDataFromLine(line: string){
if(line != "") {
const values = line.split(" ")
if (values.length >= 4) {
const amount: number = parseInt(values.at(0)!.toString())
let set_index = 0
for(let i = 1; i < values.length; i++){
if(values.at(-i)!.toString().match(/\([A-Z]{3}\)/gm)){
set_index = -i
}
}
const set = values.at(set_index)!.toString().replace(/[()]/gm,"").toLowerCase()
const card_name = values.slice(1,set_index).join(" ").replace(/[^a-zA-Z0-9]/gim,"-").toLowerCase()
const card_data : cardEntryAPIProps = {amount, sanitized_name: card_name, set}
return card_data
} else {
return null
}
} else {
return null
}
}
function deleteDeck(id:string){
fetch('http://localhost:3000/api/account/decks/delete', {
method: "DELETE",
headers: {Authorization: 'Bearer ' + token},
body: JSON.stringify({ id })
}).then((res) => {
if(res.status == 200) {
setDecks(old => old.filter((deck: deck) => deck.id != id))
}
})
}
function updateDeckInput(txt:string){
setDeckImporter(txt)
const lines = txt.split("\n")
setDeckCommanderName(lines[0])
}
function importDeck(){
const deckText = deckImporter
const lines = deckText.split("\n")
const dataToSend : deckAPIProps = { name: deckName, selected_bset: selectedBset.replace(/[^a-zA-Z0-9]/gim,"-").toLowerCase() ,commander_name: getDataFromLine(deckCommanderName)!.sanitized_name, cards: [] }
lines.slice(1).forEach((line: string) => {
const data = getDataFromLine(line)
if(data != null) {
dataToSend.cards.push(data)
}
});
console.log(dataToSend)
fetch('http://localhost:3000/api/account/decks/create', {
method: "POST",
headers: {Authorization: 'Bearer ' + token},
body: JSON.stringify(dataToSend)
}).then((res) => {
if(res.status == 200) {
res.json().then((apiData) => {
const new_deck: deck = apiData.data
setDecks(oldDecks => [...oldDecks, new_deck])
setDeckName("")
setDeckImporter("")
setSelectedBset("")
setDeckCommanderName("")
})
}
})
}
return (
<div className="flex flex-col items-center mt-24" >
<Card className="max-w-xl w-full">
<CardHeader>
<CardTitle>Importer un deck</CardTitle>
<CardDescription>Depuis moxfield</CardDescription>
</CardHeader>
<CardContent>
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-2">
<Label className="font-bold">Nom du deck</Label>
<Input value={deckName} onChange={(e) => setDeckName(e.target.value)} placeholder="Nom du deck" />
</div>
<div className="flex flex-col gap-2">
<Label className="font-bold">BSet selectionné</Label>
<Popover open={openSelectBset} onOpenChange={setOpenSelectBset}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={openSelectBset}
className="w-full justify-between"
>
{selectedBset
? bsets.find((bset: bset) => bset.sanitized_name === selectedBset)?.name
: "Selectionnez un Bset..."}
</Button>
</PopoverTrigger>
<PopoverContent className="w-full p-0">
<Command className="w-full">
<CommandInput placeholder="Search framework..." />
<CommandList>
<CommandEmpty>Pas de BSet trouvé.</CommandEmpty>
<CommandGroup>
{bsets.map((bset) => (
<CommandItem
key={bset.sanitized_name}
value={bset.sanitized_name}
onSelect={(currentValue) => {
setSelectedBset(currentValue === selectedBset ? "" : currentValue)
setOpenSelectBset(false)
}}
>
<div className="flex flex-row gap-2">
{bset.icons.map((icon) => (
<img key={icon} src={icon} className="h-3" loading="lazy" />
))}
</div>
{bset.name}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
</div>
<div className="flex flex-col gap-2">
<Label className="font-bold">Commandant</Label>
<Label className="text-xs">Correspond à la première ligne de l&apos;import Moxfield</Label>
<Input value={deckCommanderName} placeholder="Commandant du Deck" disabled />
</div>
<div className="flex flex-col gap-2">
<Label className="font-bold">Liste des cartes</Label>
<Textarea value={deckImporter} onChange={(e) => updateDeckInput(e.target.value)} placeholder="Collez votre deck ici." className="h-60" />
</div>
<div className="flex flex-row w-full">
<Button onClick={importDeck}>Importer</Button>
</div>
</div>
</CardContent>
</Card>
<div className="max-w-3xl w-full mt-12">
<Table className="w-full">
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
<TableHead>Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{ decks.map((deck) => (
<TableRow key={deck.id}>
<TableCell>{deck.name}</TableCell>
<TableCell className="flex flex-row gap-4 items-center"><a href={"/deck/" + deck.id}>Voir la page</a><Button disabled>Editer</Button><Button onClick={() => {deleteDeck(deck.id)}} variant="destructive">Supprimer</Button></TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</div>
);
}

View file

@ -113,7 +113,6 @@ export default function Home() {
setBsetList(old => old.filter((bset) => bset.id != id)) setBsetList(old => old.filter((bset) => bset.id != id))
} }
}) })
} }
return ( return (

View file

@ -0,0 +1,141 @@
import { NextResponse, NextRequest } from 'next/server'
import { validateToken, decryptToken } from '@/lib/jwt'
import { db } from "@/lib/db"
interface orCardFilterProps {
sanitized_name: string
OR: orSetFilterProps[]
}
interface orSetFilterProps {
set_code: string
}
interface cardEntryAPIProps {
amount: number,
sanitized_name: string,
set: string
}
export async function POST(req: NextRequest) {
try {
const token = req?.headers.get("authorization")?.split(" ")[1]
const { name, cards, selected_bset, commander_name } = await req.json()
if(token == undefined) {
return NextResponse.json({"message": "You did not provide a token."},{
status: 401,
});
}
if(!validateToken(token)) {
return NextResponse.json({"message": "Your token is not valid."},{
status: 401,
});
}
const tokenData = decryptToken(token)
if(name == undefined || cards == undefined || selected_bset == undefined || commander_name == undefined) {
return NextResponse.json({"message": "Wrong data in the request."},{
status: 401,
});
}
const bset = await db.bset.findFirst({
where: {
sanitized_name: selected_bset
},
relationLoadStrategy: "join",
include: {
sets: true
}
})
const set_codes: orSetFilterProps[] = []
bset?.sets.forEach((set) => {
set_codes.push({set_code: set.code})
})
const cardsFilter: orCardFilterProps[] = [{sanitized_name: commander_name, OR: set_codes}]
cards.forEach((card:cardEntryAPIProps) => {
cardsFilter.push({sanitized_name: card.sanitized_name, OR: set_codes})
})
const cardsData = await db.carte.findMany({
where: {
OR: cardsFilter
}
})
let allCardFound = true
if(cardsData.findIndex(cardData => cardData.sanitized_name == commander_name) == -1){
allCardFound = false
}
cards.forEach((card: cardEntryAPIProps) => {
if(cardsData.findIndex(cardData => cardData.sanitized_name == card.sanitized_name) == -1){
console.log(card)
allCardFound = false
}
})
if(!allCardFound) {
return NextResponse.json({"message": "Some cards were not found..."},{
status: 401,
});
}
const deck = await db.deck.create({
data: {
name,
utilisateurice: {
connect: {
id: tokenData.id
}
},
commander: {
connect: {
id: cardsData[cardsData.findIndex(cardData => cardData.sanitized_name == commander_name)].id
}
},
bset: {
connect: {
id: bset?.id
}
}
}
})
cards.forEach(async (card: cardEntryAPIProps) => {
const cardData_id = cardsData[cardsData.findIndex(cardData => cardData.sanitized_name == card.sanitized_name)].id
console.log(card.sanitized_name)
await db.cartes_dans_deck.create({
data: {
amount: card.amount,
carte: {
connect: {
id: cardData_id
}
},
deck: {
connect: {
id: deck.id
}
}
}
})
})
return NextResponse.json({"data": deck, "message": "Deck created !"},{
status: 200,
});
} catch (error) {
console.log(error)
return NextResponse.json(
{ error: "Failed, check console" },
{
status: 500,
}
);
}
}

View file

@ -0,0 +1,55 @@
import { NextResponse, NextRequest } from 'next/server'
import { validateToken } from '@/lib/jwt'
import { db } from "@/lib/db"
// TODO: Check if the user is the owner of the deck or an admin.
export async function DELETE(req: NextRequest) {
try {
const token = req?.headers.get("authorization")?.split(" ")[1]
const { id } = await req.json()
if(token == undefined) {
return NextResponse.json({"message": "You did not provide a token."},{
status: 401,
});
}
if(!validateToken(token)) {
return NextResponse.json({"message": "Your token is not valid."},{
status: 401,
});
}
if(id == undefined) {
return NextResponse.json({"message": "You did not provide an ID."},{
status: 401,
});
}
await db.cartes_dans_deck.deleteMany({
where: {
deck_id: id
}
})
await db.deck.delete({
where: {
id
}
})
return NextResponse.json({"message": "Deck successfully deleted."},{
status: 200,
});
} catch (error) {
console.log(error)
return NextResponse.json(
{ error: "Failed, check console" },
{
status: 500,
}
);
}
}

View file

@ -0,0 +1,41 @@
import { NextResponse, NextRequest } from 'next/server'
import { validateToken, decryptToken } from '@/lib/jwt'
import { db } from "@/lib/db"
export async function GET(req: NextRequest) {
try {
const token = req?.headers.get("authorization")?.split(" ")[1]
if(token == undefined) {
return NextResponse.json({"message": "You did not provide a token."},{
status: 401,
});
}
if(!validateToken(token)) {
return NextResponse.json({"message": "Your token is not valid."},{
status: 401,
});
}
const tokenData = decryptToken(token)
const decks = await db.deck.findMany({
where: {
utilisateurice_id: tokenData.id
}
})
return NextResponse.json({"data": decks, "message": "Deck created !"},{
status: 200,
});
} catch (error) {
console.log(error)
return NextResponse.json(
{ error: "Failed, check console" },
{
status: 500,
}
);
}
}

View file

@ -18,7 +18,7 @@ export async function POST(req: NextRequest) {
if (user !== undefined){ if (user !== undefined){
if(createHmac('sha256',secret).update(password).digest('hex') == user!.password) { if(createHmac('sha256',secret).update(password).digest('hex') == user!.password) {
const token = createToken({data: {username: user!.username, admin: user!.admin}, maxAge: 60*60*24*7}) const token = createToken({data: {username: user!.username, admin: user!.admin, id: user!.id}, maxAge: 60*60*24*7})
return NextResponse.json({"JWT": token},{ return NextResponse.json({"JWT": token},{
status: 200, status: 200,
}); });

View file

@ -0,0 +1,36 @@
import { NextResponse, NextRequest } from 'next/server'
import { db } from "@/lib/db"
export async function GET(req: NextRequest) {
try {
const deckId = req.nextUrl.pathname.slice(10).replace(/[^a-zA-Z0-9\-]/gim,"")
console.log(deckId)
const deck = await db.deck.findFirst({
where: {
id: deckId
},
relationLoadStrategy: "join",
include: {
cartes: {
include: {
carte: true
}
},
commander: true
}
})
return NextResponse.json({"deck": deck, "message": "Deck created !"},{
status: 200,
});
} catch (error) {
console.log(error)
return NextResponse.json(
{ error: "Failed, check console" },
{
status: 500,
}
);
}
}

View file

@ -0,0 +1,49 @@
'use client'
import { usePathname } from 'next/navigation'
import { useEffect, useState } from "react"
import { MTGCard } from '@/components/ui/mtg-card'
import type { deck, cartes_dans_deck, carte } from '@prisma/client'
interface deckFromApi extends deck {
commander: carte,
cartes: cartes_dans_deckFromApi[]
}
interface cartes_dans_deckFromApi extends cartes_dans_deck {
carte: carte
}
export default function Page() {
const deckId = usePathname().slice(6).split("/")[0]
const [deck, setDeck] = useState<deckFromApi>()
useEffect(() => {
fetch('http://localhost:3000/api/deck/'+deckId, {
method: "GET",
}).then((res) => {
if(res.status == 200) {
res.json().then((apiData) => {
setDeck(apiData.deck)
})
}
})
}, [])
return (
<div className="flex flex-col items-center mt-32">
{ deck != undefined && (
<>
<h1 className="text-5xl">{deck?.name}</h1>
<MTGCard cardname={deck.commander?.name} imageURI={deck.commander?.normal_image} url={"/card/" + deck.commander?.sanitized_name} />
<div className="flex flex-row flex-wrap gap-4 p-8">
{deck.cartes?.map((card) => (
<MTGCard key={card.carte.id} cardname={card.amount + "x " + card.carte.name} imageURI={card.carte.normal_image} url={"/card/" + card.carte.sanitized_name}/>
))}
</div>
</>
)}
</div>
);
}

View file

@ -3,7 +3,17 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { MTGCard } from '@/components/ui/mtg-card' import { MTGCard } from '@/components/ui/mtg-card'
import type { carte } from "@prisma/client"; interface carte_from_stats {
id: string,
name: string,
normal_image: string,
sanitized_name: string,
nbr_decks: number,
total_decks: number,
percent_decks: number,
price: string,
cardmarket_uri: string
}
export default function Home() { export default function Home() {
const [creatureCardList, setCreatureCardList] = useState([]) const [creatureCardList, setCreatureCardList] = useState([])
@ -35,44 +45,44 @@ export default function Home() {
<div className="flex flex-col items-center mt-32"> <div className="flex flex-col items-center mt-32">
<h1>Creature</h1> <h1>Creature</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{creatureCardList.map((card: carte) => ( {creatureCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>
<h1>Instants</h1> <h1>Instants</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{instantCardList.map((card: carte) => ( {instantCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>
<h1>Sorceries</h1> <h1>Sorceries</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{sorceryCardList.map((card: carte) => ( {sorceryCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>
<h1>Enchantment</h1> <h1>Enchantment</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{enchantmentCardList.map((card: carte) => ( {enchantmentCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>
<h1>Planeswalker</h1> <h1>Planeswalker</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{planeswalkerCardList.map((card: carte) => ( {planeswalkerCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>
<h1>Artifact</h1> <h1>Artifact</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{artifactCardList.map((card: carte) => ( {artifactCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>
<h1>Lands</h1> <h1>Lands</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{landCardList.map((card: carte) => ( {landCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>

View file

@ -3,7 +3,17 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { MTGCard } from '@/components/ui/mtg-card' import { MTGCard } from '@/components/ui/mtg-card'
import type { carte } from "@prisma/client"; interface carte_from_stats {
id: string,
name: string,
normal_image: string,
sanitized_name: string,
nbr_decks: number,
total_decks: number,
percent_decks: number,
price: string,
cardmarket_uri: string
}
export default function Home() { export default function Home() {
const [creatureCardList, setCreatureCardList] = useState([]) const [creatureCardList, setCreatureCardList] = useState([])
@ -35,44 +45,44 @@ export default function Home() {
<div className="flex flex-col items-center mt-32"> <div className="flex flex-col items-center mt-32">
<h1>Creature</h1> <h1>Creature</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{creatureCardList.map((card: carte) => ( {creatureCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>
<h1>Instants</h1> <h1>Instants</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{instantCardList.map((card: carte) => ( {instantCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>
<h1>Sorceries</h1> <h1>Sorceries</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{sorceryCardList.map((card: carte) => ( {sorceryCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>
<h1>Enchantment</h1> <h1>Enchantment</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{enchantmentCardList.map((card: carte) => ( {enchantmentCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>
<h1>Planeswalker</h1> <h1>Planeswalker</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{planeswalkerCardList.map((card: carte) => ( {planeswalkerCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>
<h1>Artifact</h1> <h1>Artifact</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{artifactCardList.map((card: carte) => ( {artifactCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>
<h1>Lands</h1> <h1>Lands</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{landCardList.map((card: carte) => ( {landCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>

View file

@ -3,7 +3,17 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { MTGCard } from '@/components/ui/mtg-card' import { MTGCard } from '@/components/ui/mtg-card'
import type { carte } from "@prisma/client"; interface carte_from_stats {
id: string,
name: string,
normal_image: string,
sanitized_name: string,
nbr_decks: number,
total_decks: number,
percent_decks: number,
price: string,
cardmarket_uri: string
}
export default function Home() { export default function Home() {
const [creatureCardList, setCreatureCardList] = useState([]) const [creatureCardList, setCreatureCardList] = useState([])
@ -15,7 +25,7 @@ export default function Home() {
const [landCardList, setLandCardList] = useState([]) const [landCardList, setLandCardList] = useState([])
useEffect(() => { useEffect(() => {
fetch('http://localhost:8072/top/colorless.json').then((res) => { fetch('http://localhost:8072/top/mono-colorless.json').then((res) => {
if(res.status == 200) { if(res.status == 200) {
res.json().then((data) => { res.json().then((data) => {
const limit = 20 const limit = 20
@ -35,44 +45,44 @@ export default function Home() {
<div className="flex flex-col items-center mt-32"> <div className="flex flex-col items-center mt-32">
<h1>Creature</h1> <h1>Creature</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{creatureCardList.map((card: carte) => ( {creatureCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>
<h1>Instants</h1> <h1>Instants</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{instantCardList.map((card: carte) => ( {instantCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>
<h1>Sorceries</h1> <h1>Sorceries</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{sorceryCardList.map((card: carte) => ( {sorceryCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>
<h1>Enchantment</h1> <h1>Enchantment</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{enchantmentCardList.map((card: carte) => ( {enchantmentCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>
<h1>Planeswalker</h1> <h1>Planeswalker</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{planeswalkerCardList.map((card: carte) => ( {planeswalkerCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>
<h1>Artifact</h1> <h1>Artifact</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{artifactCardList.map((card: carte) => ( {artifactCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>
<h1>Lands</h1> <h1>Lands</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{landCardList.map((card: carte) => ( {landCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>

View file

@ -3,7 +3,17 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { MTGCard } from '@/components/ui/mtg-card' import { MTGCard } from '@/components/ui/mtg-card'
import type { carte } from "@prisma/client"; interface carte_from_stats {
id: string,
name: string,
normal_image: string,
sanitized_name: string,
nbr_decks: number,
total_decks: number,
percent_decks: number,
price: string,
cardmarket_uri: string
}
export default function Home() { export default function Home() {
const [creatureCardList, setCreatureCardList] = useState([]) const [creatureCardList, setCreatureCardList] = useState([])
@ -35,44 +45,44 @@ export default function Home() {
<div className="flex flex-col items-center mt-32"> <div className="flex flex-col items-center mt-32">
<h1>Creature</h1> <h1>Creature</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{creatureCardList.map((card: carte) => ( {creatureCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>
<h1>Instants</h1> <h1>Instants</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{instantCardList.map((card: carte) => ( {instantCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>
<h1>Sorceries</h1> <h1>Sorceries</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{sorceryCardList.map((card: carte) => ( {sorceryCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>
<h1>Enchantment</h1> <h1>Enchantment</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{enchantmentCardList.map((card: carte) => ( {enchantmentCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>
<h1>Planeswalker</h1> <h1>Planeswalker</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{planeswalkerCardList.map((card: carte) => ( {planeswalkerCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>
<h1>Artifact</h1> <h1>Artifact</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{artifactCardList.map((card: carte) => ( {artifactCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>
<h1>Lands</h1> <h1>Lands</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{landCardList.map((card: carte) => ( {landCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>

View file

@ -3,7 +3,17 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { MTGCard } from '@/components/ui/mtg-card' import { MTGCard } from '@/components/ui/mtg-card'
import type { carte } from "@prisma/client"; interface carte_from_stats {
id: string,
name: string,
normal_image: string,
sanitized_name: string,
nbr_decks: number,
total_decks: number,
percent_decks: number,
price: string,
cardmarket_uri: string
}
export default function Home() { export default function Home() {
const [creatureCardList, setCreatureCardList] = useState([]) const [creatureCardList, setCreatureCardList] = useState([])
@ -15,7 +25,7 @@ export default function Home() {
const [landCardList, setLandCardList] = useState([]) const [landCardList, setLandCardList] = useState([])
useEffect(() => { useEffect(() => {
fetch('http://localhost:8072/top/multicolor.json').then((res) => { fetch('http://localhost:8072/top/mono-multicolor.json').then((res) => {
if(res.status == 200) { if(res.status == 200) {
res.json().then((data) => { res.json().then((data) => {
const limit = 20 const limit = 20
@ -35,44 +45,44 @@ export default function Home() {
<div className="flex flex-col items-center mt-32"> <div className="flex flex-col items-center mt-32">
<h1>Creature</h1> <h1>Creature</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{creatureCardList.map((card: carte) => ( {creatureCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>
<h1>Instants</h1> <h1>Instants</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{instantCardList.map((card: carte) => ( {instantCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>
<h1>Sorceries</h1> <h1>Sorceries</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{sorceryCardList.map((card: carte) => ( {sorceryCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>
<h1>Enchantment</h1> <h1>Enchantment</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{enchantmentCardList.map((card: carte) => ( {enchantmentCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>
<h1>Planeswalker</h1> <h1>Planeswalker</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{planeswalkerCardList.map((card: carte) => ( {planeswalkerCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>
<h1>Artifact</h1> <h1>Artifact</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{artifactCardList.map((card: carte) => ( {artifactCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>
<h1>Lands</h1> <h1>Lands</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{landCardList.map((card: carte) => ( {landCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>

View file

@ -3,7 +3,17 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { MTGCard } from '@/components/ui/mtg-card' import { MTGCard } from '@/components/ui/mtg-card'
import type { carte } from "@prisma/client"; interface carte_from_stats {
id: string,
name: string,
normal_image: string,
sanitized_name: string,
nbr_decks: number,
total_decks: number,
percent_decks: number,
price: string,
cardmarket_uri: string
}
export default function Home() { export default function Home() {
const [creatureCardList, setCreatureCardList] = useState([]) const [creatureCardList, setCreatureCardList] = useState([])
@ -35,44 +45,44 @@ export default function Home() {
<div className="flex flex-col items-center mt-32"> <div className="flex flex-col items-center mt-32">
<h1>Creature</h1> <h1>Creature</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{creatureCardList.map((card: carte) => ( {creatureCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>
<h1>Instants</h1> <h1>Instants</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{instantCardList.map((card: carte) => ( {instantCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>
<h1>Sorceries</h1> <h1>Sorceries</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{sorceryCardList.map((card: carte) => ( {sorceryCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>
<h1>Enchantment</h1> <h1>Enchantment</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{enchantmentCardList.map((card: carte) => ( {enchantmentCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>
<h1>Planeswalker</h1> <h1>Planeswalker</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{planeswalkerCardList.map((card: carte) => ( {planeswalkerCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>
<h1>Artifact</h1> <h1>Artifact</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{artifactCardList.map((card: carte) => ( {artifactCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>
<h1>Lands</h1> <h1>Lands</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{landCardList.map((card: carte) => ( {landCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>

View file

@ -3,7 +3,17 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { MTGCard } from '@/components/ui/mtg-card' import { MTGCard } from '@/components/ui/mtg-card'
import type { carte } from "@prisma/client"; interface carte_from_stats {
id: string,
name: string,
normal_image: string,
sanitized_name: string,
nbr_decks: number,
total_decks: number,
percent_decks: number,
price: string,
cardmarket_uri: string
}
export default function Home() { export default function Home() {
const [creatureCardList, setCreatureCardList] = useState([]) const [creatureCardList, setCreatureCardList] = useState([])
@ -35,44 +45,44 @@ export default function Home() {
<div className="flex flex-col items-center mt-32"> <div className="flex flex-col items-center mt-32">
<h1>Creature</h1> <h1>Creature</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{creatureCardList.map((card: carte) => ( {creatureCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>
<h1>Instants</h1> <h1>Instants</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{instantCardList.map((card: carte) => ( {instantCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>
<h1>Sorceries</h1> <h1>Sorceries</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{sorceryCardList.map((card: carte) => ( {sorceryCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>
<h1>Enchantment</h1> <h1>Enchantment</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{enchantmentCardList.map((card: carte) => ( {enchantmentCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>
<h1>Planeswalker</h1> <h1>Planeswalker</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{planeswalkerCardList.map((card: carte) => ( {planeswalkerCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>
<h1>Artifact</h1> <h1>Artifact</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{artifactCardList.map((card: carte) => ( {artifactCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>
<h1>Lands</h1> <h1>Lands</h1>
<div className="flex flex-row flex-wrap gap-4 p-8"> <div className="flex flex-row flex-wrap gap-4 p-8">
{landCardList.map((card: carte) => ( {landCardList.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name_fr} imageURI={card.normal_image} url={"/card/" + card.sanitized_name}/> <MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={"/card/" + card.sanitized_name} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={card.percent_decks} price={card.price} cardmarketURI={card.cardmarket_uri}/>
))} ))}
</div> </div>

View file

@ -0,0 +1,26 @@
"use client"
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const labelVariants = cva(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
)
const Label = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
VariantProps<typeof labelVariants>
>(({ className, ...props }, ref) => (
<LabelPrimitive.Root
ref={ref}
className={cn(labelVariants(), className)}
{...props}
/>
))
Label.displayName = LabelPrimitive.Root.displayName
export { Label }

View file

@ -6,10 +6,15 @@ interface MTGCardProps {
className?: string, className?: string,
imageURI: string, imageURI: string,
cardname: string, cardname: string,
url: string url: string,
nbrDecks?: number,
totalDecks?: number,
percentDecks?: number,
price?: string,
cardmarketURI?: string
} }
const MTGCard = ({ className, imageURI, cardname, url }: MTGCardProps) => { const MTGCard = ({ className, imageURI, cardname, url, nbrDecks, totalDecks, percentDecks, price, cardmarketURI }: MTGCardProps) => {
const [loaded, setLoaded] = React.useState(false) const [loaded, setLoaded] = React.useState(false)
return ( return (
@ -24,7 +29,17 @@ const MTGCard = ({ className, imageURI, cardname, url }: MTGCardProps) => {
<span className="h-64 shadow">Loading...</span> <span className="h-64 shadow">Loading...</span>
} }
<img src={imageURI} className="rounded" height={loaded ? 'auto' : '0'} onLoad={() => {setLoaded(true)}} loading="lazy" /> <img src={imageURI} className="rounded" height={loaded ? 'auto' : '0'} onLoad={() => {setLoaded(true)}} loading="lazy" />
<span className="text-center">{cardname}</span> <div className="flex flex-col items-center gap-0">
{ price != undefined && (
<a href={cardmarketURI != undefined ? cardmarketURI : "#"} target={cardmarketURI != undefined ? "_blank" : "_self"}>{price}</a>
)}
<span className="text-center">{cardname}</span>
{ nbrDecks != undefined && (
<>
<span className="text-xs">{nbrDecks} de {totalDecks} Decks ({percentDecks}%)</span>
</>
)}
</div>
</a> </a>
)} )}
MTGCard.displayName = "MTGCard" MTGCard.displayName = "MTGCard"

View file

@ -66,7 +66,7 @@ export function NavigationBar ({ isLoggedIn, username}: NavigationProps) {
</a> </a>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem> <DropdownMenuItem>
<a className="flex flex-row gap-2" href="/top/blue"> <a className="flex flex-row gap-2" href="/top/black">
<Black className="h-4 w-4"/> <Black className="h-4 w-4"/>
<span>Noir</span> <span>Noir</span>
</a> </a>
@ -394,10 +394,13 @@ export function NavigationBar ({ isLoggedIn, username}: NavigationProps) {
} }
{ {
isLoggedIn && isLoggedIn &&
<a href="/account/profile" className="flex flex-row items-center gap-2"> <>
<IconUserFilled color="gray" /> <a href="/account/profile/decks" className="flex flex-row items-center gap-2"><span className="text-gray-400">Decks</span></a>
<span className="text-gray-400">{username}</span> <a href="/account/profile" className="flex flex-row items-center gap-2">
</a> <IconUserFilled color="gray" />
<span className="text-gray-400">{username}</span>
</a>
</>
} }
</div> </div>
</div> </div>

View file

@ -0,0 +1,33 @@
"use client"
import * as React from "react"
import * as PopoverPrimitive from "@radix-ui/react-popover"
import { cn } from "@/lib/utils"
const Popover = PopoverPrimitive.Root
const PopoverTrigger = PopoverPrimitive.Trigger
const PopoverAnchor = PopoverPrimitive.Anchor
const PopoverContent = React.forwardRef<
React.ElementRef<typeof PopoverPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={cn(
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
</PopoverPrimitive.Portal>
))
PopoverContent.displayName = PopoverPrimitive.Content.displayName
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }

View file

@ -0,0 +1,22 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Textarea = React.forwardRef<
HTMLTextAreaElement,
React.ComponentProps<"textarea">
>(({ className, ...props }, ref) => {
return (
<textarea
className={cn(
"flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
className
)}
ref={ref}
{...props}
/>
)
})
Textarea.displayName = "Textarea"
export { Textarea }

279
app/package-lock.json generated
View file

@ -14,7 +14,9 @@
"@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-dropdown-menu": "^2.1.2",
"@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-navigation-menu": "^1.2.1", "@radix-ui/react-navigation-menu": "^1.2.1",
"@radix-ui/react-popover": "^1.1.3",
"@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-slot": "^1.1.0",
"@tabler/icons": "^3.22.0", "@tabler/icons": "^3.22.0",
"@tabler/icons-react": "^3.22.0", "@tabler/icons-react": "^3.22.0",
@ -1339,6 +1341,29 @@
} }
} }
}, },
"node_modules/@radix-ui/react-label": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.0.tgz",
"integrity": "sha512-peLblDlFw/ngk3UWq0VnYaOLy6agTZZ+MUO/WhVfm14vJGML+xH4FAl2XQGLqdefjNb7ApRg6Yn7U42ZhmYXdw==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-primitive": "2.0.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-menu": { "node_modules/@radix-ui/react-menu": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.2.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.2.tgz",
@ -1415,6 +1440,260 @@
} }
} }
}, },
"node_modules/@radix-ui/react-popover": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.3.tgz",
"integrity": "sha512-MBDKFwRe6fi0LT8m/Jl4V8J3WbS/UfXJtsgg8Ym5w5AyPG3XfHH4zhBp1P8HmZK83T8J7UzVm6/JpDE3WMl1Dw==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.1",
"@radix-ui/react-compose-refs": "1.1.1",
"@radix-ui/react-context": "1.1.1",
"@radix-ui/react-dismissable-layer": "1.1.2",
"@radix-ui/react-focus-guards": "1.1.1",
"@radix-ui/react-focus-scope": "1.1.1",
"@radix-ui/react-id": "1.1.0",
"@radix-ui/react-popper": "1.2.1",
"@radix-ui/react-portal": "1.1.3",
"@radix-ui/react-presence": "1.1.2",
"@radix-ui/react-primitive": "2.0.1",
"@radix-ui/react-slot": "1.1.1",
"@radix-ui/react-use-controllable-state": "1.1.0",
"aria-hidden": "^1.1.1",
"react-remove-scroll": "2.6.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-popover/node_modules/@radix-ui/primitive": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz",
"integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==",
"license": "MIT"
},
"node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-arrow": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.1.tgz",
"integrity": "sha512-NaVpZfmv8SKeZbn4ijN2V3jlHA9ngBG16VnIIm22nUR0Yk8KUALyBxT3KYEUnNuch9sTE8UTsS3whzBgKOL30w==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-primitive": "2.0.1"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-compose-refs": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz",
"integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-dismissable-layer": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.2.tgz",
"integrity": "sha512-kEHnlhv7wUggvhuJPkyw4qspXLJOdYoAP4dO2c8ngGuXTq1w/HZp1YeVB+NQ2KbH1iEG+pvOCGYSqh9HZOz6hg==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.1",
"@radix-ui/react-compose-refs": "1.1.1",
"@radix-ui/react-primitive": "2.0.1",
"@radix-ui/react-use-callback-ref": "1.1.0",
"@radix-ui/react-use-escape-keydown": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-focus-scope": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.1.tgz",
"integrity": "sha512-01omzJAYRxXdG2/he/+xy+c8a8gCydoQ1yOxnWNcRhrrBW5W+RQJ22EK1SaO8tb3WoUsuEw7mJjBozPzihDFjA==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.1",
"@radix-ui/react-primitive": "2.0.1",
"@radix-ui/react-use-callback-ref": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-popper": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.1.tgz",
"integrity": "sha512-3kn5Me69L+jv82EKRuQCXdYyf1DqHwD2U/sxoNgBGCB7K9TRc3bQamQ+5EPM9EvyPdli0W41sROd+ZU1dTCztw==",
"license": "MIT",
"dependencies": {
"@floating-ui/react-dom": "^2.0.0",
"@radix-ui/react-arrow": "1.1.1",
"@radix-ui/react-compose-refs": "1.1.1",
"@radix-ui/react-context": "1.1.1",
"@radix-ui/react-primitive": "2.0.1",
"@radix-ui/react-use-callback-ref": "1.1.0",
"@radix-ui/react-use-layout-effect": "1.1.0",
"@radix-ui/react-use-rect": "1.1.0",
"@radix-ui/react-use-size": "1.1.0",
"@radix-ui/rect": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-portal": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.3.tgz",
"integrity": "sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-primitive": "2.0.1",
"@radix-ui/react-use-layout-effect": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-presence": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.2.tgz",
"integrity": "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.1",
"@radix-ui/react-use-layout-effect": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-primitive": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz",
"integrity": "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-slot": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-slot": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz",
"integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-popper": { "node_modules/@radix-ui/react-popper": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.0.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.0.tgz",

View file

@ -15,7 +15,9 @@
"@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-dropdown-menu": "^2.1.2",
"@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-navigation-menu": "^1.2.1", "@radix-ui/react-navigation-menu": "^1.2.1",
"@radix-ui/react-popover": "^1.1.3",
"@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-slot": "^1.1.0",
"@tabler/icons": "^3.22.0", "@tabler/icons": "^3.22.0",
"@tabler/icons-react": "^3.22.0", "@tabler/icons-react": "^3.22.0",

View file

@ -1,5 +1,5 @@
generator client { generator client {
provider = "prisma-client-js" provider = "prisma-client-js"
previewFeatures = ["relationJoins"] previewFeatures = ["relationJoins"]
} }
@ -9,54 +9,76 @@ datasource db {
} }
model utilisateurice { model utilisateurice {
id String @id @default(uuid()) id String @id @default(uuid()) @db.Uuid
username String username String
password String password String
email String email String
admin Boolean @default(false) admin Boolean @default(false)
deck deck[]
} }
model carte { model carte {
id String @id @default(uuid()) @db.Uuid id String @id @default(uuid()) @db.Uuid
name_en String name String
name_fr String sanitized_name String
sanitized_name String released_at String
released_at String layout String
small_image String small_image String
normal_image String small_image_back String?
mana_cost String normal_image String
cmc Int normal_image_back String?
type_line_en String? type_line String?
type_line_fr String? colors String[]
oracle_text_en String? set set @relation(fields: [set_id], references: [id])
oracle_text_fr String? set_id String @db.Uuid
power String? set_code String
toughness String? rarity String
colors String[] type String?
keywords String[] price String?
set set @relation(fields: [set_id], references: [id]) cardmarket_uri String?
set_id String @db.Uuid decks cartes_dans_deck[]
rarity String decks_as_commander deck[]
type String? }
cardmarket_uri String?
model deck {
id String @id @default(uuid()) @db.Uuid
name String
utilisateurice_id String @db.Uuid
utilisateurice utilisateurice @relation(fields: [utilisateurice_id], references: [id])
cartes cartes_dans_deck[]
commander carte @relation(fields: [commander_id], references: [id])
commander_id String @db.Uuid
bset bset @relation(fields: [bset_id], references: [id])
bset_id String @db.Uuid
}
model cartes_dans_deck {
carte carte @relation(fields: [carte_id], references: [id])
carte_id String @db.Uuid
deck deck @relation(fields: [deck_id], references: [id])
deck_id String @db.Uuid
amount Int
@@id([carte_id, deck_id])
} }
model set { model set {
id String @id @default(uuid()) @db.Uuid id String @id @default(uuid()) @db.Uuid
name_en String name_en String
sanitized_name String sanitized_name String
code String code String
set_type String set_type String
released_at String? released_at String?
icon_svg_uri String icon_svg_uri String
cards carte[] cards carte[]
bset bset? @relation(fields: [bset_id], references: [id]) bset bset? @relation(fields: [bset_id], references: [id])
bset_id String? @db.Uuid bset_id String? @db.Uuid
} }
model bset { model bset {
id String @id @default(uuid()) @db.Uuid id String @id @default(uuid()) @db.Uuid
name String name String
sanitized_name String sanitized_name String
sets set[] sets set[]
decks deck[]
} }

View file

@ -19,7 +19,7 @@ const Bsets = [
{"name": "War of the Spark", sets: ["war"]}, {"name": "War of the Spark", sets: ["war"]},
{"name": "Dominaria", sets: ["dom"]}, {"name": "Dominaria", sets: ["dom"]},
{"name": "March of the machine", sets: ["mom","mat"]}, {"name": "March of the machine", sets: ["mom","mat"]},
{"name": "Innistrad Midnight Hunt", sets: ["wow","mid"]}, {"name": "Innistrad Midnight Hunt", sets: ["vow","mid"]},
{"name": "Guilds of Ravnica", sets: ["rna","grn"]}, {"name": "Guilds of Ravnica", sets: ["rna","grn"]},
{"name": "Ixalan", sets: ["rix","xln"]}, {"name": "Ixalan", sets: ["rix","xln"]},
{"name": "Amonkhet", sets: ["hou","akh"]}, {"name": "Amonkhet", sets: ["hou","akh"]},
@ -36,13 +36,13 @@ const Bsets = [
{"name": "Ravnica", sets: ["rav","gpt","dis"]}, {"name": "Ravnica", sets: ["rav","gpt","dis"]},
{"name": "Kamigawa 3x", sets: ["chk","bok","sok"]}, {"name": "Kamigawa 3x", sets: ["chk","bok","sok"]},
{"name": "Mirrodin", sets: ["5dn","dst","mrd"]}, {"name": "Mirrodin", sets: ["5dn","dst","mrd"]},
{"name": "Onslaught", sets: ["ons","lng","scg"]}, {"name": "Onslaught", sets: ["ons","lgn","scg"]},
{"name": "Odyssey", sets: ["ody","tor","jud"]}, {"name": "Odyssey", sets: ["ody","tor","jud"]},
{"name": "Invasion", sets: ["inv","pls","apc"]}, {"name": "Invasion", sets: ["inv","pls","apc"]},
{"name": "Masques", sets: ["mmq","nem","pcy"]}, {"name": "Masques", sets: ["mmq","nem","pcy"]},
{"name": "Urza", sets: ["usg","ulg","uds"]}, {"name": "Urza", sets: ["usg","ulg","uds"]},
{"name": "Tempest", sets: ["tmp","sth","exo"]}, {"name": "Tempest", sets: ["tmp","sth","exo"]},
{"name": "Mirage", sets: ["wth","mir","wis"]}, {"name": "Mirage", sets: ["wth","mir","vis"]},
{"name": "Ice Age", sets: ["csp","ice","all"]}, {"name": "Ice Age", sets: ["csp","ice","all"]},
{"name": "Time Spiral", sets: ["fut","tsp","plc","tsb"]}, {"name": "Time Spiral", sets: ["fut","tsp","plc","tsb"]},
{"name": "Lorwyn-Shadowmoor", sets: ["lrw","shm","mor","eve"]}, {"name": "Lorwyn-Shadowmoor", sets: ["lrw","shm","mor","eve"]},

View file

@ -61,20 +61,36 @@ async function createJson() {
include: { include: {
sets: { sets: {
include: { include: {
cards: true cards: {
include: {
decks: true
}
}
} }
} },
decks: true
} }
}) })
let bsets_export = []
let all_cards = [] let all_cards = []
bsets.forEach((bset) => { bsets.forEach((bset) => {
let icons = []
let set_codes = []
bset.sets.forEach((set) => { bset.sets.forEach((set) => {
all_cards = [...all_cards, ...set.cards] icons.push(set.icon_svg_uri)
set_codes.push(set.code)
let cards_temp = set.cards
for(let i = 0; i < cards_temp.length; i++){
cards_temp[i].bset_id = bset.id
}
all_cards = [...all_cards, ...cards_temp]
}) })
bsets_export.push({name: bset.name, sanitized_name: bset.sanitized_name, icons, set_codes})
}) })
writeFileSync(import.meta.dirname + "/json/misc/bsets.json",JSON.stringify(bsets_export), 'utf8')
@ -86,23 +102,43 @@ async function createJson() {
const colorsData = {"mono-white": structuredClone(type_dict),"mono-black": structuredClone(type_dict),"mono-blue": structuredClone(type_dict),"mono-green": structuredClone(type_dict),"mono-red": structuredClone(type_dict),"colorless": structuredClone(type_dict),"multicolor": structuredClone(type_dict)} const colorsData = {"mono-white": structuredClone(type_dict),"mono-black": structuredClone(type_dict),"mono-blue": structuredClone(type_dict),"mono-green": structuredClone(type_dict),"mono-red": structuredClone(type_dict),"colorless": structuredClone(type_dict),"multicolor": structuredClone(type_dict)}
for (const card of all_cards) { for (const card of all_cards) {
let card_object = {
"name": card.name,
"sanitized_name": card.sanitized_name,
"normal_image": card.normal_image,
"small_image": card.small_image,
"type": card.type,
"layout": card.layout,
"price": card.price,
"cardmarket_uri": card.cardmarket_uri,
"nbr_decks": card.decks.length,
"total_decks": bsets.find((bset) => bset.id == card.bset_id).decks.length,
"colors": card.colors,
}
card_object.percent_decks = (card_object.total_decks != 0) ? parseInt(10000 * (card_object.nbr_decks / card_object.total_decks)) / 100 : 0
const colorName = getColorName(card.colors) const colorName = getColorName(card.colors)
if (card.type == "land") { if (card.type == "land") {
if (colorName != "") { if (colorName != "") {
landsData[colorName].push(card) landsData[colorName].push(card_object)
} }
} }
if (card.colors.length <= 1) { if (card.colors.length <= 1) {
colorsData[colorName][card.type].push(card) colorsData[colorName][card.type].push(card_object)
} else { } else {
colorsData["multicolor"][card.type].push(card) colorsData["multicolor"][card.type].push(card_object)
} }
} }
for (const index of Object.keys(colorsData)) { for (const index of Object.keys(colorsData)) {
writeFileSync(import.meta.dirname + "/json/top/" + index + ".json",JSON.stringify(colorsData[index]), 'utf8') let JSONToWrite = colorsData[index]
for (const key of Object.keys(JSONToWrite)){
JSONToWrite[key].sort((a,b) => b.percent_decks - a.percent_decks)
}
writeFileSync(import.meta.dirname + "/json/top/" + index + ".json",JSON.stringify(JSONToWrite), 'utf8')
} }
const landsJsonRoot = [[],[],[],[]] const landsJsonRoot = [[],[],[],[]]
@ -114,7 +150,9 @@ async function createJson() {
} else { } else {
landsJsonRoot[3].push({ "name": colorName, "count": landsData[colorName].length}) landsJsonRoot[3].push({ "name": colorName, "count": landsData[colorName].length})
} }
writeFileSync(import.meta.dirname + "/json/lands/" + colorName + ".json",JSON.stringify(landsData[colorName]), 'utf8') let JSONToWrite = landsData[colorName]
JSONToWrite.sort((a,b) => b.percent_decks - a.percent_decks)
writeFileSync(import.meta.dirname + "/json/lands/" + colorName + ".json",JSON.stringify(JSONToWrite), 'utf8')
} }
writeFileSync(import.meta.dirname + "/json/lands/lands.json",JSON.stringify(landsJsonRoot), 'utf8') writeFileSync(import.meta.dirname + "/json/lands/lands.json",JSON.stringify(landsJsonRoot), 'utf8')
} }

View file

@ -10,7 +10,7 @@ console.log('Status Code:', scryfallSets.status);
const sets = await scryfallSets.json(); const sets = await scryfallSets.json();
// Read the data from the exported fr_cards.json extracted from Scryfall Bulk Data // Read the data from the exported fr_cards.json extracted from Scryfall Bulk Data
const fileBytes = fs.readFileSync(import.meta.dirname + '/data/fr_cards.json') const fileBytes = fs.readFileSync(import.meta.dirname + '/data/default-cards-20241210100712.json')
let scryfallData = JSON.parse(fileBytes) let scryfallData = JSON.parse(fileBytes)
// Connect to postgres database // Connect to postgres database
@ -23,6 +23,8 @@ const client = new Client({
}) })
await client.connect() await client.connect()
const two_faced_layouts = ["transform","modal_dfc","double_faced_token","reversible_card"]
try { try {
const setRes = await client.query('SELECT id FROM set') const setRes = await client.query('SELECT id FROM set')
const preUpdateSetRows = setRes.rows const preUpdateSetRows = setRes.rows
@ -47,15 +49,15 @@ try {
}); });
// Define counter for logging // Define counter for logging
let total_no_fr_name = 0
let total_inserted = 0 let total_inserted = 0
let total_skipped = 0 let total_skipped = 0
// For each card check if we need to upload it to the database // For each card check if we need to upload it to the database
for (const carte of scryfallData) { for (const carte of scryfallData) {
if(!preUpdateCardsIds.includes(carte.id)){ if(!preUpdateCardsIds.includes(carte.id) && carte.layout != "art_series"){
let type = null let type = ""
const card_type = carte.type_line.toLowerCase() const layout = carte.layout
const card_type = (carte.type_line == undefined) ? carte.card_faces[0].type_line.toLowerCase() : carte.type_line.toLowerCase()
if(card_type.includes("creature")){ if(card_type.includes("creature")){
type = "creature" type = "creature"
@ -73,27 +75,28 @@ try {
type = "land" type = "land"
} }
try {
if(two_faced_layouts.includes(layout)) {
const addingCardsQuery = await client.query('INSERT INTO carte(id, name, released_at, small_image, small_image_back, normal_image, normal_image_back, type_line, colors, set_id, rarity, cardmarket_uri, price, type, sanitized_name, set_code, layout) VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17)', [carte.id, carte.name, carte.released_at, carte.card_faces[0].image_uris.small, carte.card_faces[1].image_uris.small, carte.card_faces[0].image_uris.normal, carte.card_faces[0].image_uris.normal, carte.type_line, carte.color_identity, carte.set_id, carte.rarity, carte.purchase_uris?.cardmarket, carte.prices.eur, type, carte.name.replace(/[^a-zA-Z0-9]/gim,"-").toLowerCase(), carte.set, layout])
} else {
const addingCardsQuery = await client.query('INSERT INTO carte(id, name, released_at, small_image, normal_image, type_line, colors, set_id, rarity, cardmarket_uri, price, type, sanitized_name, set_code, layout) VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)', [carte.id, carte.name, carte.released_at, carte.image_uris.small, carte.image_uris.normal, carte.type_line, carte.color_identity, carte.set_id, carte.rarity, carte.purchase_uris?.cardmarket, carte.prices.eur, type, carte.name.replace(/[^a-zA-Z0-9]/gim,"-").toLowerCase(), carte.set, layout])
if(carte.printed_name == undefined) { }
// If the card doesn't have a french name, print it to the console and skip total_inserted = total_inserted + 1
} catch (err) {
//console.log("Erreur sur la carte : " + carte.name) console.log(carte.uri)
//console.log("Scryfall URI : " + carte.scryfall_uri) console.log(carte.layout)
//console.log("API URI : " + carte.uri) console.log(err)
total_no_fr_name = total_no_fr_name + 1 total_skipped = total_skipped + 1
continue
} }
// Add the card to the database // Add the card to the database
const addingCardsQuery = await client.query('INSERT INTO carte(id, name_en, name_fr, released_at, small_image, normal_image, mana_cost, cmc, type_line_en, type_line_fr, oracle_text_en, oracle_text_fr, power, toughness, colors, keywords, set_id, rarity, cardmarket_uri, type, sanitized_name) VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21)', [carte.id, carte.name, carte.printed_name, carte.released_at, carte.image_uris.small, carte.image_uris.normal, carte.mana_cost, carte.cmc, carte.type_line, carte.printed_type_line, carte.oracle_text, carte.printed_text, carte.power, carte.toughness, carte.color_identity, carte.keywords, carte.set_id, carte.rarity, carte.purchase_uris?.cardmarket, type, carte.name.replace(/[^a-zA-Z0-9]/gim,"-").toLowerCase()])
total_inserted = total_inserted + 1
} else { } else {
total_skipped = total_skipped + 1 total_skipped = total_skipped + 1
} }
} }
console.log("Un total de " + total_no_fr_name + " cartes n'ont pas de nom français.")
console.log("Un total de " + total_inserted + " cartes ont été insérées.") console.log("Un total de " + total_inserted + " cartes ont été insérées.")
console.log("Un total de " + total_skipped + " cartes ont été ignorées.") console.log("Un total de " + total_skipped + " cartes ont été ignorées.")
} catch (err) { } catch (err) {