Remove old code

This commit is contained in:
zuma 2025-04-12 16:06:51 +02:00
parent dcbab5c99f
commit 72bfc2ed89
106 changed files with 0 additions and 14832 deletions

28
.gitignore vendored
View file

@ -1,28 +0,0 @@
app/node_modules/
app/.env
# testing
app/coverage
# next.js
app/.next/
app/out/
# production
app/build
# debug
app/npm-debug.log*
# typescript
app/*.tsbuildinfo
app/next-env.d.ts
# data
app/tools/data/*
app/data/misc/*
app/data/commander/*
app/data/card-commander/*
app/data/bset/*
app/tools/json/*
app/.pg

View file

@ -1,4 +0,0 @@
# Projet Brawl Set
Ce dépot contient le code et la documentation pour le projet Brawl Set.
Pour plus d'information sur l'avancé, allez dans [la section projet](https://git.shenanigans.cc/globuzma/brawlset/projects) du dépot.

View file

@ -1,28 +0,0 @@
node_modules/
.env
# testing
coverage
# next.js
.next/
out/
# production
build
# debug
npm-debug.log*
# typescript
*.tsbuildinfo
next-env.d.ts
# data
tools/data/*
tools/json
data/misc/*
data/commander/*
data/card-commander/*
data/bset/*
.pg/

View file

@ -1 +0,0 @@
/components/ui/**.tsx

View file

@ -1,3 +0,0 @@
{
"extends": ["next/core-web-vitals", "next/typescript"]
}

36
app/.gitignore vendored
View file

@ -1,36 +0,0 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts

View file

@ -1,27 +0,0 @@
FROM node:alpine AS build
WORKDIR /app
COPY package.json ./
# install dependencies
RUN npm install
COPY . .
# build
RUN npx prisma generate
RUN npm run build
FROM node:alpine
WORKDIR /app
RUN apk add --no-cache openssl
# copy from build image
COPY --from=build /app/package.json ./package.json
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/.next ./.next
COPY --from=build /app/public ./public
COPY --from=build /app/tools ./tools
COPY --from=build /app/data ./data
COPY --from=build /app/prisma ./prisma
EXPOSE 3000
CMD ["npm","run","start"]

View file

@ -1,36 +0,0 @@
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.

View file

@ -1,440 +0,0 @@
'use client'
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog"
import {
Dialog,
DialogContent,
DialogFooter,
DialogClose,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import { useEffect, useState, useRef } from "react"
import { useRouter } from 'next/navigation'
import { getCookie } from "@/lib/utils"
import { Textarea } from "@/components/ui/textarea"
import { MTGCardTextHover } from "@/components/ui/mtg-card-text-hover"
import { Toaster } from "@/components/ui/toaster"
import { useToast } from "@/hooks/use-toast"
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/deck-accordion"
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover"
import { Spinner } from "@/components/ui/spinner"
import type { deck, bset, carte, cartes_dans_deck } from "@prisma/client";
interface bsetJson extends bset {
set_codes: string[],
icons: string[]
}
interface deckExtended extends deck {
commander: carte
cartes: cartes_dans_deckExtended[]
}
interface cartes_dans_deckExtended extends cartes_dans_deck {
carte: carte
}
interface cardEntryAPIProps {
amount: number,
sanitized_name: string,
}
interface deckAPIProps {
id?: string, // For Update API
name: string,
selected_bset?: string, // For Create API
url?: string,
commander_name?: string,
cards: cardEntryAPIProps[]
}
export default function Signin() {
const [deckName, setDeckName] = useState("")
const [deckUrl, setDeckUrl] = useState("")
const [deckCommanderName, setDeckCommanderName] = useState("")
const [deckImporter, setDeckImporter] = useState("")
const [selectedBset, setSelectedBset] = useState("")
const [decks, setDecks] = useState<deckExtended[]>([])
const [displayedDecks, setDisplayedDecks] = useState<deckExtended[]>([])
const [loading, setLoading] = useState<boolean>(true)
const [bsets, setBsets] = useState<bsetJson[]>([])
const [openSelectBset, setOpenSelectBset] = useState(false)
const router = useRouter()
const token = getCookie('JWT')
const [deckIdToDelete, setDeckIdToDelete] = useState("")
const deleteDialogRef = useRef<HTMLButtonElement>(null)
const [editDeckUrl, setEditDeckUrl] = useState("")
const [editDeckId, setEditDeckId] = useState("")
const [editDeckName, setEditDeckName] = useState("")
const [editDeckImporter, setEditDeckImporter] = useState("")
const [editDeckCommanderName, setEditDeckCommanderName] = useState("")
const editDialogRef = useRef<HTMLButtonElement>(null)
const { toast } = useToast()
useEffect(() => {
if(getCookie('JWT') == "") {
router.refresh()
router.push('/')
}
fetch('/api/account/decks/', {
method: "GET",
headers: {Authorization: 'Bearer ' + token},
}).then((res) => {
if(res.status == 200) {
res.json().then((apiData) => {
console.log(apiData.data)
setDecks(apiData.data)
setDisplayedDecks(apiData.data)
setLoading(false)
})
}
})
fetch('/api/json/misc/bsets.json').then((res) => {
if(res.status == 200) {
res.json().then((data) => {
setBsets(data)
})
}
})
},[])
function getDataFromLine(line: string){
if(line != "") {
const data = line.split(" ")
const amount = parseInt(data[0])
const name = data.slice(1).join(" ").split("/")[0].replace(/[^a-zA-Z0-9]/gim,"-").toLowerCase()
return {"sanitized_name":name, "amount":amount}
} else {
return null
}
}
function deleteDeck(id:string){
fetch('/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))
setDisplayedDecks(old => old.filter((deck: deck) => deck.id != id))
toast({
title: "Deck supprimé",
})
}
})
}
function updateDeckInput(txt:string){
setDeckImporter(txt)
const lines = txt.split("\n")
setDeckCommanderName(lines[lines.length - 1].slice(1))
}
function updateEditDeckInput(txt:string){
setEditDeckImporter(txt)
const lines = txt.split("\n")
setEditDeckCommanderName(lines[lines.length - 1].slice(1))
}
function editDialogHandler(id:string){
const deck_to_edit = decks.find((deck: deck) => deck.id === id)
setEditDeckId(id)
setEditDeckName(deck_to_edit ? deck_to_edit.name : "")
setEditDeckUrl(deck_to_edit && deck_to_edit.url != null ? deck_to_edit.url : "")
editDialogRef.current?.click()
}
function importDeck(){
const deckText = deckImporter
let lines = deckText.split("\n")
lines = lines.filter((line) => line.match(/[0-9]+\s[\w]+/) != undefined)
const dataToSend : deckAPIProps = { name: deckName, url: deckUrl, selected_bset: selectedBset.replace(/[^a-zA-Z0-9]/gim,"-").toLowerCase() ,commander_name: getDataFromLine(deckCommanderName)!.sanitized_name, cards: [] }
lines.slice(0, lines.length - 1).forEach((line: string) => {
const data = getDataFromLine(line)
if(data != null) {
dataToSend.cards.push(data)
}
});
fetch('/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: deckExtended = apiData.data
setDisplayedDecks([...decks, new_deck])
setDecks(oldDecks => [...oldDecks, new_deck])
setDeckName("")
setDeckImporter("")
setSelectedBset("")
setDeckCommanderName("")
setDeckUrl("")
toast({
title: "Deck " + new_deck.name + " créé.",
})
})
} else if (res.status == 401) {
res.json().then((apiData) => {
toast({
title: "ERROR : " + apiData.message,
})
})
}
})
}
function updateDeck(){
const deckText = editDeckImporter
let lines = deckText.split("\n")
lines = lines.filter((line) => line.match(/[0-9]+\s[\w]+/) != undefined)
const dataToSend : deckAPIProps = { id: editDeckId, name: editDeckName, url: editDeckUrl, commander_name: editDeckCommanderName != "" ? getDataFromLine(editDeckCommanderName)!.sanitized_name : undefined, cards: [] }
if (editDeckImporter != "") {
lines.slice(0, lines.length - 1).forEach((line: string) => {
const data = getDataFromLine(line)
if(data != null) {
dataToSend.cards.push(data)
}
})
}
console.log(dataToSend)
fetch('/api/account/decks/update', {
method: "PUT",
headers: {Authorization: 'Bearer ' + token},
body: JSON.stringify(dataToSend)
}).then((res) => {
if(res.status == 200) {
res.json().then((apiData) => {
const new_deck = apiData.data
const displayedDecksIndex = displayedDecks.findIndex((deck) => deck.id == new_deck.id)
if (displayedDecksIndex != -1) { setDisplayedDecks(oldDecks => [...oldDecks.slice(0, displayedDecksIndex),new_deck,...oldDecks.slice(displayedDecksIndex + 1)]) }
const decksIndex = decks.findIndex((deck) => deck.id == new_deck.id)
if (decksIndex != -1) { setDecks(oldDecks => [...oldDecks.slice(0, decksIndex),new_deck,...oldDecks.slice(decksIndex + 1)]) }
console.log(apiData)
toast({
title: "Deck " + new_deck.name + " mis à jour.",
})
})
} else if (res.status == 401) {
res.json().then((apiData) => {
toast({
title: "ERROR : " + apiData.message,
})
})
}
})
}
function deleteAlertHandler(id:string, mode:string) {
if (mode == "set") {
deleteDialogRef.current?.click()
} else if (mode == "delete") {
deleteDeck(deckIdToDelete)
}
setDeckIdToDelete(id)
}
function filterDecks(searchString:string) {
if(searchString != "") {
setDisplayedDecks(decks.filter((deck) => deck.name.toLowerCase().includes(searchString.toLowerCase())))
} else {
setDisplayedDecks(decks)
}
}
return (
<div className="flex flex-col items-center mt-24" >
<div className="flex flex-col max-w-5xl w-full gap-12 items-center">
<div className="flex flex-row gap-12 w-full">
<div className="flex flex-col gap-4 grow-4 w-full">
<Textarea value={deckImporter} onChange={(e) => updateDeckInput(e.target.value)} placeholder="Deck List dans le format MTGO.&#10;&#10;Exemple :&#10;&#10;1 Agate-Blade Assassin&#10;1 Agate-Blade Assassin&#10;1 Agate-Blade Assassin&#10;1 Agate-Blade Assassin" className="h-60" />
<div><span className="font-bold">Commandant :</span> <span className="italic">{deckCommanderName}</span></div>
</div>
<div className="flex flex-col gap-4 w-full">
<Input className="text-3xl" value={deckName} onChange={(e) => setDeckName(e.target.value)} placeholder="Nom du deck" />
<Input value={deckUrl} onChange={(e) => setDeckUrl(e.target.value)} placeholder="URL du Deck (Facultatif)" />
<Popover open={openSelectBset} onOpenChange={setOpenSelectBset}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={openSelectBset}
className="w-full justify-between"
>
{ selectedBset && (
<div className="flex flex-row gap-2 items-center">
<div className="flex flex-row gap-1">
{ bsets.find((bset: bset) => bset.sanitized_name === selectedBset)?.icons.map((icon) => (
<img key={icon} src={icon} className="h-3" loading="lazy" />
))}
</div>
<span>{ bsets.find((bset: bset) => bset.sanitized_name === selectedBset)?.name }</span>
</div>
)}
{ !selectedBset && (
<span>Selectionnez un Bset...</span>
)}
</Button>
</PopoverTrigger>
<PopoverContent>
<Command>
<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>
<Button onClick={importDeck}>Importer</Button>
</div>
</div>
{/* Edit Dialog */}
<Dialog>
<DialogTrigger className="hidden" asChild>
<Button ref={editDialogRef} variant="outline">Edit Profile</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Modifier le deck</DialogTitle>
</DialogHeader>
<div className="flex flex-col gap-4">
<Input className="text-3xl" value={editDeckName} onChange={(e) => setEditDeckName(e.target.value)} placeholder="Nom du deck" />
<Input value={editDeckUrl} onChange={(e) => setEditDeckUrl(e.target.value)} placeholder="URL du deck" />
<Textarea value={editDeckImporter} onChange={(e) => updateEditDeckInput(e.target.value)} placeholder="Deck List dans le format MTGO.&#10;&#10;Exemple :&#10;&#10;1 Agate-Blade Assassin&#10;1 Agate-Blade Assassin&#10;1 Agate-Blade Assassin&#10;1 Agate-Blade Assassin" className="min-h-48" />
<div><span className="font-bold">Commandant :</span> <span className="italic">{editDeckCommanderName}</span></div>
</div>
<DialogFooter>
<DialogClose>
<Button onClick={updateDeck} type="submit">Sauvegarder</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
{/* Delete Dialog */}
<AlertDialog>
<AlertDialogTrigger className="hidden" ref={deleteDialogRef}>Open</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Es-tu sûr·e?</AlertDialogTitle>
<AlertDialogDescription>
Cette action ne peut être annulée et le deck sera effacé de nos serveurs.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel onClick={() => deleteAlertHandler("","set")}>Annuler</AlertDialogCancel>
<AlertDialogAction onClick={() => deleteAlertHandler("","delete")}>Supprimer</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
{/* Separator */}
<div className="w-full h-0.5 rounded-md bg-stone-200"></div>
{/* Decks Display */}
<div className="flex flex-col gap-4 w-full items-center">
<Input className="text-3xl" placeholder="Rechercher des Decks..." onChange={(e) => filterDecks(e.target.value)}/>
{ loading && (
<Spinner className="mt-12" />
)}
{ !loading && (
<div className="w-full">
<Accordion type="single" collapsible className="w-full">
{ displayedDecks.map((deck) => (
<AccordionItem key={deck.id} value={deck.id}>
<AccordionTrigger trashFunction={() => deleteAlertHandler(deck.id, "set")} editFunction={() => editDialogHandler(deck.id)} className="text-3xl"><div className="flex flex-col"><span>{deck.name}</span></div></AccordionTrigger>
<a className="text-sm text-orange-500" target={deck.url ? "_blank" : ""} href={deck.url ? deck.url : "#"}>{deck.url ? deck.url : "No url"}</a>
<AccordionContent className="flex flex-col gap-4">
<MTGCardTextHover carte={deck.commander} className="text-lg" />
<div className="grid grid-flow-rows grid-cols-3 gap-2">
{ deck.cartes.map((carte_parent: cartes_dans_deckExtended) => (
<div key={carte_parent.carte.id} className="flex flex-row gap-2">
{carte_parent.amount}x <MTGCardTextHover carte={carte_parent.carte} className="text-stone-500" />
</div>
))}
</div>
</AccordionContent>
</AccordionItem>
))}
</Accordion>
</div>
)}
</div>
</div>
<Toaster />
</div>
);
}

View file

@ -1,58 +0,0 @@
'use client'
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { useEffect } from "react"
import { useRouter } from 'next/navigation'
import { setCookie, getCookie } from "@/lib/utils"
export default function Signin() {
const router = useRouter()
useEffect(() => {
if(getCookie('JWT') == "") {
router.refresh()
router.push('/')
}
},[])
function disconnect(){
setCookie('JWT','',-1)
// INFO : We need to hard refresh for the NavigationBar to be updated
const base_url = window.location.origin
window.location.replace(base_url)
}
return (
<>
<div className="flex flex-col items-center mt-24" >
<h1 className="text-3xl mb-4">Mon compte</h1>
<Card className="w-full max-w-xs">
<CardHeader>
<CardTitle>Administration</CardTitle>
</CardHeader>
<CardContent className="flex flex-col gap-4">
<a href="/admin/bsets" className="w-full"><Button className="w-auto">Éditer les BSets</Button></a>
<Button className="w-auto" disabled={true}>Éditer les cartes</Button>
<Button className="w-auto" disabled={true}>Éditer les joueur·euses</Button>
</CardContent>
</Card>
<Card className="w-full max-w-xs">
<CardHeader>
<CardTitle>Profile</CardTitle>
</CardHeader>
<CardContent className="flex flex-col gap-4">
<Button className="w-auto" disabled={true}>Changer mon mot de passe</Button>
<Button onClick={disconnect} className="w-auto">Deconnexion</Button>
</CardContent>
</Card>
</div>
</>
);
}

View file

@ -1,63 +0,0 @@
'use client'
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Button } from "@/components/ui/button"
import { useRef, useEffect } from "react"
import { useRouter } from 'next/navigation'
import { setCookie, getCookie } from "@/lib/utils"
export default function Signin() {
const router = useRouter()
const usernameField = useRef<HTMLInputElement>(null)
const passwordField = useRef<HTMLInputElement>(null)
useEffect(() => {
if(getCookie('JWT') != "") {
router.refresh()
router.push('/')
}
}, [])
async function signIn() {
const data = {
"email": usernameField.current!.value,
"password": passwordField.current!.value
}
console.log(usernameField.current!.value + " - " + passwordField.current!.value)
const response = await fetch("/api/auth/signin",
{
method: "post",
body: JSON.stringify(data)
})
if(response.status == 200) {
const responseData = await response.json()
setCookie('JWT', responseData["JWT"], 7)
const base_url = window.location.origin
window.location.replace(base_url)
}
}
return (
<>
<div className="flex flex-col items-center mt-24" >
<Card className="w-full max-w-xs">
<CardHeader>
<CardTitle>Connectez vous</CardTitle>
</CardHeader>
<CardContent className="flex flex-col gap-4">
<Input ref={usernameField} placeholder="Email" />
<Input ref={passwordField} placeholder="Mot de passe" type="password"/>
<Button onClick={signIn} className="w-auto">Connexion</Button>
</CardContent>
</Card>
</div>
</>
);
}

View file

@ -1,192 +0,0 @@
'use client'
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
import {
Dialog,
DialogContent,
DialogHeader,
DialogFooter,
DialogClose,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command"
import { Badge } from "@/components/ui/badge"
import { Input } from "@/components/ui/input"
import { Button } from "@/components/ui/button"
import { useState, useEffect, useRef } from "react"
import { getCookie } from "@/lib/utils"
import type { set, bset } from "@prisma/client";
interface bsetExtended extends bset {
sets: set[]
}
export default function Home() {
const [setList, setSetList] = useState<set[]>([])
const [bsetList, setBsetList] = useState<bsetExtended[]>([])
const [selectedSets, setSelectedSets] = useState<set[]>([])
const BSetNameInput = useRef<HTMLInputElement>(null)
const token = getCookie('JWT')
useEffect(() => {
fetch('/api/admin/sets', {
method: "GET",
headers: {Authorization: 'Bearer ' + token}
}).then((res) => {
console.log(res.status)
if(res.status == 200) {
res.json().then((setsData) => {
setSetList(setsData.data)
})
}
})
fetch('/api/admin/bsets', {
method: "GET",
headers: {Authorization: 'Bearer ' + token}
}).then((res) => {
console.log(res.status)
if(res.status == 200) {
res.json().then((bsetsData) => {
setBsetList(bsetsData.data)
})
}
})
}, [])
function addSetToBSet(currentValue: string) {
const selected = setList.filter((set: set) => set.name_en == currentValue)[0]
const isAlreadyIn = selectedSets.filter((set: set) => set.name_en == currentValue).length != 0
if(!isAlreadyIn){
setSelectedSets(old => [...old, selected])
}
}
function removeSetToBSet(currentValue: string) {
setSelectedSets(old => old.filter((set: set) => set.name_en != currentValue))
}
function createBSet(){
const bset_name = BSetNameInput.current!.value
const data : { bset_name: string, selected_sets: string[] } = { bset_name , selected_sets: [] }
selectedSets.forEach((set: set) => data.selected_sets.push(set.id))
BSetNameInput.current!.value = ""
fetch('/api/admin/bsets/create', {
method: "POST",
headers: {Authorization: 'Bearer ' + token},
body: JSON.stringify(data)
}).then((res) => {
if(res.status == 200) {
res.json().then((apiData) => {
const addedBset : bsetExtended = { sanitized_name: bset_name.replace(/[^a-zA-Z0-9]/gim,"-").toLowerCase() ,id: apiData.data, name: bset_name, sets: selectedSets }
setBsetList(old => [...old, addedBset])
setSelectedSets([])
})
}
})
}
function deleteBSet(id: string){
fetch('/api/admin/bsets/delete', {
method: "DELETE",
headers: {Authorization: 'Bearer ' + token},
body: JSON.stringify({ id })
}).then((res) => {
if(res.status == 200) {
setBsetList(old => old.filter((bset) => bset.id != id))
}
})
}
return (
<div className="flex flex-col w-full items-center mt-32">
<div className="max-w-3xl w-full flex flex-col items-center gap-8">
<Dialog>
<DialogTrigger asChild><Button>Créer un nouveau BSet</Button></DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Créer un nouveau BSet</DialogTitle>
</DialogHeader>
<div className="flex flex-col gap-4">
<Input ref={BSetNameInput} placeholder="Nom du BSet" />
<div className="flex flex-row flex-wrap gap-4">
{selectedSets.map((set) => (
<Badge onClick={() => {removeSetToBSet(set.name_en)}} className="flex flex-row gap-2 cursor-pointer" key={set.id}><span>{set.name_en}</span><span>x</span></Badge>
))}
</div>
<Command>
<CommandInput placeholder="Rechercher une extension..." />
<CommandList>
<CommandEmpty>Pas dextension trouvée...</CommandEmpty>
<CommandGroup>
{setList.map((set) => (
<CommandItem
key={set.id}
value={set.name_en}
onSelect={addSetToBSet}
>
<img src={set.icon_svg_uri} loading="lazy" className="w-8 h-8" />
{set.name_en}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</div>
<DialogFooter className="sm:justify-start">
<DialogClose asChild>
<Button type="button" variant="secondary">
Close
</Button>
</DialogClose>
<DialogClose asChild>
<Button onClick={createBSet} type="button" variant="secondary">
Save
</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
<Table className="max-w-3xl">
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
<TableHead>Extensions</TableHead>
<TableHead>Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{ bsetList.map((bset) => (
<TableRow key={bset.id}>
<TableCell>{bset.name}</TableCell>
<TableCell className="flex flex-row gap-4 items-center mt-2 flex-wrap">
{ bset.sets.map((set) => (
<Badge key={set.id}>{set.name_en}</Badge>
))}
</TableCell>
<TableCell><Button onClick={() => {deleteBSet(bset.id)}} variant="destructive">Supprimer</Button></TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</div>
);
}

View file

@ -1,176 +0,0 @@
import { NextResponse, NextRequest } from 'next/server'
import { validateToken, decryptToken } from '@/lib/jwt'
import { db } from "@/lib/db"
import { logging } from '@/lib/logging'
interface orCardFilterProps {
sanitized_name: string
OR: orSetFilterProps[]
}
interface orSetFilterProps {
set_code: string
}
interface cardEntryAPIProps {
amount: number,
sanitized_name: string,
}
export async function POST(req: NextRequest) {
try {
const token = req?.headers.get("authorization")?.split(" ")[1]
const { name, cards, url, selected_bset, commander_name } = await req.json()
console.log(url)
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})
})
let cardsData = await db.carte.findMany({
where: {
OR: set_codes
}
})
// Sort cards to select non promo types first and fallback to promo cards if not found
cardsData = cardsData.sort((a,b) => +a.is_promo - +b.is_promo)
const cardsNotFound = []
let allCardFound = true
if(cardsData.findIndex(cardData => cardData.sanitized_name == commander_name) == -1){
if(cardsData.findIndex(cardData => cardData.sanitized_name.includes(commander_name)) == -1){
console.log("Not Found : " + commander_name)
allCardFound = false
cardsNotFound.push(commander_name)
}
}
cards.forEach((card: cardEntryAPIProps) => {
if(cardsData.findIndex(cardData => cardData.sanitized_name == card.sanitized_name) == -1){
if(cardsData.findIndex(cardData => cardData.sanitized_name.includes(card.sanitized_name)) == -1 ){
allCardFound = false
console.log("Not Found : Bset = " + selected_bset + " , name = " + card.sanitized_name)
cardsNotFound.push(card.sanitized_name)
}
}
})
if(!allCardFound) {
return NextResponse.json({"message": "Some cards were not found... " + cardsNotFound.join(", ")},{
status: 401,
});
}
const commander_card = cardsData.findIndex(cardData => cardData.sanitized_name == commander_name) == -1 ? cardsData[cardsData.findIndex(cardData => cardData.sanitized_name.includes(commander_name))] : cardsData[cardsData.findIndex(cardData => cardData.sanitized_name == commander_name)]
const deck = await db.deck.create({
data: {
name,
url,
color_identity: commander_card.color_identity,
utilisateurice: {
connect: {
id: tokenData.id
}
},
commander: {
connect: {
id: commander_card.id
}
},
bset: {
connect: {
id: bset?.id
}
}
}
})
cards.forEach(async (card: cardEntryAPIProps) => {
const cardData_id = cardsData.findIndex(cardData => cardData.sanitized_name == card.sanitized_name) == -1 ? cardsData[cardsData.findIndex(cardData => cardData.sanitized_name.includes(card.sanitized_name))].id : cardsData[cardsData.findIndex(cardData => cardData.sanitized_name == card.sanitized_name)].id
await db.cartes_dans_deck.create({
data: {
amount: card.amount,
carte: {
connect: {
id: cardData_id
}
},
deck: {
connect: {
id: deck.id
}
}
}
})
})
// Necessary to fetch complete deck with cards to display it to the user
const deck_complete = await db.deck.findFirst({
where: {
id: deck.id
},
relationLoadStrategy: "join",
include: {
cartes: {
include: {
carte: true
}
},
commander: true
}
})
logging("Deck created by " + tokenData.username + " : " + name)
return NextResponse.json({"data": deck_complete, "message": "Deck created !"},{
status: 200,
});
} catch (error) {
console.log(error)
return NextResponse.json(
{ error: "Failed, check console" },
{
status: 500,
}
);
}
}

View file

@ -1,55 +0,0 @@
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

@ -1,50 +0,0 @@
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
},
relationLoadStrategy: "join",
include: {
cartes: {
include: {
carte: true
}
},
commander: true
}
})
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

@ -1,194 +0,0 @@
import { NextResponse, NextRequest } from 'next/server'
import { validateToken } 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,
}
export async function PUT(req: NextRequest) {
try {
const token = req?.headers.get("authorization")?.split(" ")[1]
const { id, name, cards, url, 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,
});
}
if( id == undefined ) {
return NextResponse.json({"message": "Wrong data in the request."},{
status: 401,
});
}
const deck_to_update = await db.deck.findFirst({
where: {
id: id
},
relationLoadStrategy: "join",
include: {
bset: {
include: {
sets: true
}
}
}
})
if(deck_to_update == undefined) {
return NextResponse.json({"message": "No deck with this ID."},{
status: 401,
});
}
if (cards.length != 0 && commander_name != undefined) {
// Delete previous cards from deck
await db.cartes_dans_deck.deleteMany({
where: {
deck_id: id
}
})
const bset = deck_to_update?.bset
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})
})
let cardsData = await db.carte.findMany({
where: {
OR: set_codes
}
})
// Sort cards to select non promo types first and fallback to promo cards if not found
cardsData = cardsData.sort((a,b) => +a.is_promo - +b.is_promo)
let allCardFound = true
if(cardsData.findIndex(cardData => cardData.sanitized_name == commander_name) == -1){
if(cardsData.findIndex(cardData => cardData.sanitized_name.includes(commander_name)) == -1){
allCardFound = false
}
}
cards.forEach((card: cardEntryAPIProps) => {
if(cardsData.findIndex(cardData => cardData.sanitized_name == card.sanitized_name) == -1){
if(cardsData.findIndex(cardData => cardData.sanitized_name.includes(card.sanitized_name)) == -1 ){
allCardFound = false
}
}
})
if(!allCardFound) {
return NextResponse.json({"message": "Some cards were not found..."},{
status: 401,
});
}
const commander_card = cardsData.findIndex(cardData => cardData.sanitized_name == commander_name) == -1 ? cardsData[cardsData.findIndex(cardData => cardData.sanitized_name.includes(commander_name))] : cardsData[cardsData.findIndex(cardData => cardData.sanitized_name == commander_name)]
cards.forEach(async (card: cardEntryAPIProps) => {
const cardData_id = cardsData.findIndex(cardData => cardData.sanitized_name == card.sanitized_name) == -1 ? cardsData[cardsData.findIndex(cardData => cardData.sanitized_name.includes(card.sanitized_name))].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: id
}
}
}
})
})
const deck = await db.deck.update({
where: {
id: id,
},
data: {
name,
url,
color_identity: commander_card.color_identity,
commander: {
connect: {
id: commander_card.id
}
},
},
relationLoadStrategy: "join",
include: {
cartes: {
include: {
carte: true
}
},
commander: true
}
})
return NextResponse.json({"data": deck, "message": "Deck created !"},{
status: 200,
})
} else {
const deck = await db.deck.update({
where: {
id: id,
},
data: {
name,
url,
},
relationLoadStrategy: "join",
include: {
cartes: {
include: {
carte: true
}
},
commander: true
}
})
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

@ -1,70 +0,0 @@
import { NextResponse, NextRequest } from 'next/server'
import { validateToken, decryptToken } from '@/lib/jwt'
import { db } from "@/lib/db"
interface orFilterProps {
id: string
}
export async function POST(req: NextRequest) {
try {
const token = req?.headers.get("authorization")?.split(" ")[1]
const { bset_name, selected_sets } = await req.json()
if(token !== undefined) {
if(validateToken(token)) {
const tokenData = decryptToken(token)
if(tokenData.admin == true){
if(bset_name != undefined && selected_sets != undefined) {
const bset = await db.bset.create({
data: {
name: bset_name,
sanitized_name: bset_name.replace(/[^a-zA-Z0-9]/gim,"-").toLowerCase()
}
})
const or_filter: orFilterProps[] = []
selected_sets.forEach((set_id: string) => {
or_filter.push({ id: set_id })
})
await db.set.updateMany({
where: {
OR: or_filter
},
data: {
bset_id: bset.id
}
})
return NextResponse.json({"data": bset.id, "message": "BSet created !"},{
status: 200,
});
}
} else {
return NextResponse.json({"message": "You're not admin."},{
status: 401,
});
}
} else {
return NextResponse.json({"message": "Your token is not valid."},{
status: 401,
});
}
} else {
return NextResponse.json({"message": "You did not provide a token."},{
status: 401,
});
}
} catch (error) {
console.log(error)
return NextResponse.json(
{ error: "Failed, check console" },
{
status: 500,
}
);
}
}

View file

@ -1,57 +0,0 @@
import { NextResponse, NextRequest } from 'next/server'
import { validateToken, decryptToken } from '@/lib/jwt'
import { db } from "@/lib/db"
export async function DELETE(req: NextRequest) {
try {
const token = req?.headers.get("authorization")?.split(" ")[1]
const { id } = await req.json()
if(token !== undefined) {
if(validateToken(token)) {
const tokenData = decryptToken(token)
if(tokenData.admin == true){
if(id != undefined) {
await db.set.updateMany({
where: {
bset_id: id
},
data: {
bset_id: null
}
})
await db.bset.delete({
where: { id }
})
return NextResponse.json({"message": "BSet deleted"},{
status: 200,
});
}
} else {
return NextResponse.json({"message": "You're not admin."},{
status: 401,
});
}
} else {
return NextResponse.json({"message": "Your token is not valid."},{
status: 401,
});
}
} else {
return NextResponse.json({"message": "You did not provide a token."},{
status: 401,
});
}
} catch (error) {
console.log(error)
return NextResponse.json(
{ error: "Failed, check console" },
{
status: 500,
}
);
}
}

View file

@ -1,46 +0,0 @@
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) {
if(validateToken(token)) {
const tokenData = decryptToken(token)
if(tokenData.admin == true){
const bsets = await db.bset.findMany({
relationLoadStrategy: "join",
include: {
sets: true
}
})
return NextResponse.json({"data": bsets},{
status: 200,
});
} else {
return NextResponse.json({"message": "You're not admin."},{
status: 401,
});
}
} else {
return NextResponse.json({"message": "Your token is not valid."},{
status: 401,
});
}
} else {
return NextResponse.json({"message": "You did not provide a token."},{
status: 401,
});
}
} catch (error) {
console.log(error)
return NextResponse.json(
{ error: "Failed, check console" },
{
status: 500,
}
);
}
}

View file

@ -1,45 +0,0 @@
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) {
if(validateToken(token)) {
const tokenData = decryptToken(token)
if(tokenData.admin == true){
const sets = await db.set.findMany({
where: {
set_type: "expansion"
}
})
return NextResponse.json({"data": sets,"message": "You're admin ! Congrats !"},{
status: 200,
});
} else {
return NextResponse.json({"message": "You're not admin."},{
status: 401,
});
}
} else {
return NextResponse.json({"message": "Your token is not valid."},{
status: 401,
});
}
} else {
return NextResponse.json({"message": "You did not provide a token."},{
status: 401,
});
}
} catch (error) {
console.log(error)
return NextResponse.json(
{ error: "Failed, check console" },
{
status: 500,
}
);
}
}

View file

@ -1,48 +0,0 @@
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) {
if(validateToken(token)) {
const tokenData = decryptToken(token)
if(tokenData.admin == true){
const sets = await db.set.findMany({
where: {
AND: [
{ set_type: "expansion"},
{ bset_id: null}
]
}
})
return NextResponse.json({"data": sets,"message": "You're admin ! Congrats !"},{
status: 200,
});
} else {
return NextResponse.json({"message": "You're not admin."},{
status: 401,
});
}
} else {
return NextResponse.json({"message": "Your token is not valid."},{
status: 401,
});
}
} else {
return NextResponse.json({"message": "You did not provide a token."},{
status: 401,
});
}
} catch (error) {
console.log(error)
return NextResponse.json(
{ error: "Failed, check console" },
{
status: 500,
}
);
}
}

View file

@ -1,46 +0,0 @@
import { NextResponse, NextRequest } from 'next/server'
import { createToken } from '@/lib/jwt'
import { createHmac } from "crypto"
import { db } from '@/lib/db'
import { logging } from '@/lib/logging'
const secret = process.env.PASSWORD_SECRET ? process.env.PASSWORD_SECRET : ""
export async function POST(req: NextRequest) {
try {
const { email, password } = await req.json()
const user = await db.utilisateurice.findFirst({
where: {
email
}
})
if (user !== undefined){
if(createHmac('sha256',secret).update(password).digest('hex') == user!.password) {
const token = createToken({data: {username: user!.username, admin: user!.admin, id: user!.id}, maxAge: 60*60*24*7})
logging("User " + user!.username + " has connected.")
return NextResponse.json({"JWT": token},{
status: 200,
});
} else {
return NextResponse.json({error: "Wrong credentials"},{
status: 401,
});
}
} else {
return NextResponse.json({error: "Wrong credentials"},{
status: 401,
});
}
} catch (error) {
console.log(error)
return NextResponse.json(
{ error: "Failed, check console" },
{
status: 500,
}
);
}
}

View file

@ -1,32 +0,0 @@
import { NextResponse, NextRequest } from 'next/server'
import { validateToken } from '@/lib/jwt'
export async function POST(req: NextRequest) {
try {
const token = req?.headers.get("authorization")?.split(" ")[1]
if(token !== undefined) {
if(validateToken(token)) {
return NextResponse.json({"message": "Your token is valid!"},{
status: 200,
});
} else {
return NextResponse.json({"message": "Your token is not valid."},{
status: 401,
});
}
} else {
return NextResponse.json({"message": "You did not provide a token."},{
status: 401,
});
}
} catch (error) {
console.log(error)
return NextResponse.json(
{ error: "Failed, check console" },
{
status: 500,
}
);
}
}

View file

@ -1,36 +0,0 @@
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

@ -1,25 +0,0 @@
import { NextResponse, NextRequest } from 'next/server'
import fs from "fs";
import path from "path";
export async function GET(req: NextRequest, {params}: { params: { slug: string[] } }) {
try {
const jsonPath = await (params).slug.join("/")
const filePath = path.resolve(".",`data/${jsonPath}`);
const jsonBuffer = fs.readFileSync(filePath);
return new NextResponse(jsonBuffer,{
status: 200,
headers: {
"content-type": "application/json",
}
});
} catch (error) {
console.log(error)
return NextResponse.json(
{ error: "Failed, check console" },
{
status: 500,
}
);
}
}

View file

@ -1,41 +0,0 @@
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 || !validateToken(token)) {
return NextResponse.json({"message": "You did not provide token or it is in the wrong format"},{
status: 401,
});
}
const tokenData = decryptToken(token)
if (!tokenData.admin) {
return NextResponse.json({"message": "You need to be an admin to use this endpoint."},{
status: 401,
});
}
const decks = await db.deck.findMany()
const bsets = await db.bset.findMany()
const users = await db.utilisateurice.findMany()
return NextResponse.json({"data": { "decks": decks.length, "bsets": bsets.length, "users": users.length },"message": "Here are the stats"},{
status: 200,
});
} catch (error) {
console.log(error)
return NextResponse.json(
{ error: "Failed, check console" },
{
status: 500,
}
);
}
}

View file

@ -1,10 +0,0 @@
'use server'
import PageContent from './page_content'
export default async function Home({ params }: { params: { bset: string } } ) {
const bset = (await params).bset
return (
<PageContent bset={bset}/>
);
}

View file

@ -1,94 +0,0 @@
'use client'
import { useEffect, useState, useRef } from 'react'
import { CardGroup } from '@/components/ui/card-group'
import {CommanderIcon, PlaneswalkerIcon, SorceryIcon, InstantIcon, CreatureIcon, EnchantmentIcon, LandIcon, ArtifactIcon} from '@/components/ui/symbols-icons'
interface PageContentProps {
bset: string
}
export default function PageContent({bset}: PageContentProps) {
const [commanderList, setCommanderList] = useState([])
const [creatureList, setCreatureList] = useState([])
const [planeswalkerList, setPlaneswalkerList] = useState([])
const [instantList, setInstantList] = useState([])
const [sorceryList, setSorceryList] = useState([])
const [artifactList, setArtifactList] = useState([])
const [enchantmentList, setEnchantmentList] = useState([])
const [landList, setLandList] = useState([])
const [scrollState, setScrollState] = useState("commander")
const CardListRef = useRef<HTMLDivElement>(null)
useEffect(() => {
fetch('/api/json/bset/'+bset+'.json').then((res) => {
if(res.status == 200) {
res.json().then((data) => {
setCommanderList(data["commander"])
setCreatureList(data["creature"])
setPlaneswalkerList(data["planeswalker"])
setSorceryList(data["sorcery"])
setInstantList(data["instant"])
setEnchantmentList(data["enchantment"])
setLandList(data["land"])
setArtifactList(data["artifact"])
console.log(data)
})
}
})
}, [])
useEffect(() => {
const handleScroll = () => {
const windowHeight = window.innerHeight
const TOP_MARGIN = 0.1
const BOTTOM_MARGIN = 0.2
const card_children = CardListRef.current?.children
if (card_children) {
for (const child of card_children){
const targetBounds = child.getBoundingClientRect()
if( targetBounds.bottom > windowHeight * TOP_MARGIN && targetBounds.top < windowHeight * ( 1 - BOTTOM_MARGIN ) ) {
setScrollState(child.id)
break
}
}
}
};
// Add event listener to the window
window.addEventListener('scroll', handleScroll);
// Remove event listener when the component is unmounted
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, []);
return (
<div>
<div className="flex flex-col items-center w-full">
<div ref={CardListRef} className="flex flex-col items-center mt-24 gap-4 max-w-6xl">
<CardGroup className="scroll-mt-16" groupName={"Commandants"} Icon={CommanderIcon} id="commander" commanderUrl={true} showPercent={false} cards={commanderList} />
<CardGroup className="scroll-mt-16" groupName={"Planeswalker"} Icon={PlaneswalkerIcon} id="planeswalker" cards={planeswalkerList} />
<CardGroup className="scroll-mt-16" groupName={"Créatures"} Icon={CreatureIcon} id="creature" cards={creatureList} />
<CardGroup className="scroll-mt-16" groupName={"Rituels"} Icon={SorceryIcon} id="sorcery" cards={sorceryList} />
<CardGroup className="scroll-mt-16" groupName={"Artefacts"} Icon={ArtifactIcon} id="artifact" cards={artifactList} />
<CardGroup className="scroll-mt-16" groupName={"Éphémères"} Icon={InstantIcon} id="instant" cards={instantList} />
<CardGroup className="scroll-mt-16" groupName={"Enchantements"} Icon={EnchantmentIcon} id="enchantment" cards={enchantmentList} />
<CardGroup className="scroll-mt-16" groupName={"Terrains"} Icon={LandIcon} id="land" cards={landList} />
</div>
</div>
<div className="fixed top-80 ml-8 flex flex-col gap-2 text-stone-500">
<a href="#commander" className={`${scrollState == "commander" ? "text-black text-2xl" : "neutral-svg-filter"} flex flex-row items-center gap-1`}><CommanderIcon className={scrollState == "commander" ? "h-5" : "h-4"} />Commandants</a>
<a href="#planeswalker" className={`${scrollState == "planeswalker" ? "text-black text-2xl" : "neutral-svg-filter"} flex flex-row items-center gap-1`}><PlaneswalkerIcon className={scrollState == "planeswalker" ? "h-5" : "h-4"} />Planeswalker</a>
<a href="#creature" className={`${scrollState == "creature" ? "text-black text-2xl" : "neutral-svg-filter"} flex flex-row items-center gap-1`}><CreatureIcon className={scrollState == "creature" ? "h-5" : "h-4"} />Créatures</a>
<a href="#sorcery" className={`${scrollState == "sorcery" ? "text-black text-2xl" : "neutral-svg-filter"} flex flex-row items-center gap-1`}><SorceryIcon className={scrollState == "sorcery" ? "h-5" : "h-4"} />Rituels</a>
<a href="#artifact" className={`${scrollState == "artifact" ? "text-black text-2xl" : "neutral-svg-filter"} flex flex-row items-center gap-1`}><ArtifactIcon className={scrollState == "artifact" ? "h-5" : "h-4"} />Artefacts</a>
<a href="#instant" className={`${scrollState == "instant" ? "text-black text-2xl" : "neutral-svg-filter"} flex flex-row items-center gap-1`}><InstantIcon className={scrollState == "instant" ? "h-5" : "h-4"} />Éphémères</a>
<a href="#enchantment" className={`${scrollState == "enchantment" ? "text-black text-2xl" : "neutral-svg-filter"} flex flex-row items-center gap-1`}><EnchantmentIcon className={scrollState == "enchantment" ? "h-5" : "h-4"} />Enchantements</a>
<a href="#land" className={`${scrollState == "land" ? "text-black text-2xl" : "neutral-svg-filter"} flex flex-row items-center gap-1`}><LandIcon className={scrollState == "land" ? "h-5" : "h-4"} />Terrains</a>
</div>
</div>
);
}

View file

@ -1,62 +0,0 @@
'use client'
import { useEffect, useState } from 'react'
import { Input } from '@/components/ui/input'
import { Spinner } from '@/components/ui/spinner'
interface bsetJsonObject {
name: string,
sanitized_name: string,
set_codes: string[],
icons: string[]
}
export default function Home() {
const [displayedBsetList, setDisplayedBsetList] = useState([])
const [originalBsetList, setOriginalBsetList] = useState([])
const [loading, setLoading] = useState(true)
useEffect(() => {
fetch('/api/json/misc/bsets.json').then((res) => {
if(res.status == 200) {
res.json().then((data) => {
setLoading(false)
setOriginalBsetList(data)
setDisplayedBsetList(data)
})
}
})
}, [])
function filterBsetList(searchString:string) {
if(searchString != "") {
setDisplayedBsetList(originalBsetList.filter((bset:bsetJsonObject) => bset.name.toLowerCase().includes(searchString.toLowerCase())))
} else {
setDisplayedBsetList(originalBsetList)
}
}
return (
<div className="flex flex-col items-center mt-16">
<div className="flex flex-col p-16 max-w-6xl w-full gap-4 items-center">
<Input placeholder="Rechercher des BrawlSets..." onChange={(e) => filterBsetList(e.target.value)}/>
{ loading && <Spinner className='mt-24'/> }
{ !loading && (
<div className="grid grid-cols-3 gap-4 p-2 w-full">
{ displayedBsetList.map((bset: bsetJsonObject) => (
<a key={bset.name} className="flex flex-row gap-2 items-center text-stone-500" href={"/bset/" + bset.sanitized_name}>
<div className="flex flex-row gap-1">
{ bset.icons.map((icon) => (
<img key={icon} src={icon} loading="lazy" className="w-5 h-5"/>
))}
</div>
<span>{bset.name}</span>
</a>
))}
</div>
)}
</div>
</div>
);
}

View file

@ -1,10 +0,0 @@
'use server'
import PageContent from './page_content'
export default async function Home({ params }: { params: { color: string } } ) {
const color = (await params).color
return (
<PageContent color={color}/>
);
}

View file

@ -1,36 +0,0 @@
'use client'
import { useEffect, useState } from 'react'
import { CardGroup } from '@/components/ui/card-group'
import { Spinner } from '@/components/ui/spinner'
interface PageContentProps {
color: string
}
export default function PageContent({color}: PageContentProps) {
const [commanderCardList, setCommanderCardList] = useState([])
const [loading, setLoading] = useState(true)
useEffect(() => {
fetch('/api/json/commander/'+color+'.json').then((res) => {
if(res.status == 200) {
res.json().then((data) => {
const limit = 100
setCommanderCardList(data.slice(0,limit))
setLoading(false)
console.log(data)
})
}
})
}, [])
return (
<div className="flex flex-col items-center w-full">
<div className="flex flex-col items-center mt-24 mb-24 max-w-6xl">
{ loading && ( <Spinner className='mt-36' /> )}
{ !loading && ( <CardGroup groupName={"Top commandants - " + color} showPercent={false} commanderUrl={true} cards={commanderCardList} /> )}
</div>
</div>
);
}

View file

@ -1,10 +0,0 @@
'use server'
import PageContent from './page_content'
export default async function Home({ params }: { params: { card_name: string } } ) {
const card_name = (await params).card_name
return (
<PageContent card_name={card_name}/>
);
}

View file

@ -1,118 +0,0 @@
'use client'
import { useEffect, useRef, useState } from 'react'
import { CardGroup } from '@/components/ui/card-group'
import { Spinner } from '@/components/ui/spinner'
import {PlaneswalkerIcon, SorceryIcon, InstantIcon, CreatureIcon, EnchantmentIcon, LandIcon, ArtifactIcon} from '@/components/ui/symbols-icons'
import { MTGCard } from '@/components/ui/mtg-card'
interface PageContentProps {
card_name: string
}
interface commanderCardData {
name: string,
normal_image: string,
nbr_decks: number,
total_decks: number,
price: string,
cardmarket_uri: string,
}
export default function PageContent({card_name}: PageContentProps) {
const [highSynergyList, setHighSynergyList] = useState([])
const [creatureList, setCreatureList] = useState([])
const [planeswalkerList, setPlaneswalkerList] = useState([])
const [instantList, setInstantList] = useState([])
const [sorceryList, setSorceryList] = useState([])
const [artifactList, setArtifactList] = useState([])
const [enchantmentList, setEnchantmentList] = useState([])
const [landList, setLandList] = useState([])
const [commanderCard, setCommanderCard] = useState<commanderCardData | null>(null)
const [loading, setLoading] = useState(true)
const [scrollState, setScrollState] = useState("commander")
const CardListRef = useRef<HTMLDivElement>(null)
useEffect(() => {
fetch('/api/json/card-commander/'+card_name+'.json').then((res) => {
if(res.status == 200) {
res.json().then((data) => {
setCommanderCard(data["card_data"])
setHighSynergyList(data["high_synergy"])
setCreatureList(data["creature"])
setPlaneswalkerList(data["planeswalker"])
setSorceryList(data["sorcery"])
setInstantList(data["instant"])
setEnchantmentList(data["enchantment"])
setLandList(data["land"])
setArtifactList(data["artifact"])
setLoading(false)
console.log(data)
})
}
})
}, [])
useEffect(() => {
const handleScroll = () => {
const windowHeight = window.innerHeight
const TOP_MARGIN = 0.1
const BOTTOM_MARGIN = 0.2
const card_children = CardListRef.current?.children
if (card_children) {
for (const child of card_children){
const targetBounds = child.getBoundingClientRect()
if( targetBounds.bottom > windowHeight * TOP_MARGIN && targetBounds.top < windowHeight * ( 1 - BOTTOM_MARGIN ) ) {
setScrollState(child.id)
break
}
}
}
};
// Add event listener to the window
window.addEventListener('scroll', handleScroll);
// Remove event listener when the component is unmounted
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, []);
return (
<div>
<div className="flex flex-col items-center w-full">
<div className="flex flex-col items-center mt-24 mb-24 max-w-6xl">
{ loading && ( <Spinner className='mt-36' /> )}
{ !loading && (
<div ref={CardListRef} className="flex flex-col items-center mt-24 gap-4 max-w-6xl">
<div className="w-full flex flex-col items-center">
<MTGCard className="w-64 h-auto" cardname={commanderCard!.name} imageURI={commanderCard!.normal_image} url={undefined} nbrDecks={commanderCard?.nbr_decks} totalDecks={commanderCard?.total_decks} price={commanderCard?.price} cardmarketURI={commanderCard?.cardmarket_uri}/>
</div>
<CardGroup className="scroll-mt-16" groupName={"Haute synergie"} Icon={undefined} id="highsynergy" cards={highSynergyList} />
<CardGroup className="scroll-mt-16" groupName={"Planeswalker"} Icon={PlaneswalkerIcon} id="planeswalker" cards={planeswalkerList} />
<CardGroup className="scroll-mt-16" groupName={"Créatures"} Icon={CreatureIcon} id="creature" cards={creatureList} />
<CardGroup className="scroll-mt-16" groupName={"Rituels"} Icon={SorceryIcon} id="sorcery" cards={sorceryList} />
<CardGroup className="scroll-mt-16" groupName={"Artefacts"} Icon={ArtifactIcon} id="artifact" cards={artifactList} />
<CardGroup className="scroll-mt-16" groupName={"Éphémères"} Icon={InstantIcon} id="instant" cards={instantList} />
<CardGroup className="scroll-mt-16" groupName={"Enchantements"} Icon={EnchantmentIcon} id="enchantment" cards={enchantmentList} />
<CardGroup className="scroll-mt-16" groupName={"Terrains"} Icon={LandIcon} id="land" cards={landList} />
</div>
)}
</div>
</div>
<div className="fixed top-80 ml-8 flex flex-col gap-2 text-stone-500">
<a href="#highsynergy" className={`${scrollState == "highsynergy" ? "text-black text-2xl" : "neutral-svg-filter"} flex flex-row items-center gap-1`}><PlaneswalkerIcon className={scrollState == "highsynergy" ? "h-5" : "h-4"} />Haute synergie</a>
<a href="#planeswalker" className={`${scrollState == "planeswalker" ? "text-black text-2xl" : "neutral-svg-filter"} flex flex-row items-center gap-1`}><PlaneswalkerIcon className={scrollState == "planeswalker" ? "h-5" : "h-4"} />Planeswalker</a>
<a href="#creature" className={`${scrollState == "creature" ? "text-black text-2xl" : "neutral-svg-filter"} flex flex-row items-center gap-1`}><CreatureIcon className={scrollState == "creature" ? "h-5" : "h-4"} />Créatures</a>
<a href="#sorcery" className={`${scrollState == "sorcery" ? "text-black text-2xl" : "neutral-svg-filter"} flex flex-row items-center gap-1`}><SorceryIcon className={scrollState == "sorcery" ? "h-5" : "h-4"} />Rituels</a>
<a href="#artifact" className={`${scrollState == "artifact" ? "text-black text-2xl" : "neutral-svg-filter"} flex flex-row items-center gap-1`}><ArtifactIcon className={scrollState == "artifact" ? "h-5" : "h-4"} />Artefacts</a>
<a href="#instant" className={`${scrollState == "instant" ? "text-black text-2xl" : "neutral-svg-filter"} flex flex-row items-center gap-1`}><InstantIcon className={scrollState == "instant" ? "h-5" : "h-4"} />Éphémères</a>
<a href="#enchantment" className={`${scrollState == "enchantment" ? "text-black text-2xl" : "neutral-svg-filter"} flex flex-row items-center gap-1`}><EnchantmentIcon className={scrollState == "enchantment" ? "h-5" : "h-4"} />Enchantements</a>
<a href="#land" className={`${scrollState == "land" ? "text-black text-2xl" : "neutral-svg-filter"} flex flex-row items-center gap-1`}><LandIcon className={scrollState == "land" ? "h-5" : "h-4"} />Terrains</a>
</div>
</div>
);
}

View file

@ -1,31 +0,0 @@
'use client'
import { useEffect, useState } from 'react'
import { CardGroup } from '@/components/ui/card-group'
import { Spinner } from '@/components/ui/spinner'
export default function Home() {
const [commanderCardList, setCommanderCardList] = useState([])
const [loading, setLoading] = useState(true)
useEffect(() => {
fetch('/api/json/commander/top.json').then((res) => {
if(res.status == 200) {
res.json().then((data) => {
const limit = 100
setCommanderCardList(data.slice(0,limit))
setLoading(false)
console.log(data)
})
}
})
}, [])
return (
<div className="flex flex-col items-center w-full">
<div className="flex flex-col items-center mt-24 mb-24 max-w-6xl">
{ loading && ( <Spinner className='mt-36' /> )}
{ !loading && ( <CardGroup groupName="Top commandants" commanderUrl={true} showPercent={false} cards={commanderCardList} /> )}
</div>
</div>
);
}

View file

@ -1,49 +0,0 @@
'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('/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

@ -1,12 +0,0 @@
export default function Home() {
return (
<>
<div className="flex flex-col items-center mt-32">
<div className="flex flex-col items-center w-full">
<h1 className="text-3xl">Foire Aux Questions</h1>
<h2>WIP</h2>
</div>
</div>
</>
);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -1,115 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
body {
font-family: Arial, Helvetica, sans-serif;
}
@font-face {
font-family: "Beleren";
src: url("./fonts/Beleren2016-Bold.woff");
}
@font-face {
font-family: "Inter-Tight-Normal";
src: url("./fonts/inter-tight-latin-400-normal.ttf");
}
@font-face {
font-family: "Inter-Tight-Italic";
src: url("./fonts/inter-tight-latin-400-italic.ttf");
}
@font-face {
font-family: "Inter-Tight-Bold";
src: url("./fonts/inter-tight-latin-800-normal.ttf");
}
@font-face {
font-family: "Inter-Tight-Bold-Italic";
src: url("./fonts/inter-tight-latin-800-italic.ttf");
}
.font-beleren {
font-family: 'Beleren';
}
.font-inter-tight {
font-family: 'Inter-Tight-Normal';
}
.neutral-svg-filter {
filter: invert(49%) sepia(2%) saturate(1917%) hue-rotate(343deg) brightness(89%) contrast(85%);
}
@layer utilities {
.text-balance {
text-wrap: balance;
}
}
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
--card: 0 0% 100%;
--card-foreground: 240 10% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 240 10% 3.9%;
--primary: 240 5.9% 10%;
--primary-foreground: 0 0% 98%;
--secondary: 240 4.8% 95.9%;
--secondary-foreground: 240 5.9% 10%;
--muted: 240 4.8% 95.9%;
--muted-foreground: 240 3.8% 46.1%;
--accent: 240 4.8% 95.9%;
--accent-foreground: 240 5.9% 10%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 240 5.9% 90%;
--input: 240 5.9% 90%;
--ring: 240 10% 3.9%;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
--radius: 0.5rem;
}
.dark {
--background: 240 10% 3.9%;
--foreground: 0 0% 98%;
--card: 240 10% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 240 10% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 240 5.9% 10%;
--secondary: 240 3.7% 15.9%;
--secondary-foreground: 0 0% 98%;
--muted: 240 3.7% 15.9%;
--muted-foreground: 240 5% 64.9%;
--accent: 240 3.7% 15.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 240 3.7% 15.9%;
--input: 240 3.7% 15.9%;
--ring: 240 4.9% 83.9%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}

View file

@ -1,53 +0,0 @@
import type { Metadata } from "next";
import localFont from "next/font/local";
import "./globals.css";
import { cookies } from 'next/headers'
import { NavigationBar } from "@/components/ui/navigation-bar"
import { decryptToken } from '@/lib/jwt'
const geistSans = localFont({
src: "./fonts/GeistVF.woff",
variable: "--font-geist-sans",
weight: "100 900",
});
const geistMono = localFont({
src: "./fonts/GeistMonoVF.woff",
variable: "--font-geist-mono",
weight: "100 900",
});
export const metadata: Metadata = {
title: "Brawl Set",
description: "Un mode de jeu MTG 100% rennais",
};
export default async function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const cookieStore = await cookies()
const JWT = cookieStore.get("JWT")
let username = ""
if(JWT !== undefined){
username = decryptToken(JWT.value)?.username
}
return (
<html lang="fr">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased font-inter-tight`}
>
<NavigationBar username={username} isLoggedIn={JWT !== undefined} />
<div className="min-h-screen scroll-smooth">
{children}
</div>
<div className="flex flex-col p-4 items-center text-sm">
<h1>Brawlset is unofficial Fan Content permitted under the Fan Content Policy. Not approved/endorsed by Wizards. Portions of the materials used are property of Wizards of the Coast. ©Wizards of the Coast LLC.</h1>
</div>
</body>
</html>
);
}

View file

@ -1,12 +0,0 @@
export default function Home() {
return (
<>
<div className="flex flex-col items-center mt-32">
<p className="mb-12 text-3xl text-orange-500">CE SITE EST EN COURS DE DEVELOPPEMENT - NE SAUVEGARDEZ PAS VOS DONNÉES UNIQUEMENT SUR BRAWLSET</p>
<h1 className="text-8xl font-beleren">The BrawlSet</h1>
<p className="text-center text-stone-500 mt-12">Un système de règles MTG basé sur le mode de jeu commander et inventé à Rennes, pays de la galette saucisse.<br></br>
Pour plus d&apos;informations allez voir les <a href="/rules" className="text-orange-500">règles</a> ou la <a href="/faq" className="text-orange-500">FAQ</a>.</p>
</div>
</>
);
}

View file

@ -1,37 +0,0 @@
export default function Home() {
return (
<>
<div className="flex flex-col items-center mt-32">
<div className="flex flex-col items-center w-full">
<h1 className="text-3xl">BrawlSet Canon Règles Officielles</h1>
<h2>BrawlSet Committee</h2>
<h2>17 Octobre 2022</h2>
</div>
<div className="flex flex-col max-w-5xl gap-4 mt-8 mb-32">
<h2 className="font-bold">903.BS. Le mode de jeu BrawlSet</h2>
<ul className="flex flex-col gap-4">
<li><strong>903.BS.1</strong> Le BrawlSet est une variante du Commander. Cette variante utilise les règles classiques du Commander avec les modifications suivantes.</li>
<li><strong>903.BS.2</strong> La Règle dOr du format est dutiliser des cartes provenant du même set. Pour des raisons de niveau des cartes et/ou dhistoire, certains sets sont regroupés en « BSet »</li>
<li><strong>903.BS.3</strong> La liste des BSets est disponible dans le document List_BSet_FR.pdf</li>
<li><strong>903.BS.4</strong> Une carte fait partie dun BSet si elle a é imprimée au moins une fois avec le symbole dédition dun des sets qui composent le BSet. La liste complète des symboles dédition peut être trouvée dans la section Card Set Archive du site Magic de Wizard of the Coast (<a href="https://magic.wizards.com/en/products/card-set-archive">lien</a>)</li>
<li>Exemple : <em>Cela prend en compte les cartes Timeshifted pour Time Spiral, les cartes de Brawl Decks, de Planeswalker Decks et de Theme Boosters, ainsi que les cartes de Buy a Box. Cela ne prend pas en compte les cartes des Masterpiece series (Explorer dIxalan, Expeditions de Zendikar, Inventions de Kaladesh, Invocations de Amonkhet, et Planeswalker Mythic Editions de Guilds of Ravnica, Ravnica Allegeance et War of the Spark).</em></li>
<li><strong>903.BS.5</strong> Il ny a pas de sideboard pour modifier son deck entre deux parties de BrawlSet. Cependant, comme certaines cartes permettent damener des cartes dans la partie depuis lextérieur du jeu, les joueur·euses peuvent avoir un « wishboard ». Le nombre maximum de cartes dun wishboard est 15, il ny pas de nombre minimum de cartes.</li>
<li><strong>903.BS.6</strong> Les cartes des wishboard sont hors de la partie, ainsi aucun effet de la partie ne peut permettre à un·e joueur·euse de regarder ou rechercher dans le wishboard dun·e autre joueur·euse. Un·e joueur·euse ne peut amener une carte hors de la partie dans la partie sauf si cette carte vient de son wishboard ou dune partie principale dune sous-partie. Il ny a pas de restriction sur lidentité colorée des cartes quun·e joueur·euse peut amener dans la partie depuis lextérieur du jeu, ceci est une modification de la règle 903.11.</li>
<li>Exemple : <em>Léon lance Emrakul, the Promised End pour prendre le contrôle de ladversaire ciblé durant son prochain tour et prend le contrôle du prochain tour de Dylan. Léon peut faire en sorte que Dylan lance Granted, laventure de Fae of Wishes pendant quil contrôle le tour de Dylan. Léon ne pourra pas chercher ou trouver quoi que ce soit dans le wishboard de Dylan. Parce que leffet demande de chercher dans le wishboard du joueur / de la joueuse lançant cette carte, Léon ne peut pas chercher dans son wishboard. Aucune carte ne sera amenée dans la partie.</em></li>
<li><strong>903.BS.7</strong> Un·e joueur·euse désigne comme commandant un Planeswalker légendaire ou une créature légendaire. Ceci est une modification de la règle 903.3</li>
<li><strong>903.BS.8</strong> Si un deck de BrawlSet utilise un·e compagnon·ne, le/la compagnon·ne doit respecter lidentité colorée du commandant.</li>
<li><strong>903.BS.9</strong> La règle de construction en singleton du Commander sapplique à la combinaison du deck et du wishboard. Ceci est une modification de la règle 903.5b</li>
<li><strong>903.BS.10</strong> Toutes les cartes dun deck de BrawlSet doivent faire partie du même BSet. Cette règle sapplique à la combinaison du deck et du wishboard. Cette règle sapplique aux terrains de base. Ce BSet est le BSet du deck.</li>
<li><strong>903.BS.11</strong> Une carte avec un type de terrain de base ne peut être inclue dans un deck que si elle ne produit que du mana de lidentité colorée du commandant. Si aucun terrain de base faisant partie du BSet de ce deck ne vérifie cette propriété, cette règle ne sapplique pas pour les terrains de base pour ce deck. Ceci est une modification de la règle 903.5d.</li>
<li>Exemple : <em>Paul veut construire un deck de BrawlSet War of the Spark avec Karn, the Great Creator en commandant. Comme lidentité colorée de Karn est incolore et que les Wastes nont pas é éditées dans War of the Spark, Paul peut utiliser nimporte quel terrain de base édité dans War of the Spark pour construire son deck, par exemple une Forest, huit Islands, neuf Mountains, treize Plains et quinze Swamps.</em></li>
<li><strong>903.BS.12</strong> La taille minimum dun deck de BrawlSet est 60, il ny a pas de taille maximum de deck. Ceci est une modification de la règle 903.5a.</li>
<li><strong>903.BS.13</strong> Dans une partie de BrawlSet, le nombre de point de vie de départ est 20. Ceci est une modification de la règle 119.1c</li>
<li><strong>903.BS.14</strong> Les parties de BrawlSet nutilisent pas la règle des blessures de commandant, ainsi laction décrite dans la règle 704.5v, qui fait perdre un·e joueur·euse qui a subi 21 blessures de combat ou plus dun commandant ne sapplique pas. Ceci est une modification de la règle 704.5v.</li>
<li><strong>903.BS.15</strong> Au début dun match, chaque joueur·euse doit déclarer son BSet, il ne peut pas être changé entre les parties dun même match.</li>
<li><strong>903.BS.16</strong> Le format de BrawlSet utilise la règle du commander swap. Cela signifie que nimporte quel joueur·euse peut changer de commandant entre deux parties dun match. Le nouveau commandant doit être une carte du deck, lancien commandant devient une carte normale du deck. Le commandant du deck reste secret jusquà ce quil soit révélé au début de la partie.</li>
</ul>
</div>
</div>
</>
);
}

View file

@ -1,20 +0,0 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "app/globals.css",
"baseColor": "zinc",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
}
}

View file

@ -1,141 +0,0 @@
"use client"
import * as React from "react"
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
import { cn } from "@/lib/utils"
import { buttonVariants } from "@/components/ui/button"
const AlertDialog = AlertDialogPrimitive.Root
const AlertDialogTrigger = AlertDialogPrimitive.Trigger
const AlertDialogPortal = AlertDialogPrimitive.Portal
const AlertDialogOverlay = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Overlay
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
ref={ref}
/>
))
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
const AlertDialogContent = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
>(({ className, ...props }, ref) => (
<AlertDialogPortal>
<AlertDialogOverlay />
<AlertDialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 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-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className
)}
{...props}
/>
</AlertDialogPortal>
))
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
const AlertDialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-2 text-center sm:text-left",
className
)}
{...props}
/>
)
AlertDialogHeader.displayName = "AlertDialogHeader"
const AlertDialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
AlertDialogFooter.displayName = "AlertDialogFooter"
const AlertDialogTitle = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Title
ref={ref}
className={cn("text-lg font-semibold", className)}
{...props}
/>
))
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
const AlertDialogDescription = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
AlertDialogDescription.displayName =
AlertDialogPrimitive.Description.displayName
const AlertDialogAction = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Action>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Action
ref={ref}
className={cn(buttonVariants(), className)}
{...props}
/>
))
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
const AlertDialogCancel = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Cancel
ref={ref}
className={cn(
buttonVariants({ variant: "outline" }),
"mt-2 sm:mt-0",
className
)}
{...props}
/>
))
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
export {
AlertDialog,
AlertDialogPortal,
AlertDialogOverlay,
AlertDialogTrigger,
AlertDialogContent,
AlertDialogHeader,
AlertDialogFooter,
AlertDialogTitle,
AlertDialogDescription,
AlertDialogAction,
AlertDialogCancel,
}

View file

@ -1,50 +0,0 @@
"use client"
import * as React from "react"
import * as AvatarPrimitive from "@radix-ui/react-avatar"
import { cn } from "@/lib/utils"
const Avatar = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Root
ref={ref}
className={cn(
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
className
)}
{...props}
/>
))
Avatar.displayName = AvatarPrimitive.Root.displayName
const AvatarImage = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Image>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Image
ref={ref}
className={cn("aspect-square h-full w-full", className)}
{...props}
/>
))
AvatarImage.displayName = AvatarPrimitive.Image.displayName
const AvatarFallback = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Fallback>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Fallback
ref={ref}
className={cn(
"flex h-full w-full items-center justify-center rounded-full bg-muted",
className
)}
{...props}
/>
))
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
export { Avatar, AvatarImage, AvatarFallback }

View file

@ -1,36 +0,0 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const badgeVariants = cva(
"inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
{
variants: {
variant: {
default:
"border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
secondary:
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive:
"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
outline: "text-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
)
export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) {
return (
<div className={cn(badgeVariants({ variant }), className)} {...props} />
)
}
export { Badge, badgeVariants }

View file

@ -1,57 +0,0 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default:
"bg-orange-500 text-primary-foreground shadow hover:bg-orange-400",
destructive:
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
outline:
"border border-orange-500 bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2",
sm: "h-8 rounded-md px-3 text-xs",
lg: "h-10 rounded-md px-8",
icon: "h-9 w-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants }

View file

@ -1,55 +0,0 @@
import * as React from "react"
import { cn } from "@/lib/utils"
import { MTGCard } from '@/components/ui/mtg-card'
interface carte_from_stats {
id: string,
name: string,
normal_image: string,
url: string,
sanitized_name: string,
nbr_decks: number,
total_decks: number,
percent_decks: number,
price: string,
synergy?: number
cardmarket_uri: string
}
interface CardGroupProps {
className?: string,
groupName: string,
cards: carte_from_stats[],
showPrice?: boolean,
showStats?: boolean,
showPercent?: boolean,
commanderUrl?: boolean,
id?: string,
Icon?: any
}
const CardGroup = ({ className, groupName, cards, showPrice=true, showStats=true,showPercent=true, commanderUrl=false, id, Icon}: CardGroupProps) => {
return (
<div id={id} className={cn('flex flex-col w-full items-start',className)}>
<div className="flex items-center flex-row gap-2 mb-2">
{ Icon && (
<Icon className="h-6"/>
)}
<h1 className="text-3xl">{groupName}</h1>
</div>
<div
className="flex flex-row flex-wrap gap-4"
>
{cards.length == 0 && (
<span>Pas de cartes...</span>
)}
{cards.length > 0 && cards.map((card: carte_from_stats) => (
<MTGCard key={card.id} cardname={card.name} imageURI={card.normal_image} url={commanderUrl ? "/commander/card/" + card.url : undefined} nbrDecks={card.nbr_decks} totalDecks={card.total_decks} percentDecks={showPercent ? card.percent_decks : undefined} price={card.price} synergy={card.synergy} cardmarketURI={card.cardmarket_uri}/>
))}
</div>
</div>
)}
MTGCard.displayName = "MTGCard"
export { CardGroup }

View file

@ -1,76 +0,0 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"rounded-xl border bg-card text-card-foreground shadow",
className
)}
{...props}
/>
))
Card.displayName = "Card"
const CardHeader = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex flex-col space-y-1.5 p-6", className)}
{...props}
/>
))
CardHeader.displayName = "CardHeader"
const CardTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h3
ref={ref}
className={cn("font-semibold leading-none tracking-tight", className)}
{...props}
/>
))
CardTitle.displayName = "CardTitle"
const CardDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<p
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
CardDescription.displayName = "CardDescription"
const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
))
CardContent.displayName = "CardContent"
const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex items-center p-6 pt-0", className)}
{...props}
/>
))
CardFooter.displayName = "CardFooter"
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }

View file

@ -1,29 +0,0 @@
"use client"
import * as React from "react"
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
import { cn } from "@/lib/utils"
import { CheckIcon } from "@radix-ui/react-icons"
const Checkbox = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
>(({ className, ...props }, ref) => (
<CheckboxPrimitive.Root
ref={ref}
className={cn(
"peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
className
)}
{...props}
>
<CheckboxPrimitive.Indicator
className={cn("flex items-center justify-center text-current")}
>
<CheckIcon className="h-4 w-4" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
))
Checkbox.displayName = CheckboxPrimitive.Root.displayName
export { Checkbox }

View file

@ -1,152 +0,0 @@
"use client"
import * as React from "react"
import { type DialogProps } from "@radix-ui/react-dialog"
import { Command as CommandPrimitive } from "cmdk"
import { cn } from "@/lib/utils"
import { Dialog, DialogContent } from "@/components/ui/dialog"
import { MagnifyingGlassIcon } from "@radix-ui/react-icons"
const Command = React.forwardRef<
React.ElementRef<typeof CommandPrimitive>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive>
>(({ className, ...props }, ref) => (
<CommandPrimitive
ref={ref}
className={cn(
"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
className
)}
{...props}
/>
))
Command.displayName = CommandPrimitive.displayName
const CommandDialog = ({ children, ...props }: DialogProps) => {
return (
<Dialog {...props}>
<DialogContent className="overflow-hidden p-0">
<Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
{children}
</Command>
</DialogContent>
</Dialog>
)
}
const CommandInput = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Input>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
>(({ className, ...props }, ref) => (
<div className="flex items-center border-b px-3" cmdk-input-wrapper="">
<MagnifyingGlassIcon className="mr-2 h-4 w-4 shrink-0 opacity-50" />
<CommandPrimitive.Input
ref={ref}
className={cn(
"flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props}
/>
</div>
))
CommandInput.displayName = CommandPrimitive.Input.displayName
const CommandList = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.List>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
>(({ className, ...props }, ref) => (
<CommandPrimitive.List
ref={ref}
className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
{...props}
/>
))
CommandList.displayName = CommandPrimitive.List.displayName
const CommandEmpty = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Empty>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
>((props, ref) => (
<CommandPrimitive.Empty
ref={ref}
className="py-6 text-center text-sm"
{...props}
/>
))
CommandEmpty.displayName = CommandPrimitive.Empty.displayName
const CommandGroup = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Group>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Group
ref={ref}
className={cn(
"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
className
)}
{...props}
/>
))
CommandGroup.displayName = CommandPrimitive.Group.displayName
const CommandSeparator = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Separator
ref={ref}
className={cn("-mx-1 h-px bg-border", className)}
{...props}
/>
))
CommandSeparator.displayName = CommandPrimitive.Separator.displayName
const CommandItem = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
className
)}
{...props}
/>
))
CommandItem.displayName = CommandPrimitive.Item.displayName
const CommandShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn(
"ml-auto text-xs tracking-widest text-muted-foreground",
className
)}
{...props}
/>
)
}
CommandShortcut.displayName = "CommandShortcut"
export {
Command,
CommandDialog,
CommandInput,
CommandList,
CommandEmpty,
CommandGroup,
CommandItem,
CommandShortcut,
CommandSeparator,
}

View file

@ -1,61 +0,0 @@
"use client"
import * as React from "react"
import * as AccordionPrimitive from "@radix-ui/react-accordion"
import { cn } from "@/lib/utils"
import { ChevronDownIcon, Pencil1Icon, TrashIcon } from "@radix-ui/react-icons"
import { Fn } from "@prisma/client/runtime/library"
const Accordion = AccordionPrimitive.Root
const AccordionItem = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
>(({ className, ...props }, ref) => (
<AccordionPrimitive.Item
ref={ref}
className={cn("py-4 border-b", className)}
{...props}
/>
))
AccordionItem.displayName = "AccordionItem"
interface AccordionFunctionProps extends AccordionPrimitive.AccordionTriggerProps {
trashFunction: any,
editFunction: any,
}
const AccordionTrigger = (({ className, children, trashFunction, editFunction, ...props }: AccordionFunctionProps) => (
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger
className={cn(
"flex flex-1 items-center justify-between text-sm font-medium transition-all text-left [&[data-state=open]>div>#chevronDown]:rotate-180",
className
)}
{...props}
>
{children}
<div className="flex flex-row gap-4 shrink-0">
<Pencil1Icon onClick={editFunction} className="h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200" />
<TrashIcon onClick={trashFunction} className="h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200" />
<ChevronDownIcon id="chevronDown" className="h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200" />
</div>
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
))
const AccordionContent = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Content
ref={ref}
className="overflow-hidden text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
{...props}
>
<div className={cn("pb-4 pt-0", className)}>{children}</div>
</AccordionPrimitive.Content>
))
AccordionContent.displayName = AccordionPrimitive.Content.displayName
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }

View file

@ -1,121 +0,0 @@
"use client"
import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { cn } from "@/lib/utils"
import { Cross2Icon } from "@radix-ui/react-icons"
const Dialog = DialogPrimitive.Root
const DialogTrigger = DialogPrimitive.Trigger
const DialogPortal = DialogPrimitive.Portal
const DialogClose = DialogPrimitive.Close
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
/>
))
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 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-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className
)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<Cross2Icon className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
))
DialogContent.displayName = DialogPrimitive.Content.displayName
const DialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-1.5 text-center sm:text-left",
className
)}
{...props}
/>
)
DialogHeader.displayName = "DialogHeader"
const DialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
DialogFooter.displayName = "DialogFooter"
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
))
DialogTitle.displayName = DialogPrimitive.Title.displayName
const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
DialogDescription.displayName = DialogPrimitive.Description.displayName
export {
Dialog,
DialogPortal,
DialogOverlay,
DialogTrigger,
DialogClose,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
}

View file

@ -1,200 +0,0 @@
"use client"
import * as React from "react"
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
import { cn } from "@/lib/utils"
import { CheckIcon, ChevronRightIcon, DotFilledIcon } from "@radix-ui/react-icons"
const DropdownMenu = DropdownMenuPrimitive.Root
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
const DropdownMenuGroup = DropdownMenuPrimitive.Group
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
const DropdownMenuSub = DropdownMenuPrimitive.Sub
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
const DropdownMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean
}
>(({ className, inset, children, ...props }, ref) => (
<DropdownMenuPrimitive.SubTrigger
ref={ref}
className={cn(
"flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
inset && "pl-8",
className
)}
{...props}
>
{children}
<ChevronRightIcon className="ml-auto" />
</DropdownMenuPrimitive.SubTrigger>
))
DropdownMenuSubTrigger.displayName =
DropdownMenuPrimitive.SubTrigger.displayName
const DropdownMenuSubContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.SubContent
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg 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}
/>
))
DropdownMenuSubContent.displayName =
DropdownMenuPrimitive.SubContent.displayName
const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
"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}
/>
</DropdownMenuPrimitive.Portal>
))
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
const DropdownMenuItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0",
inset && "pl-8",
className
)}
{...props}
/>
))
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
const DropdownMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<CheckIcon className="h-4 w-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
))
DropdownMenuCheckboxItem.displayName =
DropdownMenuPrimitive.CheckboxItem.displayName
const DropdownMenuRadioItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<DropdownMenuPrimitive.RadioItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<DotFilledIcon className="h-2 w-2 fill-current" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
))
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
const DropdownMenuLabel = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label
ref={ref}
className={cn(
"px-2 py-1.5 text-sm font-semibold",
inset && "pl-8",
className
)}
{...props}
/>
))
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
const DropdownMenuSeparator = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
))
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
const DropdownMenuShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
{...props}
/>
)
}
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
export {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
}

View file

@ -1,25 +0,0 @@
import * as React from "react"
import { cn } from "@/lib/utils"
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-auto min-h-9 w-full border-l-orange-500 border-l-4 bg-transparent px-3 py-1 text-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50",
className
)}
ref={ref}
{...props}
/>
)
}
)
Input.displayName = "Input"
export { Input }

View file

@ -1,26 +0,0 @@
"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

@ -1,97 +0,0 @@
import { cn } from "@/lib/utils"
interface manaIcon {
className: string
}
const Black = ({ className, ...props }: manaIcon) => {
return (
<img
src="https://svgs.scryfall.io/card-symbols/B.svg"
loading="lazy"
className={cn(
"",
className
)}
{...props}
/>
)
}
Black.displayName = "Black"
const Blue = ({ className, ...props }: manaIcon) => {
return (
<img
src="https://svgs.scryfall.io/card-symbols/U.svg"
loading="lazy"
className={cn(
"",
className
)}
{...props}
/>
)
}
Blue.displayName = "Blue"
const Green = ({ className, ...props }: manaIcon) => {
return (
<img
src="https://svgs.scryfall.io/card-symbols/G.svg"
loading="lazy"
className={cn(
"",
className
)}
{...props}
/>
)
}
Green.displayName = "Green"
const White = ({ className, ...props }: manaIcon) => {
return (
<img
src="https://svgs.scryfall.io/card-symbols/W.svg"
loading="lazy"
className={cn(
"",
className
)}
{...props}
/>
)
}
White.displayName = "White"
const Red = ({ className, ...props }: manaIcon) => {
return (
<img
src="https://svgs.scryfall.io/card-symbols/R.svg"
loading="lazy"
className={cn(
"",
className
)}
{...props}
/>
)
}
Red.displayName = "Red"
const Colorless = ({ className, ...props }: manaIcon) => {
return (
<img
src="https://svgs.scryfall.io/card-symbols/C.svg"
loading="lazy"
className={cn(
"",
className
)}
{...props}
/>
)
}
Colorless.displayName = "Colorless"
export { Black, Blue, Green, White, Red, Colorless }

View file

@ -1,36 +0,0 @@
import * as React from "react"
import { cn } from "@/lib/utils"
import type { carte } from '@prisma/client'
import { Spinner } from "./spinner"
interface carteWithProps {
carte: carte
className?: string
}
const MTGCardTextHover = ({ carte, className }: carteWithProps) => {
const [loaded, setLoaded] = React.useState(false)
return (
<div
className={cn(
"flex flex-col group cursor-pointer",
className
)}
>
<span className="w-fit border-transparent border-dotted border-b-stone-500 border-b-2">{carte.name}</span>
<div className="MTGCardTooltip absolute hidden group-hover:block group-active:block">
{!loaded &&
<div className="mt-8 flex flex-col items-center justify-center h-96 w-64 bg-stone-500 rounded-md">
<Spinner />
</div>
}
<img src={carte.normal_image} className={ loaded ? "rounded-md mt-8 h-96" : "absolute opacity-0 h-96" } onLoad={() => {setLoaded(true)}} loading="lazy" />
</div>
</div>
)}
MTGCardTextHover.displayName = "MTGCardTextHover"
export { MTGCardTextHover }

View file

@ -1,53 +0,0 @@
import * as React from "react"
import { cn } from "@/lib/utils"
import { Spinner } from "./spinner"
interface MTGCardProps {
className?: string,
imageURI: string,
cardname: string,
url?: string,
nbrDecks?: number,
totalDecks?: number,
percentDecks?: number,
synergy?: number,
price?: string,
cardmarketURI?: string
}
const MTGCard = ({ className, imageURI, cardname, url, nbrDecks, totalDecks, percentDecks, price, synergy, cardmarketURI }: MTGCardProps) => {
const [loaded, setLoaded] = React.useState(false)
return (
<a
className={cn(
"flex flex-col w-48",
className
)}
href={url != undefined ? url : "#"}
>
{!loaded &&
<div className="flex flex-col items-center justify-center h-64 bg-stone-500 rounded-md">
<Spinner />
</div>
}
<img src={imageURI} className={ cn(loaded ? "rounded h-64" : "absolute opacity-0", className) } onLoad={() => {setLoaded(true)}} loading="lazy" />
<div className="flex flex-col items-center gap-0">
{ price != undefined && (
<a className="text-xs" href={cardmarketURI != undefined ? cardmarketURI : "#"} target={cardmarketURI != undefined ? "_blank" : "_self"}>{price}</a>
)}
<span className="text-center text-xs">{cardname}</span>
{ nbrDecks != undefined && (
<>
<span className="text-md">{nbrDecks} Deck{nbrDecks > 1 ? "s" : ""}
{ percentDecks != undefined && ( <span> sur {totalDecks} ({percentDecks}%)</span>)}</span>
</>
)}
{ synergy != undefined && ( <span className="text-md">{synergy}% Synergie</span> )}
</div>
</a>
)}
MTGCard.displayName = "MTGCard"
export { MTGCard, type MTGCardProps }

View file

@ -1,453 +0,0 @@
'use client'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuPortal,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { Input } from "@/components/ui/input"
import { Button } from "@/components/ui/button"
import { IconUserFilled } from "@tabler/icons-react"
import { IconChevronDown } from "@tabler/icons-react"
import { Black, Blue, Green, White, Red, Colorless } from "@/components/ui/mana-icons"
import { useEffect, useState } from 'react'
interface NavigationProps {
isLoggedIn: boolean,
username: string
}
interface bsetJsonObject {
name: string,
sanitized_name: string,
set_codes: string[],
icons: string[]
}
export function NavigationBar ({ isLoggedIn, username}: NavigationProps) {
const [bsetsList, setBsetsList] = useState<bsetJsonObject[]>([])
useEffect(() => {
fetch('/api/json/misc/bsets.json').then((res) => {
if(res.status == 200) {
res.json().then((data) => {
setBsetsList(data)
console.log(data)
})
}
})
}, [])
return (
<div className="flex z-50 flex-row p-4 gap-4 w-full bg-white fixed top-0 left-0 items-center justify-between">
<div className="flex flex-row gap-4 items-center">
<a className="flex flex-row gap-2 items-center" href="/">
<img src="/assets/logo.png" className="h-8" />
<span className="font-beleren text-3xl mt-2 bg-gradient-to-r from-black to-orange-500 bg-clip-text text-transparent">BrawlSet</span>
</a>
</div>
<div className="flex flex-row gap-4 items-center">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<span className="text-stone-500 cursor-pointer flex flex-row gap-1 items-center">Commandants <IconChevronDown className="h-4"/></span>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56">
<DropdownMenuGroup>
<DropdownMenuItem>
<a href="/commander/top">Top commandants</a>
</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<span>Mono</span>
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
<DropdownMenuItem>
<a className="flex flex-row gap-2 items-center" href="/commander/white">
<White className="h-4 w-4"/>
<span>White</span>
</a>
</DropdownMenuItem>
<DropdownMenuItem>
<a className="flex flex-row gap-2 items-center" href="/commander/blue">
<Blue className="h-4 w-4"/>
<span>Bleu</span>
</a>
</DropdownMenuItem>
<DropdownMenuItem>
<a className="flex flex-row gap-2 items-center" href="/commander/black">
<Black className="h-4 w-4"/>
<span>Noir</span>
</a>
</DropdownMenuItem>
<DropdownMenuItem>
<a className="flex flex-row gap-2 items-center" href="/commander/red">
<Red className="h-4 w-4"/>
<span>Rouge</span>
</a>
</DropdownMenuItem>
<DropdownMenuItem>
<a className="flex flex-row gap-2 items-center" href="/commander/green">
<Green className="h-4 w-4"/>
<span>Vert</span>
</a>
</DropdownMenuItem>
<DropdownMenuItem>
<a className="flex flex-row gap-2 items-center" href="/commander/colorless">
<Colorless className="h-4 w-4"/>
<span>Incolor</span>
</a>
</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<span>2 couleurs</span>
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
<DropdownMenuItem>
<a className="flex flex-row gap-2 items-center" href="/commander/azorius">
<div className="flex flex-row">
<White className="h-4 w-4"/>
<Blue className="h-4 w-4"/>
</div>
<span>Azorius</span>
</a>
</DropdownMenuItem>
<DropdownMenuItem>
<a className="flex flex-row gap-2 items-center" href="/commander/dimir">
<div className="flex flex-row">
<Blue className="h-4 w-4"/>
<Black className="h-4 w-4"/>
</div>
<span>Dimir</span>
</a>
</DropdownMenuItem>
<DropdownMenuItem>
<a className="flex flex-row gap-2 items-center" href="/commander/rakdos">
<div className="flex flex-row">
<Black className="h-4 w-4"/>
<Red className="h-4 w-4"/>
</div>
<span>Rakdos</span>
</a>
</DropdownMenuItem>
<DropdownMenuItem>
<a className="flex flex-row gap-2 items-center" href="/commander/gruul">
<div className="flex flex-row">
<Red className="h-4 w-4"/>
<Green className="h-4 w-4"/>
</div>
<span>Gruul</span>
</a>
</DropdownMenuItem>
<DropdownMenuItem>
<a className="flex flex-row gap-2 items-center" href="/commander/selesnya">
<div className="flex flex-row">
<Green className="h-4 w-4"/>
<White className="h-4 w-4"/>
</div>
<span>Selesnya</span>
</a>
</DropdownMenuItem>
<DropdownMenuItem>
<a className="flex flex-row gap-2 items-center" href="/commander/orzhov">
<div className="flex flex-row">
<White className="h-4 w-4"/>
<Black className="h-4 w-4"/>
</div>
<span>Orzhov</span>
</a>
</DropdownMenuItem>
<DropdownMenuItem>
<a className="flex flex-row gap-2 items-center" href="/commander/izzet">
<div className="flex flex-row">
<Blue className="h-4 w-4"/>
<Red className="h-4 w-4"/>
</div>
<span>Izzet</span>
</a>
</DropdownMenuItem>
<DropdownMenuItem>
<a className="flex flex-row gap-2 items-center" href="/commander/golgari">
<div className="flex flex-row">
<Black className="h-4 w-4"/>
<Green className="h-4 w-4"/>
</div>
<span>Golgari</span>
</a>
</DropdownMenuItem>
<DropdownMenuItem>
<a className="flex flex-row gap-2 items-center" href="/commander/boros">
<div className="flex flex-row">
<Red className="h-4 w-4"/>
<White className="h-4 w-4"/>
</div>
<span>Boros</span>
</a>
</DropdownMenuItem>
<DropdownMenuItem>
<a className="flex flex-row gap-2 items-center" href="/commander/simic">
<div className="flex flex-row">
<Green className="h-4 w-4"/>
<Blue className="h-4 w-4"/>
</div>
<span>Simic</span>
</a>
</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<span>3 couleurs</span>
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
<DropdownMenuItem>
<a className="flex flex-row gap-2 items-center" href="/commander/esper">
<div className="flex flex-row">
<White className="h-4 w-4"/>
<Blue className="h-4 w-4"/>
<Black className="h-4 w-4"/>
</div>
<span>Esper</span>
</a>
</DropdownMenuItem>
<DropdownMenuItem>
<a className="flex flex-row gap-2 items-center" href="/commander/grixis">
<div className="flex flex-row">
<Blue className="h-4 w-4"/>
<Black className="h-4 w-4"/>
<Red className="h-4 w-4"/>
</div>
<span>Grixis</span>
</a>
</DropdownMenuItem>
<DropdownMenuItem>
<a className="flex flex-row gap-2 items-center" href="/commander/jund">
<div className="flex flex-row">
<Black className="h-4 w-4"/>
<Red className="h-4 w-4"/>
<Green className="h-4 w-4"/>
</div>
<span>Jund</span>
</a>
</DropdownMenuItem>
<DropdownMenuItem>
<a className="flex flex-row gap-2 items-center" href="/commander/naya">
<div className="flex flex-row">
<Red className="h-4 w-4"/>
<Green className="h-4 w-4"/>
<White className="h-4 w-4"/>
</div>
<span>Naya</span>
</a>
</DropdownMenuItem>
<DropdownMenuItem>
<a className="flex flex-row gap-2 items-center" href="/commander/bant">
<div className="flex flex-row">
<Green className="h-4 w-4"/>
<White className="h-4 w-4"/>
<Blue className="h-4 w-4"/>
</div>
<span>Bant</span>
</a>
</DropdownMenuItem>
<DropdownMenuItem>
<a className="flex flex-row gap-2 items-center" href="/commander/abzan">
<div className="flex flex-row">
<White className="h-4 w-4"/>
<Black className="h-4 w-4"/>
<Green className="h-4 w-4"/>
</div>
<span>Abzan</span>
</a>
</DropdownMenuItem>
<DropdownMenuItem>
<a className="flex flex-row gap-2 items-center" href="/commander/jeskai">
<div className="flex flex-row">
<Blue className="h-4 w-4"/>
<Red className="h-4 w-4"/>
<White className="h-4 w-4"/>
</div>
<span>Jeskai</span>
</a>
</DropdownMenuItem>
<DropdownMenuItem>
<a className="flex flex-row gap-2 items-center" href="/commander/sultai">
<div className="flex flex-row">
<Black className="h-4 w-4"/>
<Green className="h-4 w-4"/>
<Blue className="h-4 w-4"/>
</div>
<span>Sultai</span>
</a>
</DropdownMenuItem>
<DropdownMenuItem>
<a className="flex flex-row gap-2 items-center" href="/commander/mardu">
<div className="flex flex-row">
<Red className="h-4 w-4"/>
<White className="h-4 w-4"/>
<Black className="h-4 w-4"/>
</div>
<span>Mardu</span>
</a>
</DropdownMenuItem>
<DropdownMenuItem>
<a className="flex flex-row gap-2 items-center" href="/commander/temur">
<div className="flex flex-row">
<Green className="h-4 w-4"/>
<Blue className="h-4 w-4"/>
<Red className="h-4 w-4"/>
</div>
<span>Temur</span>
</a>
</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<span>4+ couleurs</span>
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
<DropdownMenuItem>
<a className="flex flex-row gap-2 items-center" href="/commander/yore-tiller">
<div className="flex flex-row">
<White className="h-4 w-4"/>
<Blue className="h-4 w-4"/>
<Black className="h-4 w-4"/>
<Red className="h-4 w-4"/>
</div>
<span>Yore-Tiller</span>
</a>
</DropdownMenuItem>
<DropdownMenuItem>
<a className="flex flex-row gap-2 items-center" href="/commander/glint-eye">
<div className="flex flex-row">
<Blue className="h-4 w-4"/>
<Black className="h-4 w-4"/>
<Red className="h-4 w-4"/>
<Green className="h-4 w-4"/>
</div>
<span>Glint-Eye</span>
</a>
</DropdownMenuItem>
<DropdownMenuItem>
<a className="flex flex-row gap-2 items-center" href="/commander/dune-brood">
<div className="flex flex-row">
<Black className="h-4 w-4"/>
<Red className="h-4 w-4"/>
<Green className="h-4 w-4"/>
<White className="h-4 w-4"/>
</div>
<span>Dune-Brood</span>
</a>
</DropdownMenuItem>
<DropdownMenuItem>
<a className="flex flex-row gap-2 items-center" href="/commander/ink-treader">
<div className="flex flex-row">
<Red className="h-4 w-4"/>
<Green className="h-4 w-4"/>
<White className="h-4 w-4"/>
<Blue className="h-4 w-4"/>
</div>
<span>Ink-Treader</span>
</a>
</DropdownMenuItem>
<DropdownMenuItem>
<a className="flex flex-row gap-2 items-center" href="/commander/witch-maw">
<div className="flex flex-row">
<Green className="h-4 w-4"/>
<White className="h-4 w-4"/>
<Blue className="h-4 w-4"/>
<Black className="h-4 w-4"/>
</div>
<span>Witch-Maw</span>
</a>
</DropdownMenuItem>
<DropdownMenuItem>
<a className="flex flex-row gap-2 items-center" href="/commander/five-color">
<div className="flex flex-row">
<White className="h-4 w-4"/>
<Blue className="h-4 w-4"/>
<Black className="h-4 w-4"/>
<Red className="h-4 w-4"/>
<Green className="h-4 w-4"/>
</div>
<span>5 couleurs</span>
</a>
</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<span className="text-stone-500 cursor-pointer flex flex-row gap-1 items-center">BSets <IconChevronDown className="h-4"/></span>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56">
<DropdownMenuGroup>
{ bsetsList.length == 0 && (
<DropdownMenuItem>
<span>Loading</span>
</DropdownMenuItem>
)}
{ bsetsList.length != 0 && bsetsList.slice(0,7).map((bset) => (
<DropdownMenuItem>
<a href={"/bset/" + bset.sanitized_name} className="flex flex-row gap-2 items-center">
<div className="flex flex-row gap-1">
{ bset.icons.map((icon) => (
<img src={icon} className="w-4 h-4"/>
))}
</div>
<span>{bset.name}</span>
</a>
</DropdownMenuItem>
))}
<DropdownMenuItem>
<a href="/bset/all">Plus de BSets...</a>
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
<a href="/rules" className="text-stone-500">Règles</a>
<a href="/faq" className="text-stone-500">FAQ</a>
</div>
<div className="flex flex-row gap-4">
{ !isLoggedIn &&
<>
<a href="/account/signin" className="text-stone-500">Connexion</a>
<a href="#" className="text-stone-500 cursor-not-allowed">Inscription</a>
</>
}
{
isLoggedIn &&
<>
<a href="/account/profile/decks" className="flex flex-row items-center gap-2 text-stone-500">Decks</a>
<a href="/account/profile" className="flex flex-row items-center gap-2">
<span className="text-stone-500">{username}</span>
</a>
</>
}
</div>
</div>
)
}

View file

@ -1,128 +0,0 @@
import * as React from "react"
import { ChevronDownIcon } from "@radix-ui/react-icons"
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
import { cva } from "class-variance-authority"
import { cn } from "@/lib/utils"
const NavigationMenu = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>
>(({ className, children, ...props }, ref) => (
<NavigationMenuPrimitive.Root
ref={ref}
className={cn(
"relative z-10 flex max-w-max flex-1 items-center justify-center",
className
)}
{...props}
>
{children}
<NavigationMenuViewport />
</NavigationMenuPrimitive.Root>
))
NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName
const NavigationMenuList = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.List>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>
>(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.List
ref={ref}
className={cn(
"group flex flex-1 list-none items-center justify-center space-x-1",
className
)}
{...props}
/>
))
NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName
const NavigationMenuItem = NavigationMenuPrimitive.Item
const navigationMenuTriggerStyle = cva(
"group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50"
)
const NavigationMenuTrigger = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<NavigationMenuPrimitive.Trigger
ref={ref}
className={cn(navigationMenuTriggerStyle(), "group", className)}
{...props}
>
{children}{" "}
<ChevronDownIcon
className="relative top-[1px] ml-1 h-3 w-3 transition duration-300 group-data-[state=open]:rotate-180"
aria-hidden="true"
/>
</NavigationMenuPrimitive.Trigger>
))
NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName
const NavigationMenuContent = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>
>(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.Content
ref={ref}
className={cn(
"left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto ",
className
)}
{...props}
/>
))
NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName
const NavigationMenuLink = NavigationMenuPrimitive.Link
const NavigationMenuViewport = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
>(({ className, ...props }, ref) => (
<div className={cn("absolute left-0 top-full flex justify-center")}>
<NavigationMenuPrimitive.Viewport
className={cn(
"origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]",
className
)}
ref={ref}
{...props}
/>
</div>
))
NavigationMenuViewport.displayName =
NavigationMenuPrimitive.Viewport.displayName
const NavigationMenuIndicator = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>
>(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.Indicator
ref={ref}
className={cn(
"top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in",
className
)}
{...props}
>
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
</NavigationMenuPrimitive.Indicator>
))
NavigationMenuIndicator.displayName =
NavigationMenuPrimitive.Indicator.displayName
export {
navigationMenuTriggerStyle,
NavigationMenu,
NavigationMenuList,
NavigationMenuItem,
NavigationMenuContent,
NavigationMenuTrigger,
NavigationMenuLink,
NavigationMenuIndicator,
NavigationMenuViewport,
}

View file

@ -1,33 +0,0 @@
"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

@ -1,15 +0,0 @@
import * as React from "react"
import { cn } from "@/lib/utils"
interface SpinnerProps {
className?: string,
}
const Spinner = ({ className }: SpinnerProps) => {
return (
<div className={cn("inline-block w-12 h-12 border-4 border-orange-200 border-t-orange-500 rounded-full animate-spin", className)}></div>
)}
Spinner.displayName = "Spinner"
export { Spinner }

View file

@ -1,55 +0,0 @@
import { cn } from "@/lib/utils"
interface SymbolsIconProps {
className: string
}
const CommanderIcon = ({ className }: SymbolsIconProps) => {
return (
<img className={cn("h-4",className)} src="/assets/commander.svg"></img>
)}
CommanderIcon.displayName = "CommanderIcon"
const PlaneswalkerIcon = ({ className }: SymbolsIconProps) => {
return (
<img className={cn("h-4",className)} src="/assets/planeswalker.svg"></img>
)}
PlaneswalkerIcon.displayName = "PlaneswalkerIcon"
const SorceryIcon = ({ className }: SymbolsIconProps) => {
return (
<img className={cn("h-4",className)} src="/assets/sorcery.svg"></img>
)}
SorceryIcon.displayName = "SorceryIcon"
const InstantIcon = ({ className }: SymbolsIconProps) => {
return (
<img className={cn("h-4",className)} src="/assets/instant.svg"></img>
)}
InstantIcon.displayName = "InstantIcon"
const CreatureIcon = ({ className }: SymbolsIconProps) => {
return (
<img className={cn("h-4",className)} src="/assets/creature.svg"></img>
)}
CreatureIcon.displayName = "CreatureIcon"
const ArtifactIcon = ({ className }: SymbolsIconProps) => {
return (
<img className={cn("h-4",className)} src="/assets/artifact.svg"></img>
)}
ArtifactIcon.displayName = "ArtifactIcon"
const EnchantmentIcon = ({ className }: SymbolsIconProps) => {
return (
<img className={cn("h-4",className)} src="/assets/enchantment.svg"></img>
)}
EnchantmentIcon.displayName = "EnchantmentIcon"
const LandIcon = ({ className }: SymbolsIconProps) => {
return (
<img className={cn("h-4",className)} src="/assets/land.svg"></img>
)}
LandIcon.displayName = "LandIcon"
export { CommanderIcon, PlaneswalkerIcon, SorceryIcon, InstantIcon, CreatureIcon, ArtifactIcon, EnchantmentIcon, LandIcon }

View file

@ -1,120 +0,0 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Table = React.forwardRef<
HTMLTableElement,
React.HTMLAttributes<HTMLTableElement>
>(({ className, ...props }, ref) => (
<div className="relative w-full overflow-auto">
<table
ref={ref}
className={cn("w-full caption-bottom text-sm", className)}
{...props}
/>
</div>
))
Table.displayName = "Table"
const TableHeader = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
))
TableHeader.displayName = "TableHeader"
const TableBody = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tbody
ref={ref}
className={cn("[&_tr:last-child]:border-0", className)}
{...props}
/>
))
TableBody.displayName = "TableBody"
const TableFooter = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tfoot
ref={ref}
className={cn(
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
className
)}
{...props}
/>
))
TableFooter.displayName = "TableFooter"
const TableRow = React.forwardRef<
HTMLTableRowElement,
React.HTMLAttributes<HTMLTableRowElement>
>(({ className, ...props }, ref) => (
<tr
ref={ref}
className={cn(
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
className
)}
{...props}
/>
))
TableRow.displayName = "TableRow"
const TableHead = React.forwardRef<
HTMLTableCellElement,
React.ThHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<th
ref={ref}
className={cn(
"h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className
)}
{...props}
/>
))
TableHead.displayName = "TableHead"
const TableCell = React.forwardRef<
HTMLTableCellElement,
React.TdHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<td
ref={ref}
className={cn(
"p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className
)}
{...props}
/>
))
TableCell.displayName = "TableCell"
const TableCaption = React.forwardRef<
HTMLTableCaptionElement,
React.HTMLAttributes<HTMLTableCaptionElement>
>(({ className, ...props }, ref) => (
<caption
ref={ref}
className={cn("mt-4 text-sm text-muted-foreground", className)}
{...props}
/>
))
TableCaption.displayName = "TableCaption"
export {
Table,
TableHeader,
TableBody,
TableFooter,
TableHead,
TableRow,
TableCell,
TableCaption,
}

View file

@ -1,22 +0,0 @@
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 }

View file

@ -1,128 +0,0 @@
"use client"
import * as React from "react"
import * as ToastPrimitives from "@radix-ui/react-toast"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
import { Cross2Icon } from "@radix-ui/react-icons"
const ToastProvider = ToastPrimitives.Provider
const ToastViewport = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Viewport>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Viewport
ref={ref}
className={cn(
"fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
className
)}
{...props}
/>
))
ToastViewport.displayName = ToastPrimitives.Viewport.displayName
const toastVariants = cva(
"group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
{
variants: {
variant: {
default: "border bg-background text-foreground",
destructive:
"destructive group border-destructive bg-destructive text-destructive-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
)
const Toast = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
VariantProps<typeof toastVariants>
>(({ className, variant, ...props }, ref) => {
return (
<ToastPrimitives.Root
ref={ref}
className={cn(toastVariants({ variant }), className)}
{...props}
/>
)
})
Toast.displayName = ToastPrimitives.Root.displayName
const ToastAction = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Action>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Action
ref={ref}
className={cn(
"inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium transition-colors hover:bg-secondary focus:outline-none focus:ring-1 focus:ring-ring disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
className
)}
{...props}
/>
))
ToastAction.displayName = ToastPrimitives.Action.displayName
const ToastClose = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Close>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Close
ref={ref}
className={cn(
"absolute right-1 top-1 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-1 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",
className
)}
toast-close=""
{...props}
>
<Cross2Icon className="h-4 w-4" />
</ToastPrimitives.Close>
))
ToastClose.displayName = ToastPrimitives.Close.displayName
const ToastTitle = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Title>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Title
ref={ref}
className={cn("text-sm font-semibold [&+div]:text-xs", className)}
{...props}
/>
))
ToastTitle.displayName = ToastPrimitives.Title.displayName
const ToastDescription = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Description>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Description
ref={ref}
className={cn("text-sm opacity-90", className)}
{...props}
/>
))
ToastDescription.displayName = ToastPrimitives.Description.displayName
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>
type ToastActionElement = React.ReactElement<typeof ToastAction>
export {
type ToastProps,
type ToastActionElement,
ToastProvider,
ToastViewport,
Toast,
ToastTitle,
ToastDescription,
ToastClose,
ToastAction,
}

View file

@ -1,35 +0,0 @@
"use client"
import { useToast } from "@/hooks/use-toast"
import {
Toast,
ToastClose,
ToastDescription,
ToastProvider,
ToastTitle,
ToastViewport,
} from "@/components/ui/toast"
export function Toaster() {
const { toasts } = useToast()
return (
<ToastProvider>
{toasts.map(function ({ id, title, description, action, ...props }) {
return (
<Toast key={id} {...props}>
<div className="grid gap-1">
{title && <ToastTitle>{title}</ToastTitle>}
{description && (
<ToastDescription>{description}</ToastDescription>
)}
</div>
{action}
<ToastClose />
</Toast>
)
})}
<ToastViewport />
</ToastProvider>
)
}

View file

@ -1,194 +0,0 @@
"use client"
// Inspired by react-hot-toast library
import * as React from "react"
import type {
ToastActionElement,
ToastProps,
} from "@/components/ui/toast"
const TOAST_LIMIT = 3
const TOAST_REMOVE_DELAY = 1000000
type ToasterToast = ToastProps & {
id: string
title?: React.ReactNode
description?: React.ReactNode
action?: ToastActionElement
}
const actionTypes = {
ADD_TOAST: "ADD_TOAST",
UPDATE_TOAST: "UPDATE_TOAST",
DISMISS_TOAST: "DISMISS_TOAST",
REMOVE_TOAST: "REMOVE_TOAST",
} as const
let count = 0
function genId() {
count = (count + 1) % Number.MAX_SAFE_INTEGER
return count.toString()
}
type ActionType = typeof actionTypes
type Action =
| {
type: ActionType["ADD_TOAST"]
toast: ToasterToast
}
| {
type: ActionType["UPDATE_TOAST"]
toast: Partial<ToasterToast>
}
| {
type: ActionType["DISMISS_TOAST"]
toastId?: ToasterToast["id"]
}
| {
type: ActionType["REMOVE_TOAST"]
toastId?: ToasterToast["id"]
}
interface State {
toasts: ToasterToast[]
}
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
const addToRemoveQueue = (toastId: string) => {
if (toastTimeouts.has(toastId)) {
return
}
const timeout = setTimeout(() => {
toastTimeouts.delete(toastId)
dispatch({
type: "REMOVE_TOAST",
toastId: toastId,
})
}, TOAST_REMOVE_DELAY)
toastTimeouts.set(toastId, timeout)
}
export const reducer = (state: State, action: Action): State => {
switch (action.type) {
case "ADD_TOAST":
return {
...state,
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
}
case "UPDATE_TOAST":
return {
...state,
toasts: state.toasts.map((t) =>
t.id === action.toast.id ? { ...t, ...action.toast } : t
),
}
case "DISMISS_TOAST": {
const { toastId } = action
// ! Side effects ! - This could be extracted into a dismissToast() action,
// but I'll keep it here for simplicity
if (toastId) {
addToRemoveQueue(toastId)
} else {
state.toasts.forEach((toast) => {
addToRemoveQueue(toast.id)
})
}
return {
...state,
toasts: state.toasts.map((t) =>
t.id === toastId || toastId === undefined
? {
...t,
open: false,
}
: t
),
}
}
case "REMOVE_TOAST":
if (action.toastId === undefined) {
return {
...state,
toasts: [],
}
}
return {
...state,
toasts: state.toasts.filter((t) => t.id !== action.toastId),
}
}
}
const listeners: Array<(state: State) => void> = []
let memoryState: State = { toasts: [] }
function dispatch(action: Action) {
memoryState = reducer(memoryState, action)
listeners.forEach((listener) => {
listener(memoryState)
})
}
type Toast = Omit<ToasterToast, "id">
function toast({ ...props }: Toast) {
const id = genId()
const update = (props: ToasterToast) =>
dispatch({
type: "UPDATE_TOAST",
toast: { ...props, id },
})
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
dispatch({
type: "ADD_TOAST",
toast: {
...props,
id,
open: true,
onOpenChange: (open) => {
if (!open) dismiss()
},
},
})
return {
id: id,
dismiss,
update,
}
}
function useToast() {
const [state, setState] = React.useState<State>(memoryState)
React.useEffect(() => {
listeners.push(setState)
return () => {
const index = listeners.indexOf(setState)
if (index > -1) {
listeners.splice(index, 1)
}
}
}, [state])
return {
...state,
toast,
dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
}
}
export { useToast, toast }

View file

@ -1,13 +0,0 @@
import { PrismaClient } from '@prisma/client'
const prismaClientSingleton = () => {
return new PrismaClient()
}
declare global { }
declare const globalThis: {
prismaGlobal: ReturnType<typeof prismaClientSingleton>;
} & typeof global;
export const db = globalThis.prismaGlobal ?? prismaClientSingleton() // Creating a new instance of PrismaClient.

View file

@ -1,37 +0,0 @@
import { Buffer } from "buffer"
import { createHmac } from "crypto"
const decode = (str: string):string => Buffer.from(str, 'base64').toString('binary');
const encode = (str: string):string => Buffer.from(str, 'binary').toString('base64');
const secret = process.env.JWT_SECRET ? process.env.JWT_SECRET : ""
interface tokenData {
// eslint-disable-next-line
data: any,
maxAge: number
}
export function createToken({data, maxAge}: tokenData) {
const tokenData = data
tokenData.maxAge = maxAge
const header = encode(JSON.stringify({"alg":"HS256", "typ":"jwt"}))
const body = encode(JSON.stringify(tokenData))
const signature = createHmac('sha256',secret).update(header + "." + body).digest('hex')
return header + "." + body + "." + signature
}
export function validateToken(token: string) {
const rawData = token.split(".")
const header = rawData[0]
const body = rawData[1]
const signature = rawData[2]
const calculatedSignature = createHmac('sha256',secret).update(header + "." + body).digest('hex')
return signature == calculatedSignature
}
export function decryptToken(token: string) {
const rawData = token.split(".")
const body = rawData[1]
return JSON.parse(decode(body))
}

View file

@ -1,4 +0,0 @@
export function logging(message: string) {
const date = new Date()
console.log("[" + date.getFullYear() + "/" + (date.getMonth()+1).toString().padStart(2,'0') + "/" + date.getDate().toString().padStart(2,'0') + " - " + date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds() +"] " + message)
}

View file

@ -1,33 +0,0 @@
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
export function setCookie(cname: string, cvalue:string, exdays: number) {
if (typeof window !== "undefined") {
const d = new Date();
d.setTime(d.getTime() + (exdays*24*60*60*1000));
const expires = "expires="+ d.toUTCString();
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
}
}
export function getCookie(cname: string) {
if (typeof window !== "undefined") {
const name = cname + "=";
const decodedCookie = decodeURIComponent(document.cookie);
const ca = decodedCookie.split(';');
for(let i = 0; i <ca.length; i++) {
let c = ca[i];
while (c.charAt(0) == ' ') {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
return c.substring(name.length, c.length);
}
}
return "";
}
}

View file

@ -1,4 +0,0 @@
/** @type {import('next').NextConfig} */
const nextConfig = {};
export default nextConfig;

8464
app/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,52 +0,0 @@
{
"name": "brawlset",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@prisma/client": "^5.22.0",
"@radix-ui/react-accordion": "^1.2.3",
"@radix-ui/react-alert-dialog": "^1.1.6",
"@radix-ui/react-avatar": "^1.1.1",
"@radix-ui/react-checkbox": "^1.1.2",
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-dropdown-menu": "^2.1.2",
"@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-popover": "^1.1.3",
"@radix-ui/react-slot": "^1.1.2",
"@radix-ui/react-toast": "^1.2.6",
"@tabler/icons": "^3.22.0",
"@tabler/icons-react": "^3.22.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
"dotenv": "^16.4.5",
"lucide-react": "^0.453.0",
"next": "14.2.15",
"pg": "^8.13.1",
"react": "^18",
"react-dom": "^18",
"tailwind-merge": "^2.5.4",
"tailwindcss-animate": "^1.0.7",
"ts-node": "^10.9.2"
},
"devDependencies": {
"@types/node": "^20.17.6",
"@types/react": "^18",
"@types/react-dom": "^18",
"eslint": "^8",
"eslint-config-next": "14.2.15",
"postcss": "^8",
"prisma": "^5.22.0",
"tailwindcss": "^3.4.1",
"tsx": "^4.19.2",
"typescript": "^5.6.3"
}
}

View file

@ -1,8 +0,0 @@
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
tailwindcss: {},
},
};
export default config;

View file

@ -1,89 +0,0 @@
generator client {
provider = "prisma-client-js"
previewFeatures = ["relationJoins"]
binaryTargets = ["native","linux-musl","linux-musl-openssl-3.0.x"]
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model utilisateurice {
id String @id @default(uuid()) @db.Uuid
username String
password String
email String
admin Boolean @default(false)
deck deck[]
}
model carte {
id String @id @default(uuid()) @db.Uuid
name String
sanitized_name String
released_at String
layout String
small_image String
small_image_back String?
normal_image String
normal_image_back String?
type_line String?
color_identity String[]
set set @relation(fields: [set_id], references: [id])
set_id String @db.Uuid
set_code String
rarity String
type String?
price String?
cardmarket_uri String?
can_be_commander Boolean
is_promo Boolean
decks cartes_dans_deck[]
decks_as_commander deck[]
}
model deck {
id String @id @default(uuid()) @db.Uuid
url String?
color_identity String[]
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 {
id String @id @default(uuid()) @db.Uuid
name_en String
sanitized_name String
code String
set_type String
released_at String?
icon_svg_uri String
cards carte[]
bset bset? @relation(fields: [bset_id], references: [id])
bset_id String? @db.Uuid
}
model bset {
id String @id @default(uuid()) @db.Uuid
name String
sanitized_name String
sets set[]
decks deck[]
}

View file

@ -1,38 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
width="600"
height="849.88568"
id="svg3234">
<defs
id="defs3236" />
<metadata
id="metadata3239">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(-209.32562,77.669905)"
id="layer1">
<path
d="m 277.30227,767.01932 c -14.98382,-4.60496 -20.64186,-12.00446 -17.72072,-23.17455 2.54286,-9.72384 25.17485,-17.24278 60.09182,-19.96423 17.63669,-1.37448 37.91459,-4.62606 45.06184,-7.22544 37.25354,-13.54849 73.6648,-41.33741 82.79446,-63.18741 2.40778,-5.76203 6.99974,-43.7987 10.86075,-89.95941 6.1682,-73.74185 6.311,-80.39439 1.83471,-85.34071 -2.67002,-2.95053 -24.65509,-15.2711 -48.8556,-27.37918 C 372.66032,431.42116 364.58431,426.03025 344.2126,405.95958 323.19716,385.25499 319.73422,380.09259 306.75918,350.12703 290.92799,313.56525 263.72307,233.30669 255.45209,198.76349 250.4907,178.04213 248.79634,175.1888 231.38891,158.24067 217.33102,144.5539 212.143,137.20262 210.57731,128.75229 c -3.7542,-20.26282 0.63506,-32.423269 17.27292,-47.854696 l 15.18709,-14.08577 -2.14895,-61.0241488 c -2.08752,-59.2802402 -1.96714,-61.2551122 4.20972,-69.1076892 3.49724,-4.445957 11.36244,-9.473279 17.47826,-11.171974 15.25642,-4.237222 478.24875,-4.237222 493.50479,0 6.11602,1.698695 14.01192,6.756348 17.54646,11.239605 6.24525,7.921151 6.36243,9.719126 4.15075,63.68249188 C 776.52651,30.972325 775.65258,58.5171 775.83626,61.640761 c 0.18274,3.123662 7.04458,11.903485 15.24605,19.510969 16.3485,15.163915 20.73304,27.4421 16.99806,47.60056 -1.56757,8.46032 -6.76369,15.81122 -20.91841,29.59218 -17.48429,17.02274 -19.21595,19.92429 -24.18224,40.52263 -8.56807,35.53582 -35.54297,115.43249 -51.06935,151.25993 -12.98144,29.95501 -16.45739,35.13531 -37.46529,55.83255 -20.3719,20.07067 -28.4479,25.46158 -67.15712,44.82881 -24.20051,12.10808 -46.18539,24.42865 -48.85541,27.37918 -4.47647,4.94632 -4.33273,11.63051 1.84149,85.74461 3.7606,45.14059 8.48368,84.69115 10.77316,90.21299 8.72218,21.03653 45.99719,49.15212 82.87508,62.51128 7.14726,2.58902 27.63596,5.93366 45.52999,7.43229 35.25983,2.95335 57.12094,10.20403 59.62405,19.77603 3.01214,11.51936 -2.67134,18.50347 -19.00533,23.35408 -22.89442,6.79911 -440.58943,6.63691 -462.76872,-0.17897 z"
inkscape:connector-curvature="0"
id="path7117"
style="fill:#000000" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -1,21 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
version="1.1"
id="svg1"
width="196.8"
height="313.92001"
viewBox="0 0 196.8 313.92001"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<g
id="g1">
<path
style="display:inline;fill:#000000"
d="M 96.320001,313.40138 C 95.076773,312.62187 81.49991,295.60736 80.997053,294.19868 l -0.442949,-1.24086 17.681525,-48.57366 c 9.724841,-26.71552 17.628161,-48.62183 17.562951,-48.68071 -0.35562,-0.32102 -44.593783,-24.28395 -45.05205,-24.40379 -0.398576,-0.10423 -2.71562,2.38823 -8.526912,9.17245 -4.388789,5.12356 -8.019833,9.49241 -8.068986,9.70854 -0.04915,0.21614 2.218848,9.13697 5.040001,19.82407 2.821152,10.6871 5.129368,19.61729 5.129368,19.84486 0,0.67091 -7.611897,21.35883 -8.174903,22.21809 -0.516408,0.78813 -2.229594,1.69234 -3.206458,1.69234 -0.287054,0 -1.017772,-0.2353 -1.623817,-0.52289 -0.897986,-0.42612 -1.652705,-1.44763 -4.078312,-5.52 C 25.682028,211.52918 11.152081,171.83985 4.3462228,130.56001 1.6197832,114.02322 0,95.660031 0,81.28766 0,75.078203 0.04263351,74.592113 0.65559264,73.812862 1.6837009,72.505834 96.232205,0.60704129 97.390241,0.25162984 98.163133,0.01442256 98.703079,0.05252569 99.531845,0.40275926 101.46605,1.220148 195.61007,73.16182 196.26493,74.32291 c 0.53622,0.950725 0.56848,1.636703 0.38742,8.237094 -0.49091,17.895296 -1.92443,33.081656 -4.6518,49.280006 -1.50352,8.92965 -2.43272,13.11567 -3.11571,14.03627 -0.92574,1.2478 -2.81622,1.79919 -4.24473,1.23805 -1.65348,-0.6495 -42.83099,-35.30468 -43.40168,-36.52705 -0.25352,-0.543 -3.68019,-12.839587 -7.61482,-27.325745 -3.93463,-14.486158 -7.20557,-26.376247 -7.26874,-26.422419 -0.25098,-0.183427 -34.012768,-12.007881 -34.083517,-11.937132 -0.195052,0.195051 -37.639624,61.515706 -37.768065,61.850426 -0.08113,0.21142 25.483777,21.8881 57.317352,48.59981 31.60509,26.51999 57.62644,48.52205 57.82522,48.89347 0.74334,1.38895 0.37554,2.9055 -1.99701,8.23432 -9.99817,22.45617 -22.91518,44.41087 -38.02711,64.63366 -5.70912,7.63994 -28.06775,35.27685 -29.19434,36.08634 -1.1768,0.84557 -2.941955,0.93211 -4.107399,0.20137 z"
id="path1" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -1,38 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
width="600.00006"
height="533.4812"
id="svg3208">
<defs
id="defs3210" />
<metadata
id="metadata3213">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(-510.31037,-331.03141)"
id="layer1">
<path
d="m 713.85991,852.97324 c -13.24237,-13.24237 -14.84693,-23.54582 -7.09008,-45.53094 6.99159,-19.81635 16.57282,-30.21975 46.99885,-51.03259 15.37837,-10.51951 42.36291,-30.01837 59.96548,-43.3307 30.71662,-23.23012 46.24631,-32.88718 138.57862,-86.17383 67.21712,-38.79226 157.99762,-74.97988 157.99762,-62.98235 0,5.72718 -21.6024,21.17322 -51.8605,37.08105 -38.8505,20.42524 -148.00006,94.34145 -180.46523,122.21143 -25.57402,21.9543 -59.52308,58.95089 -95.23194,103.78065 -32.31156,40.56494 -48.28299,46.58727 -68.89282,25.97728 z M 582.44653,816.20576 c -8.45298,-9.07328 -10.25942,-20.87627 -6.1929,-40.46499 5.2375,-25.22816 4.44304,-50.05388 -2.02527,-63.29429 -4.62779,-9.47312 -9.75636,-13.42386 -30.8275,-23.74688 -13.90181,-6.81075 -27.06754,-14.83324 -29.25718,-17.82777 -8.88347,-12.14885 -1.85438,-42.35067 16.19924,-69.60247 15.03429,-22.6943 70.08906,-84.7188 103.21529,-116.28207 34.27584,-32.65888 56.12645,-47.6048 82.96195,-56.74722 20.31794,-6.9218 32.05522,-12.39753 98.21751,-45.81973 78.12883,-39.46719 156.03835,-62.44863 156.03835,-46.0273 0,2.79086 -15.37038,11.06447 -42.01036,22.61341 -58.01571,25.15103 -67.51638,30.78852 -109.88679,65.20542 -20.43225,16.59679 -52.72358,41.95507 -71.75852,56.35162 -36.37515,27.5111 -64.18822,55.36967 -93.04461,93.19691 -37.09377,48.6251 -41.04109,58.81668 -29.87389,77.13251 3.29473,5.40382 5.94112,13.84359 5.99037,18.75463 0.11904,11.89398 5.92237,8.12016 11.5416,3.70876 8.32595,-6.53631 22.8854,-19.75439 46.97278,-42.4296 63.70864,-59.9738 148.65491,-122.48685 207.54269,-152.73336 37.96748,-19.50115 139.96581,-61.43062 168.98981,-69.46828 26.6216,-7.37234 42.0707,-8.09195 42.0707,-1.95939 0,5.34202 -7.4131,9.84589 -70.7112,42.96168 -87.20664,45.62406 -123.09569,71.60314 -191.85365,138.87721 -37.24738,36.4438 -103.39288,96.203 -150.30449,135.79298 -5.41638,4.57104 -24.86797,25.80313 -43.2257,47.1823 -18.35757,21.37917 -36.85635,41.60758 -41.10811,44.95205 -9.97667,7.84768 -20.15683,7.72767 -27.66012,-0.32613 z"
inkscape:connector-curvature="0"
id="path7130"
style="fill:#000000" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -1,38 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
width="600"
height="470.37875"
id="svg3193">
<defs
id="defs3195" />
<metadata
id="metadata3198">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(-74.617828,-93.966166)"
id="layer1">
<path
d="M 227.47403,561.75843 C 207.24579,558.40482 79.908114,529.25417 76.620024,527.22197 c -4.4587,-2.75567 0.63436,-6.37383 11.94276,-8.48412 9.97287,-1.86114 82.172696,-20.26915 112.536416,-28.69223 13.22825,-3.66957 19.03248,-12.07303 14.36399,-20.7963 -2.70246,-5.04959 -66.2607,-60.97927 -123.865836,-108.99867 -17.4482,-14.54494 -20.99596,-20.17028 -12.72047,-20.17028 2.49562,0 41.615306,14.96527 86.932496,33.25621 45.31731,18.29081 84.80719,32.79907 87.75534,32.24037 12.29295,-2.32957 8.13469,-17.7239 -30.48361,-112.85687 -36.69419,-90.39306 -42.61293,-108.76388 -33.37597,-103.59465 2.37278,1.32796 33.44868,41.23856 69.05753,88.69028 65.01878,86.64283 70.75428,92.55169 79.54961,81.95408 2.10247,-2.53346 9.05782,-56.11536 16.80808,-129.48607 13.49588,-127.76175 14.71997,-136.317554 19.50335,-136.317554 4.34971,0 5.27361,6.636194 19.16919,137.690004 13.71054,129.30808 14.97592,135.29299 27.7473,131.23949 3.14139,-0.99699 35.22173,-40.97928 71.28978,-88.84954 36.06792,-47.87026 66.73875,-87.03675 68.15723,-87.03675 5.88502,0 0.85671,15.01798 -33.70527,100.66907 -19.95652,49.45557 -37.46987,93.80604 -38.91862,98.55659 -3.15267,10.33757 1.03675,19.876 8.72994,19.876 2.94199,0 40.37442,-14.35605 83.18303,-31.90223 77.0726,-31.59011 94.34154,-37.44717 94.34154,-31.99762 0,1.54671 -20.5133,20.27107 -45.58507,41.60991 -99.32162,84.53292 -101.26842,86.50973 -95.60896,97.08439 3.63533,6.79275 6.01606,7.57393 78.16486,25.6479 66.48498,16.65536 64.76143,16.10807 61.2801,19.4563 -4.07799,3.92193 -104.24375,26.84943 -163.46883,37.41742 -14.61264,2.60731 -16.35991,-1.26179 -16.4407,-36.40478 -0.0616,-26.69068 -1.29076,-35.41639 -7.00754,-49.73205 -9.69782,-24.28443 -34.12561,-51.39814 -59.91009,-62.87234 -34.77689,-15.47576 -73.57113,-14.51621 -103.07505,-0.0244 -47.33861,23.25102 -71.40325,66.16517 -67.37271,119.91724 1.67751,22.37059 1.20421,24.74863 -3.96604,27.76294 -8.05186,4.69324 -17.16443,0.84312 -24.15377,-0.3157 z"
inkscape:connector-curvature="0"
id="path7143"
style="fill:#000000" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -1,38 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
width="600"
height="798.29449"
id="svg3260">
<defs
id="defs3262" />
<metadata
id="metadata3265">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(-126.2445,-111.90626)"
id="layer1">
<path
d="m 371.57707,908.61481 c -0.98691,-0.9867 -0.94614,-4.0257 0.09,-6.7534 1.03667,-2.7275 39.21124,-63.714 84.8321,-135.5256 45.6207,-71.8114 84.60551,-133.6678 86.63266,-137.4585 5.863,-10.9625 3.04531,-25.2559 -5.97086,-30.2919 -7.10708,-3.9695 -24.22149,-1.7812 -193.59847,24.7552 -102.19094,16.0103 -189.33421,29.0708 -193.65129,29.0234 -12.63035,-0.1393 -23.66671,-10.7845 -23.66671,-22.8293 0,-12.7751 13.70899,-39.2564 96.33389,-186.08678 76.14904,-135.32228 74.83463,-130.01779 34.29302,-138.38284 -13.28704,-2.74159 -18.94621,-12.09393 -15.53722,-25.67637 2.92691,-11.66213 79.52303,-137.50094 91.12775,-149.71292 5.66664,-5.96322 15.43242,-12.37896 21.70173,-14.2573 14.40509,-4.31589 326.25013,-4.83092 337.49053,-0.55733 11.073,4.21007 14.825,12.9869 10.1836,23.82282 -2.0915,4.88238 -78.06728,85.99788 -168.83526,180.25652 -90.76781,94.25884 -166.69058,174.54354 -168.71654,178.41048 -2.96548,5.65984 -2.88683,8.12082 0.40223,12.61897 3.93336,5.37923 8.10248,4.7774 111.68733,-16.12173 59.18105,-11.94037 112.66675,-21.69884 118.85742,-21.68576 14.92691,0.0323 20.99833,6.6442 29.31161,31.92359 15.48761,47.09528 20.46141,50.82765 63.56401,47.70085 32.041,-2.32424 40.7414,0.87122 37.5031,13.7744 -1.003,3.9957 -25.1768,30.1217 -53.7198,58.0577 -28.5429,27.9363 -89.04025,87.3578 -134.43826,132.048 -162.15269,159.625 -160.61628,158.2083 -165.8766,152.9478 z"
inkscape:connector-curvature="0"
id="path7156"
style="fill:#000000" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -1,38 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
width="600"
height="470.40985"
id="svg3167">
<defs
id="defs3169" />
<metadata
id="metadata3172">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(-109.58004,-73.095985)"
id="layer1">
<path
d="M 328.63936,541.67929 C 246.53884,533.77761 165.84388,512.6966 132.07303,490.32766 96.641563,466.85884 102.10545,442.36571 155.33678,386.04328 c 47.79682,-50.57247 69.78599,-92.9501 100.81797,-194.29796 20.38021,-66.55995 39.18723,-108.401257 51.90149,-115.468842 19.63437,-10.914083 33.19725,4.882525 59.18602,68.933912 27.62365,68.08066 51.2835,109.36882 80.49105,140.46283 8.81695,9.38627 17.39024,15.77384 21.17158,15.77384 7.47226,0 18.42198,-13.08595 38.06261,-45.48852 15.90054,-26.23243 28.05191,-34.47776 46.56017,-31.59338 17.13916,2.6709 30.08009,19.69425 45.28907,59.57568 7.13786,18.71712 17.37737,42.81959 22.75449,53.56078 10.08757,20.15073 35.72363,57.03791 39.7181,57.14976 4.60422,0.12868 39.1318,34.82074 43.89588,44.10456 14.44499,28.14975 -6.88892,53.0083 -61.48392,71.64177 -65.61796,22.39567 -124.91599,31.36027 -217.5119,32.88281 -38.00751,0.62508 -81.90503,-0.0957 -97.55003,-1.60123 z"
inkscape:connector-curvature="0"
id="path7169"
style="fill:#000000" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

View file

@ -1,61 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
width="600"
height="1204.1633"
id="svg3570">
<defs
id="defs3572">
<clipPath
id="clipPath3783">
<path
d="M 0,300 147,300 147,0 0,0 0,300 z"
inkscape:connector-curvature="0"
id="path3785" />
</clipPath>
</defs>
<metadata
id="metadata3575">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(-128.125,398.84217)"
id="layer1">
<g
transform="matrix(4.0816327,0,0,-4.0816327,128.125,815.48356)"
id="g3777">
<g
id="g3779">
<g
clip-path="url(#clipPath3783)"
id="g3781">
<g
transform="translate(145.458,184.2598)"
id="g3787">
<path
d="m 0,0 c -1.245,32.734 -4.061,45.164 -5.927,45.164 -1.894,0 -2.49,-18.131 -4.979,-34.153 -2.49,-15.985 -6.874,-34.113 -6.874,-34.113 l -11.204,4.268 c 0,0 -3.141,23.131 -4.385,50.851 -1.216,27.721 -2.164,51.931 -5.63,51.931 -3.382,0.029 -4.031,-22.762 -5.276,-52.296 -1.246,-29.517 -5.601,-45.865 -5.601,-45.865 l -10.283,1.433 c 0,0 -4.98,25.602 -6.848,103.807 -0.433,18.509 -4.951,22.223 -4.951,22.223 0,0 -4.52,-3.714 -4.953,-22.223 -1.866,-78.205 -6.874,-103.807 -6.874,-103.807 l -10.257,-1.433 c 0,0 -4.382,16.348 -5.627,45.865 -1.245,29.534 -1.869,52.325 -5.276,52.296 -3.438,0 -4.386,-24.21 -5.659,-51.931 -1.216,-27.72 -4.33,-50.851 -4.33,-50.851 l -11.204,-4.268 c 0,0 -4.382,18.128 -6.872,34.113 -2.489,16.022 -3.113,34.153 -4.979,34.153 -1.868,0 -4.681,-12.43 -5.927,-45.164 -1.245,-32.693 -1.542,-39.084 -1.542,-39.084 0,0 36.777,-15.67 51.093,-56.223 14.343,-40.529 17.969,-75.72 18.077,-79.627 0.188,-6.064 4.33,-6.836 4.33,-6.836 0,0 3.6,0.772 4.33,6.836 0.459,3.879 3.734,39.098 18.075,79.627 14.318,40.553 51.095,56.223 51.095,56.223 0,0 -0.299,6.391 -1.542,39.084"
inkscape:connector-curvature="0"
id="path3789"
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none" />
</g>
</g>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

View file

@ -1,38 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
width="600"
height="758.45209"
id="svg3338">
<defs
id="defs3340" />
<metadata
id="metadata3343">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(-248.75759,103.7998)"
id="layer1">
<path
d="m 549.35881,651.15793 c -7.26111,-3.30528 -9.75906,-6.38344 -9.75906,-12.02521 0,-9.63732 8.08597,-14.82133 32.81288,-21.03582 10.615,-2.66807 21.08723,-6.33414 23.27159,-8.14697 6.81083,-5.65252 4.49888,-16.5977 -5.23635,-24.78929 -23.09774,-19.43541 -67.63066,-21.56509 -115.4088,-5.51909 -36.947,12.4082 -52.50696,14.06376 -79.62666,8.47176 -34.64907,-7.14427 -67.84003,-25.03721 -93.95261,-50.64833 -26.21188,-25.70856 -39.07084,-47.2129 -47.17177,-78.88733 -13.77915,-53.87651 -1.31183,-108.98633 31.84244,-140.75376 22.18432,-21.25618 63.3297,-33.24003 73.21822,-21.32512 3.03843,3.66117 1.3796,5.78081 -9.81608,12.54327 -38.97877,23.54405 -42.44669,77.09646 -7.39267,114.16076 29.4188,31.10591 66.36486,43.04256 133.33259,43.07667 77.97133,0.0397 108.53348,6.46944 138.17357,29.06853 15.91748,12.1362 33.35102,35.33256 37.51949,49.92138 5.0202,17.56954 7.82356,20.67854 15.123,16.77202 9.13048,-4.88654 17.30572,-26.03103 17.38026,-44.95259 0.17058,-43.53187 -29.41295,-86.80809 -73.86362,-108.04745 -17.36811,-8.29885 -26.87761,-10.32104 -98.17715,-20.87833 -23.04844,-3.41301 -33.22998,-7.90698 -48.71307,-21.50106 -11.7892,-10.35119 -19.40549,-22.99003 -19.40549,-32.20276 0,-8.91341 3.13517,-9.47539 23.06736,-4.13482 14.85755,3.98106 19.78241,4.20141 27.00777,1.20854 13.29452,-5.5067 20.36543,-19.68263 20.42174,-40.94091 0.11216,-42.38594 -35.18535,-71.20981 -114.03762,-93.1233 C 356.52243,185.39467 317.72545,156.03943 301.5472,122.99917 284.34055,87.85892 279.29745,39.536552 288.96328,2.4264521 306.88472,-66.378407 371.02643,-108.50168 450.07709,-103.38006 c 35.58306,2.30541 62.68734,13.967959 58.74366,25.276943 -0.4129,1.184015 -14.26332,2.339288 -30.77877,2.567351 -19.8892,0.274798 -34.59065,2.122206 -43.54098,5.471189 -43.63514,16.327808 -61.94402,50.84462 -49.67719,93.654906 7.33612,25.603172 28.66824,44.991379 77.06305,70.040047 48.43336,25.068764 50.03238,26.213994 89.59182,64.170704 37.99478,36.45512 51.65803,44.90072 72.63941,44.90072 48.47589,0 64.72472,-58.86938 28.19389,-102.14586 C 642.01314,88.355472 633.86991,84.008945 592.149,68.443608 565.01575,58.320717 558.94683,54.937385 558.15912,49.494938 c -1.87638,-12.964572 19.99622,-15.887338 58.8897,-7.869829 45.31432,9.341259 94.90108,38.511196 137.35432,80.800391 40.53175,40.37475 65.35563,84.30293 80.83521,143.04448 35.48117,134.64419 -0.2748,268.71238 -90.85178,340.65077 -22.29018,17.70367 -59.43089,35.45314 -87.67712,41.90131 -31.36972,7.1611 -94.45921,9.00407 -107.35064,3.13587 z"
inkscape:connector-curvature="0"
id="path7218"
style="fill:#000000" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.4 KiB

View file

@ -1,70 +0,0 @@
let
nixpkgs = import (builtins.fetchTarball "https://github.com/NixOS/nixpkgs/archive/24.11-beta.tar.gz") {
overlays = [];
config = {};
};
in
with nixpkgs;
stdenv.mkDerivation {
name = "postgres-env";
buildInputs = [];
nativeBuildInputs = [
zsh
vim
geos
gdal
nixpkgs-fmt
nodejs_22
openssl
prisma
# postgres-12 with postgis support
(postgresql_12.withPackages (p: [ p.postgis ]))
];
postgresConf =
writeText "postgresql.conf"
''
# Add Custom Settings
log_min_messages = warning
log_min_error_statement = error
log_min_duration_statement = 100 # ms
log_connections = on
log_disconnections = on
log_duration = on
#log_line_prefix = '[] '
log_timezone = 'UTC'
log_statement = 'all'
log_directory = 'pg_log'
log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log'
logging_collector = on
log_min_error_statement = error
'';
# ENV Variables
LD_LIBRARY_PATH = "${geos}/lib:${gdal}/lib";
PGDATA = "${toString ./.}/.pg";
# Post Shell Hook
shellHook = ''
echo "Using ${postgresql_12.name}."
# Setup: other env variables
export PGHOST="$PGDATA"
export PRISMA_SCHEMA_ENGINE_BINARY="${pkgs.prisma-engines}/bin/schema-engine"
export PRISMA_QUERY_ENGINE_BINARY="${pkgs.prisma-engines}/bin/query-engine"
export PRISMA_QUERY_ENGINE_LIBRARY="${pkgs.prisma-engines}/lib/libquery_engine.node"
export PRISMA_FMT_BINARY="${pkgs.prisma-engines}/bin/prisma-fmt"
export PATH="$PWD/node_modules/.bin/:$PATH"
# Setup: DB
[ ! -d $PGDATA ] && pg_ctl initdb -o "-U postgres" && cat "$postgresConf" >> $PGDATA/postgresql.conf
pg_ctl -o "-p 5555 -k $PGDATA" start
alias fin="pg_ctl stop && exit"
alias pg="psql -p 5555 -U postgres"
'';
}

View file

@ -1,85 +0,0 @@
import type { Config } from "tailwindcss";
const config: Config = {
darkMode: ["class"],
content: [
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {
colors: {
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
card: {
DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))'
},
popover: {
DEFAULT: 'hsl(var(--popover))',
foreground: 'hsl(var(--popover-foreground))'
},
primary: {
DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))'
},
secondary: {
DEFAULT: 'hsl(var(--secondary))',
foreground: 'hsl(var(--secondary-foreground))'
},
muted: {
DEFAULT: 'hsl(var(--muted))',
foreground: 'hsl(var(--muted-foreground))'
},
accent: {
DEFAULT: 'hsl(var(--accent))',
foreground: 'hsl(var(--accent-foreground))'
},
destructive: {
DEFAULT: 'hsl(var(--destructive))',
foreground: 'hsl(var(--destructive-foreground))'
},
border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))',
chart: {
'1': 'hsl(var(--chart-1))',
'2': 'hsl(var(--chart-2))',
'3': 'hsl(var(--chart-3))',
'4': 'hsl(var(--chart-4))',
'5': 'hsl(var(--chart-5))'
}
},
borderRadius: {
lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)'
},
keyframes: {
'accordion-down': {
from: {
height: '0'
},
to: {
height: 'var(--radix-accordion-content-height)'
}
},
'accordion-up': {
from: {
height: 'var(--radix-accordion-content-height)'
},
to: {
height: '0'
}
}
},
animation: {
'accordion-down': 'accordion-down 0.2s ease-out',
'accordion-up': 'accordion-up 0.2s ease-out'
}
}
},
plugins: [require("tailwindcss-animate")],
};
export default config;

View file

@ -1,80 +0,0 @@
import { PrismaClient } from '@prisma/client'
const db = new PrismaClient()
const Bsets = [
{"name": "Wild of Eldraine", sets: ["woe"]},
{"name": "Bloomburrow", sets: ["blb"]},
{"name": "Duskmourn", sets: ["dsk"]},
{"name": "Phyrexia", sets: ["one"]},
{"name": "The Brothers' War", sets: ["bro"]},
{"name": "Murders at Karlov Manor", sets: ["mkm"]},
{"name": "The lost caverns of Ixalan", sets: ["lci"]},
{"name": "Dominaria United", sets: ["dmu"]},
{"name": "New Capenna", sets: ["snc"]},
{"name": "Kamigawa", sets: ["neo"]},
{"name": "Forgotten Realms", sets: ["afr"]},
{"name": "Strixhaven", sets: ["stx"]},
{"name": "Kaldheim", sets: ["khm"]},
{"name": "Zendikar Rising", sets: ["znr"]},
{"name": "Ikoria", sets: ["iko"]},
{"name": "Theros Beyond Death", sets: ["thb"]},
{"name": "Eldraine", sets: ["eld"]},
{"name": "War of the Spark", sets: ["war"]},
{"name": "Dominaria", sets: ["dom"]},
{"name": "March of the machine", sets: ["mom","mat"]},
{"name": "Outlaws", sets: ["otj","big"]},
{"name": "Innistrad Midnight Hunt", sets: ["vow","mid"]},
{"name": "Guilds of Ravnica", sets: ["rna","grn"]},
{"name": "Ixalan", sets: ["rix","xln"]},
{"name": "Amonkhet", sets: ["hou","akh"]},
{"name": "Kaladesh", sets: ["aer","kld"]},
{"name": "Shadows over Innistrad", sets: ["emn","soi"]},
{"name": "Battle for Zendikar", sets: ["ogw","bfz"]},
{"name": "Tarkir", sets: ["dtk","ktk","frf"]},
{"name": "Theros", sets: ["jou","bng","ths"]},
{"name": "Return to Ravnica", sets: ["dgm","gtc","rtr"]},
{"name": "Innistrad", sets: ["avr","dka","isd"]},
{"name": "New Phyrexia", sets: ["nph","mbs","som"]},
{"name": "Zendikar", sets: ["roe","wwk","zen"]},
{"name": "Alara", sets: ["arb","con","ala"]},
{"name": "Ravnica", sets: ["rav","gpt","dis"]},
{"name": "Kamigawa 3x", sets: ["chk","bok","sok"]},
{"name": "Mirrodin", sets: ["5dn","dst","mrd"]},
{"name": "Onslaught", sets: ["ons","lgn","scg"]},
{"name": "Odyssey", sets: ["ody","tor","jud"]},
{"name": "Invasion", sets: ["inv","pls","apc"]},
{"name": "Masques", sets: ["mmq","nem","pcy"]},
{"name": "Urza", sets: ["usg","ulg","uds"]},
{"name": "Tempest", sets: ["tmp","sth","exo"]},
{"name": "Mirage", sets: ["wth","mir","vis"]},
{"name": "Ice Age", sets: ["csp","ice","all"]},
{"name": "Time Spiral", sets: ["fut","tsp","plc","tsb"]},
{"name": "Lorwyn-Shadowmoor", sets: ["lrw","shm","mor","eve"]},
]
async function createBsets(bset_obj) {
const bset = await db.bset.create({
data: {
name: bset_obj.name,
sanitized_name: bset_obj.name.replace(/[^a-zA-Z0-9]/gim,"-").toLowerCase()
}
})
const or_filter = []
bset_obj.sets.forEach((set_id) => {
or_filter.push({ code: set_id })
})
await db.set.updateMany({
where: {
OR: or_filter
},
data: {
bset_id: bset.id
}
})
}
Bsets.forEach(createBsets)

View file

@ -1,49 +0,0 @@
import { readFileSync, readdirSync } from "fs"
const base_api_url = "http://localhost:3000"
const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6Imp3dCJ9.eyJ1c2VybmFtZSI6Inp1bWEiLCJhZG1pbiI6dHJ1ZSwiaWQiOiJjMmZlNDhhNi1iNmVmLTRhMDktOTZmNi0wM2YyNTNkZjExZGQiLCJtYXhBZ2UiOjYwNDgwMH0=.0eee4c6c4c2a13b4add8ca7b34e1f1cdec5e16b56d6cae77ede9974fccd8f2d6"
let bsets_sanitized_names = readdirSync(import.meta.dirname + "/data/decks_bsets/")
function get_line_data(line) {
let data = line.split(" ")
let amount = parseInt(data[0])
let name = data.slice(1).join(" ").split("/")[0].replace(/[^a-zA-Z0-9]/gim,"-").toLowerCase()
return {"sanitized_name":name, "amount":amount}
}
for(const sanitized_name of bsets_sanitized_names) {
let path = import.meta.dirname + "/data/decks_bsets/" + sanitized_name + "/"
let deck_files = readdirSync(path)
for( const deck_file of deck_files) {
let api_object = {
"selected_bset": sanitized_name,
"name": deck_file.split(".txt")[0],
"commander_name": "",
"cards": []
}
let data = readFileSync(path + deck_file, 'utf8')
let lines = data.split(/\n/)
lines = lines.filter((line) => line.match(/[0-9]+\s[\w]+/) != undefined)
let commander_line = lines[lines.length - 1]
api_object["commander_name"] = get_line_data(commander_line).sanitized_name
for(const card of lines.slice(0, lines.length - 1)){
api_object.cards.push(get_line_data(card))
}
fetch(base_api_url + '/api/account/decks/create', {
method: "POST",
headers: {Authorization: 'Bearer ' + token},
body: JSON.stringify(api_object)
}).then((res) => {
if(res.status == 200) {
console.log("Deck created")
} else {
console.log("Problem with : " + deck_file)
}
})
}
}

View file

@ -1,238 +0,0 @@
import { PrismaClient } from '@prisma/client'
import { writeFileSync } from 'fs'
import { performance } from 'perf_hooks'
const db = new PrismaClient()
const color_names = {
"white": ["W"],
"black": ["B"],
"blue": ["U"],
"green": ["G"],
"red": ["R"],
"colorless": [],
"azorius": ["W","U"],
"dimir": ["U","B"],
"rakdos": ["B","R"],
"gruul": ["R","G"],
"selesnya": ["G","W"],
"orzhov": ["W","B"],
"izzet": ["U","R"],
"golgari": ["B","G"],
"boros": ["R","W"],
"simic": ["G","U"],
"esper": ["W","U","B"],
"grixis": ["U","B","R"],
"jund": ["B","R","G"],
"naya": ["R","G","W"],
"bant": ["G","W","U"],
"abzan": ["W","B","G"],
"jeskai": ["U","R","W"],
"sultai": ["B","G","U"],
"mardu": ["R","W","B"],
"temur": ["G","U","R"],
"yore-tiller": ["W","U","B","R"],
"glint-eye": ["U","B","R","G"],
"dune-brood": ["B","R","G","W"],
"ink-treader": ["R","G","W","U"],
"witch-maw": ["G","W","U","B"],
"five-color": ["G","W","U","B","R"],
}
function getColorName(colorArray) {
const colorArrayID = colorArray.sort().join(',')
for (const colorName of Object.keys(color_names)) {
if(colorArrayID === color_names[colorName].sort().join(',')){
return(colorName)
}
}
return ""
}
async function createJson() {
const start = performance.now()
console.log(process.env.NODE_ENV)
console.log("Fetching data...")
const bsets = await db.bset.findMany({
relationLoadStrategy: "join",
include: {
sets: {
include: {
cards: {
include: {
decks: {
include: {
deck: {
include: {
commander: {
include: {
decks_as_commander: true,
}
},
},
},
}
},
decks_as_commander: true,
}
}
}
},
decks: {
include: {
commander: {
include: {
decks_as_commander: true
}
}
}
}
}
})
console.log("Creating stats...")
let bsets_list_export = []
let all_cards = []
const commanderData = {"top": []}
for (const colorName of Object.keys(color_names)) {
commanderData[colorName] = []
}
let bset_cards_data_export = {}
let commander_details_export = {}
bsets.forEach((bset) => {
let icons = []
let set_codes = []
let bset_cards = []
bset.sets.forEach((set) => {
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
}
bset_cards = [...bset_cards, ...cards_temp]
all_cards = [...all_cards, ...cards_temp]
})
bsets_list_export.push({name: bset.name, sanitized_name: bset.sanitized_name, icons, set_codes})
// BSETS CARDS STATS
bset_cards_data_export[bset.sanitized_name] = {
"commander": [],
"creature": [],
"land": [],
"enchantment": [],
"sorcery": [],
"instant": [],
"planeswalker": [],
"artifact": [],
}
for (const card of bset_cards) {
let card_object = {
"name": card.name,
"sanitized_name": card.sanitized_name,
"url": card.set_code + "-" + 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": bset.decks.filter((deck) => card.color_identity.every(color => deck.color_identity.includes(color))).length,
"color_identity": card.color_identity,
}
card_object.percent_decks = (card_object.total_decks != 0) ? parseInt(100 * (card_object.nbr_decks / card_object.total_decks)) : 0
bset_cards_data_export[bset.sanitized_name][card.type].push(card_object)
card.decks.forEach((deck) => {
const card_url_name = deck.deck.commander.set_code + "-" + deck.deck.commander.sanitized_name
if(!Object.keys(commander_details_export).includes(card_url_name)) {
commander_details_export[card_url_name] = {
"card_data": deck.deck.commander,
"creature": {},
"land": {},
"enchantment": {},
"sorcery": {},
"instant": {},
"planeswalker": {},
"artifact": {},
}
commander_details_export[card_url_name].card_data.nbr_decks = deck.deck.commander.decks_as_commander.length
}
if(!Object.keys(commander_details_export[card_url_name][card.type]).includes(card.sanitized_name)) {
let card_synergy_object = structuredClone(card_object)
card_synergy_object.commander_total = 0
commander_details_export[card_url_name][card.type][card.sanitized_name] = card_synergy_object
}
commander_details_export[card_url_name][card.type][card.sanitized_name].commander_total += 1
})
if(card.can_be_commander) {
let commander_card_object = structuredClone(card_object)
commander_card_object.total_decks = bset.decks.length
commander_card_object.nbr_decks = card.decks_as_commander != undefined ? card.decks_as_commander.length : 0
commander_card_object.percent_decks = (commander_card_object.total_decks != 0) ? parseInt(100 * (commander_card_object.nbr_decks / commander_card_object.total_decks)) : 0
bset_cards_data_export[bset.sanitized_name]["commander"].push(commander_card_object)
const colorName = getColorName(card.color_identity)
if(colorName != "") {
commanderData[colorName].push(commander_card_object)
commanderData["top"].push(commander_card_object)
}
}
}
})
console.log("Exporting stats...")
writeFileSync(import.meta.dirname + "/../data/misc/bsets.json",JSON.stringify(bsets_list_export), 'utf8')
for (const index of Object.keys(commanderData)) {
let JSONToWrite = commanderData[index].sort((a,b) => b.nbr_decks - a.nbr_decks || b.percent_decks - a.percent_decks)
writeFileSync(import.meta.dirname + "/../data/commander/" + index + ".json",JSON.stringify(JSONToWrite), 'utf8')
}
for (const index of Object.keys(commander_details_export)) {
let JSONToWrite = structuredClone(commander_details_export[index])
JSONToWrite["high_synergy"] = []
for (const type of ["creature","land","enchantment","sorcery","instant","planeswalker","artifact"]) {
JSONToWrite[type] = []
for (const card_key of Object.keys(commander_details_export[index][type])) {
let card_object_synergy = commander_details_export[index][type][card_key]
card_object_synergy.synergy = parseInt(100 * (card_object_synergy.commander_total / commander_details_export[index].card_data.nbr_decks) - card_object_synergy.percent_decks)
JSONToWrite[type].push(card_object_synergy)
}
JSONToWrite[type].sort((a,b) => b.synergy - a.synergy || b.percent_decks - a.percent_decks || b.nbr_decks - a.nbr_decks)
JSONToWrite["high_synergy"] = [...JSONToWrite["high_synergy"], ...JSONToWrite[type]]
}
JSONToWrite["high_synergy"].sort((a,b) => b.synergy - a.synergy || b.percent_decks - a.percent_decks || b.nbr_decks - a.nbr_decks)
JSONToWrite["high_synergy"] = JSONToWrite["high_synergy"].slice(0,15)
writeFileSync(import.meta.dirname + "/../data/card-commander/" + index + ".json",JSON.stringify(JSONToWrite), 'utf8')
}
for (const index of Object.keys(bset_cards_data_export)) {
let JSONToWrite = bset_cards_data_export[index]
for (const type of Object.keys(JSONToWrite)) {
JSONToWrite[type] = JSONToWrite[type].sort((a,b) => b.percent_decks - a.percent_decks || b.nbr_decks - a.nbr_decks)
}
writeFileSync(import.meta.dirname + "/../data/bset/" + index + ".json",JSON.stringify(JSONToWrite), 'utf8')
}
const end = performance.now()
console.log(`Time taken to generate stats is ${(end - start)/1000}s.`);
}
createJson()

View file

@ -1,51 +0,0 @@
import { PrismaClient } from '@prisma/client'
import { createHmac } from "crypto"
import 'dotenv/config'
const secret = process.env.PASSWORD_SECRET ? process.env.PASSWORD_SECRET : ""
const db = new PrismaClient()
const users = [
"zuma",
"nicolo",
"gororeznor",
"ragdub",
"onenleir",
"triskell",
"anubis",
"asmolith",
"izameh",
"lucie",
"kellaubz",
"bertrand",
"tibalt",
"jean",
"mercant",
"axel",
"aeddis",
"demo_a",
"demo_b",
"demo_c",
"demode",
]
async function createUser(username) {
const email = username + "@example.com"
const password = username + "123"
const hashed_password = createHmac('sha256',secret).update(password).digest('hex')
const admin = true
const user = await db.utilisateurice.create({
data: {
username,
password: hashed_password,
email,
admin,
}
})
console.log(user.username)
}
users.forEach(createUser)

Some files were not shown because too many files have changed in this diff Show more