Remove old code
28
.gitignore
vendored
|
@ -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
|
|
|
@ -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.
|
|
|
@ -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/
|
|
|
@ -1 +0,0 @@
|
||||||
/components/ui/**.tsx
|
|
|
@ -1,3 +0,0 @@
|
||||||
{
|
|
||||||
"extends": ["next/core-web-vitals", "next/typescript"]
|
|
||||||
}
|
|
36
app/.gitignore
vendored
|
@ -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
|
|
|
@ -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"]
|
|
|
@ -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.
|
|
|
@ -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. Exemple : 1 Agate-Blade Assassin 1 Agate-Blade Assassin 1 Agate-Blade Assassin 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. Exemple : 1 Agate-Blade Assassin 1 Agate-Blade Assassin 1 Agate-Blade Assassin 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>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -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>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -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>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -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 d’extension 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>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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}/>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -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}/>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -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}/>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -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>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
Before Width: | Height: | Size: 1.1 KiB |
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -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'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>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -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 d’Or du format est d’utiliser des cartes provenant du même set. Pour des raisons de niveau des cartes et/ou d’histoire, 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 d’un BSet si elle a été imprimée au moins une fois avec le symbole d’édition d’un 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 d’Ixalan, 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 n’y a pas de sideboard pour modifier son deck entre deux parties de BrawlSet. Cependant, comme certaines cartes permettent d’amener des cartes dans la partie depuis l’extérieur du jeu, les joueur·euses peuvent avoir un « wishboard ». Le nombre maximum de cartes d’un wishboard est 15, il n’y 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 d’un·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 d’une partie principale d’une sous-partie. Il n’y a pas de restriction sur l’identité colorée des cartes qu’un·e joueur·euse peut amener dans la partie depuis l’exté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 l’adversaire 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, l’aventure de Fae of Wishes pendant qu’il 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 l’effet 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 l’identité colorée du commandant.</li>
|
|
||||||
<li><strong>903.BS.9</strong> La règle de construction en singleton du Commander s’applique à 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 d’un deck de BrawlSet doivent faire partie du même BSet. Cette règle s’applique à la combinaison du deck et du wishboard. Cette règle s’applique 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 l’identité 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 s’applique 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 l’identité colorée de Karn est incolore et que les Wastes n’ont pas été éditées dans War of the Spark, Paul peut utiliser n’importe 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 d’un deck de BrawlSet est 60, il n’y 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 n’utilisent pas la règle des blessures de commandant, ainsi l’action décrite dans la règle 704.5v, qui fait perdre un·e joueur·euse qui a subi 21 blessures de combat ou plus d’un commandant ne s’applique pas. Ceci est une modification de la règle 704.5v.</li>
|
|
||||||
<li><strong>903.BS.15</strong> Au début d’un match, chaque joueur·euse doit déclarer son BSet, il ne peut pas être changé entre les parties d’un même match.</li>
|
|
||||||
<li><strong>903.BS.16</strong> Le format de BrawlSet utilise la règle du commander swap. Cela signifie que n’importe quel joueur·euse peut changer de commandant entre deux parties d’un match. Le nouveau commandant doit être une carte du deck, l’ancien commandant devient une carte normale du deck. Le commandant du deck reste secret jusqu’à ce qu’il soit révélé au début de la partie.</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
}
|
|
|
@ -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 }
|
|
|
@ -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 }
|
|
|
@ -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 }
|
|
|
@ -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 }
|
|
|
@ -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 }
|
|
|
@ -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 }
|
|
|
@ -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,
|
|
||||||
}
|
|
|
@ -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 }
|
|
|
@ -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,
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
}
|
|
|
@ -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 }
|
|
|
@ -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 }
|
|
|
@ -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 }
|
|
|
@ -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 }
|
|
|
@ -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 }
|
|
|
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
}
|
|
|
@ -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 }
|
|
|
@ -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 }
|
|
|
@ -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 }
|
|
|
@ -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,
|
|
||||||
}
|
|
|
@ -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 }
|
|
|
@ -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,
|
|
||||||
}
|
|
|
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -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 }
|
|
|
@ -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.
|
|
|
@ -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))
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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 "";
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
/** @type {import('next').NextConfig} */
|
|
||||||
const nextConfig = {};
|
|
||||||
|
|
||||||
export default nextConfig;
|
|
8464
app/package-lock.json
generated
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
/** @type {import('postcss-load-config').Config} */
|
|
||||||
const config = {
|
|
||||||
plugins: {
|
|
||||||
tailwindcss: {},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default config;
|
|
|
@ -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[]
|
|
||||||
}
|
|
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
Before Width: | Height: | Size: 67 KiB |
|
@ -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 |
|
@ -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 |
|
@ -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"
|
|
||||||
'';
|
|
||||||
}
|
|
|
@ -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;
|
|
|
@ -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)
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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()
|
|
|
@ -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)
|
|