├── .eslintrc.json ├── public ├── favicon.ico ├── vercel.svg └── next.svg ├── components ├── imgs │ ├── dogs.png │ ├── birds.png │ ├── richesCharm.png │ ├── unknowPokemon.png │ └── LemonMantis5571.png ├── ui │ ├── skeleton.tsx │ ├── label.tsx │ ├── input.tsx │ ├── toaster.tsx │ ├── checkbox.tsx │ ├── badge.tsx │ ├── tooltip.tsx │ ├── popover.tsx │ ├── avatar.tsx │ ├── button.tsx │ ├── card.tsx │ ├── accordion.tsx │ ├── calendar.tsx │ ├── table.tsx │ ├── dialog.tsx │ ├── use-toast.ts │ ├── select.tsx │ ├── toast.tsx │ ├── command.tsx │ ├── navigation-menu.tsx │ ├── dropdown-menu.tsx │ └── menubar.tsx ├── Moves.tsx ├── TeamCompendium │ ├── TeamMemberList.tsx │ ├── TeamList.tsx │ └── TeamMemberCard.tsx ├── Navbarmain.tsx ├── UserAvatar.tsx ├── LegendCalendar │ ├── BeastDayComponent.tsx │ ├── BirdDayComponent.tsx │ ├── Footer.tsx │ └── LegendCalendar.tsx ├── SkeletonCard.tsx ├── PokemonList.tsx ├── DropdownNavbar.tsx ├── ListItem.tsx ├── Pagination.tsx ├── Modals │ └── InfoModal.tsx ├── PokemonWrapper.tsx ├── TierSelect.tsx ├── PokemonCard.tsx ├── Labels.tsx ├── Icons.tsx └── Footer.tsx ├── postcss.config.js ├── next.config.js ├── lib ├── db.ts ├── types │ └── PokemonMembertypes.d.ts ├── pokepaste.generator.ts ├── utils.ts └── pokemon.generators.ts ├── app ├── pve │ ├── legendCalendar │ │ ├── page.tsx │ │ └── loading.tsx │ └── incomecheck │ │ ├── page.tsx │ │ ├── columns.tsx │ │ └── DataTable.tsx ├── pvp │ ├── compendium │ │ ├── loading.tsx │ │ ├── [teamId] │ │ │ ├── loading.tsx │ │ │ ├── page.tsx │ │ │ └── PokemonTeamCard.tsx │ │ ├── page.tsx │ │ └── CompendiumClient.tsx │ └── randomizer │ │ └── page.tsx ├── api │ ├── teams │ │ └── route.ts │ └── pokemon │ │ └── route.ts ├── actions │ ├── getTeams.ts │ └── getTeamById.ts ├── layout.tsx ├── globals.css └── page.tsx ├── hooks ├── useModal.ts ├── useTier.ts └── useShuffle.ts ├── components.json ├── .gitignore ├── tsconfig.json ├── data ├── items.mock.data.json └── trainers.mock.data.json ├── package.json ├── prisma └── schema.prisma ├── README.md └── tailwind.config.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LemonMantis5571/PokeMMO-Utilities/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /components/imgs/dogs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LemonMantis5571/PokeMMO-Utilities/HEAD/components/imgs/dogs.png -------------------------------------------------------------------------------- /components/imgs/birds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LemonMantis5571/PokeMMO-Utilities/HEAD/components/imgs/birds.png -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /components/imgs/richesCharm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LemonMantis5571/PokeMMO-Utilities/HEAD/components/imgs/richesCharm.png -------------------------------------------------------------------------------- /components/imgs/unknowPokemon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LemonMantis5571/PokeMMO-Utilities/HEAD/components/imgs/unknowPokemon.png -------------------------------------------------------------------------------- /components/imgs/LemonMantis5571.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LemonMantis5571/PokeMMO-Utilities/HEAD/components/imgs/LemonMantis5571.png -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | images: { 4 | domains: ['img.pokemondb.net', 'pokemon.gishan.cc'], 5 | } 6 | } 7 | 8 | module.exports = nextConfig 9 | -------------------------------------------------------------------------------- /lib/db.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from "@prisma/client"; 2 | 3 | declare global { 4 | var prisma: PrismaClient | undefined 5 | } 6 | 7 | const client = globalThis.prisma || new PrismaClient() 8 | if(process.env.NODE_ENV !== 'production' ) globalThis.prisma = client 9 | 10 | export default client; -------------------------------------------------------------------------------- /app/pve/legendCalendar/page.tsx: -------------------------------------------------------------------------------- 1 | import LegendCalendar from '@/components/LegendCalendar/LegendCalendar' 2 | 3 | export const fetchCache = 'force-no-store'; 4 | // revalidate everyday 5 | export const revalidate = 86400; 6 | 7 | const page = () => { 8 | return ( 9 | 10 | ) 11 | } 12 | 13 | export default page -------------------------------------------------------------------------------- /components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils" 2 | 3 | function Skeleton({ 4 | className, 5 | ...props 6 | }: React.HTMLAttributes) { 7 | return ( 8 |
12 | ) 13 | } 14 | 15 | export { Skeleton } 16 | -------------------------------------------------------------------------------- /app/pve/legendCalendar/loading.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Loading: React.FC = () => { 4 | return ( 5 |
6 |
7 |
8 | ); 9 | }; 10 | 11 | export default Loading; 12 | -------------------------------------------------------------------------------- /app/pvp/compendium/loading.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Loading: React.FC = () => { 4 | return ( 5 |
6 |
7 |
8 | ); 9 | }; 10 | 11 | export default Loading; 12 | -------------------------------------------------------------------------------- /app/pvp/compendium/[teamId]/loading.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Loading: React.FC = () => { 4 | return ( 5 |
6 |
7 |
8 | ); 9 | }; 10 | 11 | export default Loading; 12 | -------------------------------------------------------------------------------- /hooks/useModal.ts: -------------------------------------------------------------------------------- 1 | import { create } from 'zustand' 2 | 3 | interface ModalStore { 4 | isOpen: boolean; 5 | onOpen: () => void; 6 | onClose: () => void; 7 | } 8 | 9 | const useModal = create((set) => ({ 10 | isOpen: false, 11 | onOpen: () => set({ isOpen: true }), 12 | onClose: () => set({ isOpen: false }), 13 | })); 14 | 15 | export default useModal; -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "app/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true 11 | }, 12 | "aliases": { 13 | "components": "@/components", 14 | "utils": "@/lib/utils" 15 | } 16 | } -------------------------------------------------------------------------------- /hooks/useTier.ts: -------------------------------------------------------------------------------- 1 | import { create } from 'zustand' 2 | 3 | interface TierStore { 4 | tier: { 5 | value: string 6 | } 7 | updateTier: (tier: {value: string }) => void; 8 | } 9 | 10 | const useTier = create((set) => ({ 11 | tier: { 12 | value: '' 13 | }, 14 | updateTier: (tier) => set(() => ({ 15 | tier: { 16 | value: tier.value 17 | } 18 | })) 19 | })); 20 | 21 | export default useTier; -------------------------------------------------------------------------------- /app/pvp/compendium/[teamId]/page.tsx: -------------------------------------------------------------------------------- 1 | import getTeamById from '@/app/actions/getTeamById' 2 | import PokemonTeamCard from './PokemonTeamCard'; 3 | 4 | 5 | interface IParams { 6 | teamId?: string; 7 | } 8 | 9 | const page = async ({ params }: { params: IParams }) => { 10 | const team = await getTeamById(params); 11 | if (!team) return (<>
Team not found
) 12 | return ( 13 | 14 | ) 15 | } 16 | 17 | export default page -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | .env 30 | .env* 31 | 32 | # vercel 33 | .vercel 34 | 35 | # typescript 36 | *.tsbuildinfo 37 | next-env.d.ts 38 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /hooks/useShuffle.ts: -------------------------------------------------------------------------------- 1 | import { getRandomPokemonsWithMoves } from "@/lib/pokemon.generators"; 2 | import data from "@/data/pokemmo.mock.data.json" 3 | import randomItem from "@/data/items.mock.data.json" 4 | import { Pokemon } from "@/lib/utils"; 5 | export const ShufflePokemons = async (tier: string) => { 6 | const pokemons: Pokemon[] = data.Pokedex.map((data) => data.pokemon); 7 | const selectedItems: string[] = randomItem.Items.map((item) => item); 8 | const maxPokemons = 6; 9 | const randomPokemonWithMoves = await getRandomPokemonsWithMoves(pokemons, maxPokemons, selectedItems, tier); 10 | return randomPokemonWithMoves; 11 | } -------------------------------------------------------------------------------- /lib/types/PokemonMembertypes.d.ts: -------------------------------------------------------------------------------- 1 | import { Evs, Moves, Team } from "@prisma/client" 2 | 3 | export type PokemonMember = { 4 | id: number, 5 | name: string, 6 | item: string | null, 7 | ability: string, 8 | nature: string, 9 | type: string, 10 | type2?: string | null, 11 | evs: Evs[], 12 | moves: Moves[], 13 | } 14 | 15 | export type PokePaste = { 16 | name: string, 17 | description?: string | null, 18 | tier?: string | null, 19 | members: PokemonMember[] 20 | } 21 | 22 | 23 | export type PokemonTeam = { 24 | teams: ({ 25 | members: { 26 | name: string 27 | }[] 28 | } & Team)[] | null; 29 | count: number; 30 | } -------------------------------------------------------------------------------- /components/Moves.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react' 2 | import { Button } from './ui/button' 3 | import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@radix-ui/react-tooltip' 4 | 5 | interface MovesProps { 6 | move: string; 7 | className?: string; 8 | } 9 | 10 | const Moves: FC = ({ move, className }) => { 11 | return ( 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | ) 20 | } 21 | 22 | export default Moves -------------------------------------------------------------------------------- /components/TeamCompendium/TeamMemberList.tsx: -------------------------------------------------------------------------------- 1 | import { PokemonMember } from '@/lib/types/PokemonMembertypes' 2 | import { FC } from 'react' 3 | import TeamMemberCard from './TeamMemberCard' 4 | 5 | interface TeamMemberListProps { 6 | team: { 7 | name: string, 8 | description?: string | null, 9 | tier?: string | null, 10 | members: PokemonMember[] 11 | } 12 | } 13 | 14 | const TeamMemberList: FC = ({ team }) => { 15 | return ( 16 | team.members.map((pokemon, index) => { 17 | return ( 18 | 19 | ) 20 | }) 21 | ) 22 | } 23 | 24 | export default TeamMemberList -------------------------------------------------------------------------------- /app/api/teams/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from "next/server"; 2 | import prisma from '@/lib/db'; 3 | 4 | export async function POST( 5 | request: Request 6 | ) { 7 | const body = await request.json(); 8 | 9 | const { 10 | name, 11 | description, 12 | members, 13 | tier 14 | } = body; 15 | 16 | 17 | Object.keys(body).forEach((value: any) => { 18 | if (!body[value]) { 19 | NextResponse.error(); 20 | } 21 | }); 22 | 23 | const team = await prisma.team.create({ 24 | data: { 25 | name, 26 | description, 27 | members, 28 | Tier: tier 29 | } 30 | }); 31 | 32 | return NextResponse.json(team, { status: 200 }); 33 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "bundler", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "plugins": [ 18 | { 19 | "name": "next" 20 | } 21 | ], 22 | "paths": { 23 | "@/*": ["./*"] 24 | } 25 | }, 26 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 27 | "exclude": ["node_modules"] 28 | } 29 | -------------------------------------------------------------------------------- /components/Navbarmain.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import { FC } from 'react' 3 | 4 | import { Avatar, AvatarFallback, AvatarImage } from './ui/avatar' 5 | import Labels from './Labels' 6 | import { isMobile } from 'react-device-detect'; 7 | import DropdownNavbar from './DropdownNavbar'; 8 | import UserAvatar from './UserAvatar'; 9 | 10 | interface NavbarmainProps { 11 | 12 | } 13 | 14 | const Navbarmain: FC = ({ }) => { 15 | return ( 16 | ) 22 | } 23 | 24 | export default Navbarmain -------------------------------------------------------------------------------- /app/pvp/compendium/page.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react' 2 | import CompendiumClient from './CompendiumClient' 3 | import getTeams from '@/app/actions/getTeams' 4 | 5 | export const dynamic = 'force-dynamic' 6 | 7 | interface IParams { 8 | searchParams: { 9 | page: string 10 | } 11 | 12 | } 13 | 14 | const page: FC = async ({ searchParams }) => { 15 | 16 | let page = parseInt(searchParams.page, 10); 17 | page = !page || page < 1 ? 1 : page; 18 | const perPage = 4; 19 | 20 | const data = await getTeams(perPage, page); 21 | 22 | if (!data) { 23 | return
No Teams to show yet
24 | } 25 | 26 | const { teams, count } = data; 27 | 28 | return ( 29 | 30 | ) 31 | } 32 | 33 | export default page -------------------------------------------------------------------------------- /components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as LabelPrimitive from "@radix-ui/react-label" 5 | import { cva, type VariantProps } from "class-variance-authority" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const labelVariants = cva( 10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" 11 | ) 12 | 13 | const Label = React.forwardRef< 14 | React.ElementRef, 15 | React.ComponentPropsWithoutRef & 16 | VariantProps 17 | >(({ className, ...props }, ref) => ( 18 | 23 | )) 24 | Label.displayName = LabelPrimitive.Root.displayName 25 | 26 | export { Label } 27 | -------------------------------------------------------------------------------- /app/pvp/randomizer/page.tsx: -------------------------------------------------------------------------------- 1 | import PokemonWrapper from "@/components/PokemonWrapper"; 2 | import { ShufflePokemons } from "@/hooks/useShuffle"; 3 | 4 | export interface MovepoolItem { 5 | [move: string]: string[]; 6 | } 7 | 8 | const page = async () => { 9 | const randomPokemonWithMoves = await ShufflePokemons('ALL'); 10 | const ShuffledPokemons = randomPokemonWithMoves.map((pokemon) => { 11 | return { 12 | name: pokemon.name, 13 | number: pokemon.number, 14 | abilities: pokemon.abilities, 15 | types: pokemon.types, 16 | tier: pokemon.tier, 17 | items: pokemon.items, 18 | moves: pokemon.moves, 19 | newMoves: pokemon.newMoves 20 | }; 21 | }); 22 | 23 | 24 | return ( 25 | 26 | ) 27 | } 28 | 29 | export default page -------------------------------------------------------------------------------- /components/UserAvatar.tsx: -------------------------------------------------------------------------------- 1 | import { Avatar, AvatarFallback, AvatarImage } from "./ui/avatar"; 2 | 3 | const UserAvatar = () => { 4 | return (<> 5 | 6 | 7 | 8 | 9 | PK 10 | 11 | 12 | 13 | 14 | 15 | GH 16 | ); 17 | } 18 | 19 | export default UserAvatar; -------------------------------------------------------------------------------- /components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | export interface InputProps 6 | extends React.InputHTMLAttributes {} 7 | 8 | const Input = React.forwardRef( 9 | ({ className, type, ...props }, ref) => { 10 | return ( 11 | 20 | ) 21 | } 22 | ) 23 | Input.displayName = "Input" 24 | 25 | export { Input } 26 | -------------------------------------------------------------------------------- /app/actions/getTeams.ts: -------------------------------------------------------------------------------- 1 | import prisma from '@/lib/db'; 2 | 3 | export default async function getTeams(Perpage: number, Page: number) { 4 | try { 5 | const teams = await prisma.team.findMany({ 6 | include: { 7 | members: { 8 | select: { 9 | name: true 10 | } 11 | } 12 | }, 13 | take: Perpage, 14 | skip: (Page - 1) * Perpage, 15 | orderBy: { 16 | createdAt: 'desc' 17 | } 18 | }); 19 | 20 | const count = await prisma.team.count(); 21 | 22 | if (!teams) { 23 | return null; 24 | } 25 | 26 | if (teams) { 27 | return { 28 | teams, 29 | count 30 | } 31 | } 32 | 33 | } catch (error) { 34 | console.log(error); 35 | } 36 | 37 | return null; 38 | } -------------------------------------------------------------------------------- /components/ui/toaster.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { 4 | Toast, 5 | ToastClose, 6 | ToastDescription, 7 | ToastProvider, 8 | ToastTitle, 9 | ToastViewport, 10 | } from "@/components/ui/toast" 11 | import { useToast } from "@/components/ui/use-toast" 12 | 13 | export function Toaster() { 14 | const { toasts } = useToast() 15 | 16 | return ( 17 | 18 | {toasts.map(function ({ id, title, description, action, ...props }) { 19 | return ( 20 | 21 |
22 | {title && {title}} 23 | {description && ( 24 | {description} 25 | )} 26 |
27 | {action} 28 | 29 |
30 | ) 31 | })} 32 | 33 |
34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /app/api/pokemon/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from "next/server"; 2 | import prisma from '@/lib/db'; 3 | 4 | export async function POST( 5 | request: Request 6 | ) { 7 | const body = await request.json(); 8 | 9 | const { 10 | name, 11 | item, 12 | nature, 13 | type, 14 | type2, 15 | ability 16 | } = body; 17 | 18 | Object.keys(body).forEach((value: any) => { 19 | if (!body[value]) { 20 | NextResponse.error(); 21 | } 22 | }); 23 | 24 | try { 25 | const pokemon = await prisma.pokemon.create({ 26 | data: { 27 | name, 28 | item, 29 | nature, 30 | type, 31 | type2, 32 | ability 33 | } 34 | }); 35 | 36 | return NextResponse.json(pokemon, { status: 200 }); 37 | 38 | } catch (error) { 39 | console.log(error); 40 | return NextResponse.error(); 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /app/actions/getTeamById.ts: -------------------------------------------------------------------------------- 1 | import prisma from '@/lib/db'; 2 | 3 | interface Iparams { 4 | teamId?: string 5 | } 6 | 7 | export default async function getTeamById( 8 | params: Iparams 9 | ) { 10 | try { 11 | const { teamId } = params; 12 | const team = await prisma.team.findUnique({ 13 | where: { 14 | id: teamId 15 | }, 16 | include: { 17 | members: { 18 | select: { 19 | id: true, 20 | name: true, 21 | item: true, 22 | ability: true, 23 | nature: true, 24 | type: true, 25 | type2: true, 26 | evs: true, 27 | moves: true, 28 | } 29 | }, 30 | } 31 | }); 32 | 33 | if (!team) { 34 | return null; 35 | } 36 | 37 | return team; 38 | 39 | } catch (error) { 40 | console.log(error); 41 | } 42 | } -------------------------------------------------------------------------------- /components/LegendCalendar/BeastDayComponent.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @next/next/no-img-element */ 2 | 'use client'; 3 | import { getPokemonForDate } from "@/lib/utils"; 4 | import { DayContentProps} from "react-day-picker"; 5 | 6 | const pokemons = ['Entei', 'Suicune', 'Raikou']; 7 | 8 | const BeastDayComponent = (props: DayContentProps) => { 9 | const pokemon = getPokemonForDate(props.date, pokemons); 10 | const getPokemonIcon = (pokemonName: string, day: number) => ( 11 | <> 12 | pokemon 19 | {day} 20 | 21 | ); 22 | 23 | return ( 24 | 25 | {getPokemonIcon(pokemon.pokemonName, pokemon.day)} 26 | 27 | ); 28 | 29 | } 30 | 31 | export default BeastDayComponent; -------------------------------------------------------------------------------- /components/LegendCalendar/BirdDayComponent.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @next/next/no-img-element */ 2 | 'use client'; 3 | import { getPokemonForDate } from "@/lib/utils"; 4 | import { DayContentProps, } from "react-day-picker"; 5 | 6 | const pokemons = ['Zapdos', 'Moltres', 'Articuno']; 7 | 8 | const BirdDayComponent = (props: DayContentProps) => { 9 | const pokemon = getPokemonForDate(props.date, pokemons); 10 | const getPokemonIcon = (pokemonName: string, day: number) => ( 11 | <> 12 | pokemon 19 | {day} 20 | 21 | ); 22 | 23 | 24 | return ( 25 | 26 | {getPokemonIcon(pokemon.pokemonName, pokemon.day)} 27 | 28 | ); 29 | 30 | } 31 | 32 | export default BirdDayComponent -------------------------------------------------------------------------------- /components/SkeletonCard.tsx: -------------------------------------------------------------------------------- 1 | import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "./ui/card"; 2 | import { Skeleton } from "./ui/skeleton"; 3 | 4 | const SkeletonCard = () => { 5 | return (
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
); 23 | } 24 | 25 | export default SkeletonCard; -------------------------------------------------------------------------------- /lib/pokepaste.generator.ts: -------------------------------------------------------------------------------- 1 | 2 | import { PokePaste } from "./types/PokemonMembertypes"; 3 | 4 | export const convertToPokepaste = (team: PokePaste) => { 5 | let pokepaste = ''; 6 | 7 | 8 | team.members.forEach((pokemon) => { 9 | pokepaste += `${pokemon.name} @ ${pokemon.item}\n`; 10 | pokepaste += `Ability: ${pokemon.ability}\n`; 11 | pokepaste += `Level: 50\n`; 12 | pokepaste += `EVs: `; 13 | pokepaste += 14 | `${pokemon.evs[0].hp ? `${pokemon.evs[0].hp} HP / ` : ''}${pokemon.evs[0].atk ? `${pokemon.evs[0].atk} Atk / ` : ''}${pokemon.evs[0].def ? `${pokemon.evs[0].def} Def / ` : ''}${pokemon.evs[0].spaatk ? `${pokemon.evs[0].spaatk} SpA / ` : ''}${pokemon.evs[0].spd ? `${pokemon.evs[0].spd} SpD / ` : ''}${pokemon.evs[0].spe ? `${pokemon.evs[0].spe} Spe` : ''}\n`; 15 | pokepaste += `${pokemon.nature} Nature\n`; 16 | pokepaste += `- ${pokemon.moves[0].name}\n`; 17 | pokepaste += `- ${pokemon.moves[1].name}\n`; 18 | pokepaste += `- ${pokemon.moves[2].name}\n`; 19 | pokepaste += `- ${pokemon.moves[3].name}\n\n`; 20 | }); 21 | 22 | return pokepaste; 23 | 24 | }; -------------------------------------------------------------------------------- /components/ui/checkbox.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as CheckboxPrimitive from "@radix-ui/react-checkbox" 5 | import { Check } from "lucide-react" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const Checkbox = React.forwardRef< 10 | React.ElementRef, 11 | React.ComponentPropsWithoutRef 12 | >(({ className, ...props }, ref) => ( 13 | 21 | 24 | 25 | 26 | 27 | )) 28 | Checkbox.displayName = CheckboxPrimitive.Root.displayName 29 | 30 | export { Checkbox } 31 | -------------------------------------------------------------------------------- /lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | 5 | export function cn(...inputs: ClassValue[]) { 6 | return twMerge(clsx(inputs)) 7 | } 8 | 9 | export interface Pokemon { 10 | number: string; // yeah I should modify the json to be number instead of string. Either that or Cast to int lol 11 | name: string; 12 | types: string[]; 13 | abilities: string[]; 14 | tier: string; 15 | } 16 | 17 | 18 | // Legendary Calendar 19 | 20 | 21 | export const getPokemonForDate = (date: Date, pokemons: string[]) => { 22 | const day = date.getDate(); 23 | const month = date.getMonth(); 24 | const pokemonIndex = month % pokemons.length; 25 | const pokemonName = pokemons[pokemonIndex]; 26 | return { pokemonName, day }; 27 | }; 28 | 29 | 30 | export const getLegendaryPokemonForMonth = ( 31 | legendaryBeasts: string[], 32 | legendaryBirds: string[], 33 | beastMonth: number | undefined, 34 | birdMonth: number | undefined) => { 35 | 36 | const currenBeast = legendaryBeasts[beastMonth as number % legendaryBeasts.length] 37 | const currentBird = legendaryBirds[birdMonth as number % legendaryBirds.length]; 38 | 39 | return { currenBeast, currentBird }; 40 | }; -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import Navbarmain from '@/components/Navbarmain' 2 | import { Analytics } from '@vercel/analytics/react'; 3 | import './globals.css' 4 | import type { Metadata } from 'next' 5 | import { Inter } from 'next/font/google' 6 | import { Toaster } from '@/components/ui/toaster'; 7 | 8 | 9 | const inter = Inter({ subsets: ['latin'] }) 10 | 11 | export const metadata: Metadata = { 12 | metadataBase: new URL('https://poke-mmo-utilities.vercel.app'), 13 | title: 'PokeMMO Utilities', 14 | description: 'Utilities for PokeMMO', 15 | icons: { 16 | icon: '/favicon.ico' 17 | }, 18 | verification: { 19 | google: 'Ppil16eQLID5WGM_z4roBczMWM6I5Od2CFwYz7hnvK0' 20 | }, 21 | alternates: { 22 | canonical: './' 23 | } 24 | 25 | } 26 | 27 | export default function RootLayout({ 28 | children, 29 | }: { 30 | children: React.ReactNode 31 | }) { 32 | return ( 33 | 34 | 35 | 36 | 37 | 38 | 39 |
40 | {children} 41 |
42 | 43 | 44 | 45 | 46 | ) 47 | } 48 | -------------------------------------------------------------------------------- /components/ui/badge.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { cva, type VariantProps } from "class-variance-authority" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | const badgeVariants = cva( 7 | "inline-flex items-center rounded-full 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", 8 | { 9 | variants: { 10 | variant: { 11 | default: 12 | "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", 13 | secondary: 14 | "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", 15 | destructive: 16 | "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", 17 | outline: "text-foreground", 18 | }, 19 | }, 20 | defaultVariants: { 21 | variant: "default", 22 | }, 23 | } 24 | ) 25 | 26 | export interface BadgeProps 27 | extends React.HTMLAttributes, 28 | VariantProps {} 29 | 30 | function Badge({ className, variant, ...props }: BadgeProps) { 31 | return ( 32 |
33 | ) 34 | } 35 | 36 | export { Badge, badgeVariants } 37 | -------------------------------------------------------------------------------- /components/ui/tooltip.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as TooltipPrimitive from "@radix-ui/react-tooltip" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const TooltipProvider = TooltipPrimitive.Provider 9 | 10 | const Tooltip = TooltipPrimitive.Root 11 | 12 | const TooltipTrigger = TooltipPrimitive.Trigger 13 | 14 | const TooltipContent = React.forwardRef< 15 | React.ElementRef, 16 | React.ComponentPropsWithoutRef 17 | >(({ className, sideOffset = 4, ...props }, ref) => ( 18 | 27 | )) 28 | TooltipContent.displayName = TooltipPrimitive.Content.displayName 29 | 30 | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } 31 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/ui/popover.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as PopoverPrimitive from "@radix-ui/react-popover" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const Popover = PopoverPrimitive.Root 9 | 10 | const PopoverTrigger = PopoverPrimitive.Trigger 11 | 12 | const PopoverContent = React.forwardRef< 13 | React.ElementRef, 14 | React.ComponentPropsWithoutRef 15 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( 16 | 17 | 27 | 28 | )) 29 | PopoverContent.displayName = PopoverPrimitive.Content.displayName 30 | 31 | export { Popover, PopoverTrigger, PopoverContent } 32 | -------------------------------------------------------------------------------- /components/PokemonList.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react' 2 | import PokemonCard from './PokemonCard'; 3 | import SkeletonCard from './SkeletonCard'; 4 | 5 | interface PokemonListProps { 6 | ShuffledPokemons: { 7 | name: string; 8 | number: string; 9 | abilities: string[]; 10 | types: string[]; 11 | tier: string; 12 | items: string; 13 | moves: [string, string[]][]; 14 | newMoves: [string, { name: string; type: string; }][] | null; 15 | }[]; 16 | } 17 | 18 | const PokemonList: FC = ({ ShuffledPokemons }) => { 19 | return (
20 | {ShuffledPokemons ? ShuffledPokemons.map((pokemon, index) => { 21 | return ( 22 | 33 | ); 34 | }) : [...Array(6)].map((_, index) => ( 35 | 36 | ))} 37 |
) 38 | } 39 | 40 | export default PokemonList -------------------------------------------------------------------------------- /components/LegendCalendar/Footer.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @next/next/no-img-element */ 2 | import { getLegendaryPokemonForMonth } from '@/lib/utils'; 3 | import { FC } from 'react' 4 | 5 | interface FooterProps { 6 | birdMonth: number | undefined; 7 | beastMonth: number | undefined; 8 | } 9 | 10 | const Footer: FC = ({ birdMonth, beastMonth }) => { 11 | const legendary = getLegendaryPokemonForMonth(['Entei', 'Suicune', 'Raikou'], ['Zapdos', 'Moltres', 'Articuno'], beastMonth, birdMonth); 12 | const { currenBeast, currentBird } = legendary; 13 | 14 | 15 | return (
16 |
17 |
18 |

{currentBird}

19 | legendaryBird 20 |
21 |
22 |

{currenBeast}

23 | legendaryBeast 24 |
25 | 26 |
27 |
) 28 | } 29 | 30 | export default Footer -------------------------------------------------------------------------------- /app/pve/incomecheck/page.tsx: -------------------------------------------------------------------------------- 1 | import { DataTable } from "./DataTable"; 2 | import trainers from "@/data/trainers.mock.data.json"; 3 | import { IncomeCheck, columns } from "./columns"; 4 | 5 | 6 | async function getData(): Promise { 7 | 8 | // Hard Way to do it with flatMap and inneficent json structure. 9 | 10 | // const regions: Array = ["Kanto", "Hoenn", "Johto", "Sinnoh", "Teselia"]; 11 | // const mappedData: IncomeCheck[] = regions.flatMap((region) => { 12 | // return trainers.GymLeaders[region].map((trainer) => { 13 | // return { 14 | // trainers: { 15 | // name: trainer.name, 16 | // city: trainer.city, 17 | // income: trainer.income, 18 | // } 19 | // }; 20 | // }); 21 | // }); 22 | 23 | // return mappedData; 24 | 25 | // Easy way 26 | 27 | const data: IncomeCheck[] = trainers.GymLeaders.map((trainer) => { 28 | return { 29 | trainers: { 30 | name: trainer.name, 31 | city: trainer.city, 32 | income: trainer.income, 33 | region: trainer.region, 34 | }, 35 | }; 36 | }); 37 | 38 | return data; 39 | } 40 | 41 | 42 | 43 | 44 | export default async function Page() { 45 | const data = await getData(); 46 | 47 | 48 | return ( 49 |
50 | 51 |
52 | ); 53 | } -------------------------------------------------------------------------------- /app/pve/incomecheck/columns.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { Checkbox } from "@/components/ui/checkbox"; 3 | import { ColumnDef } from "@tanstack/react-table"; 4 | 5 | export interface IncomeCheck { 6 | trainers: Trainers; 7 | } 8 | 9 | export interface Trainers { 10 | name: string; 11 | city: string; 12 | income: number; 13 | region?: string; 14 | } 15 | 16 | 17 | export const columns: ColumnDef[] = [ 18 | { 19 | id: 'trainers.name', 20 | header: ({ table }) => ( 21 | table.toggleAllPageRowsSelected(!!value)} 24 | aria-label="Select all rows" 25 | /> 26 | ), 27 | cell: ({ row }) => ( 28 | row.toggleSelected(!!value)} 31 | aria-label="Select row" 32 | /> 33 | 34 | ), 35 | enableSorting: false, 36 | enableHiding: false, 37 | }, 38 | { 39 | id: 'name', 40 | accessorKey: 'trainers.name', 41 | header: 'Trainers', 42 | }, 43 | { 44 | id: 'income', 45 | accessorKey: 'trainers.income', 46 | header: 'Base Income', 47 | }, 48 | { 49 | id: 'city', 50 | accessorKey: 'trainers.city', 51 | header: 'City', 52 | }, 53 | { 54 | id: 'region', 55 | accessorKey: 'trainers.region', 56 | header: 'Region', 57 | 58 | } 59 | 60 | ] -------------------------------------------------------------------------------- /components/DropdownNavbar.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react' 2 | import { 3 | DropdownMenu, 4 | DropdownMenuContent, 5 | DropdownMenuItem, 6 | DropdownMenuLabel, 7 | DropdownMenuSeparator, 8 | DropdownMenuTrigger, 9 | } from "./ui/dropdown-menu" 10 | import { Button } from './ui/button' 11 | import Link from 'next/link' 12 | 13 | interface DropdownNavbarProps { 14 | label: string 15 | } 16 | 17 | const DropdownNavbar: FC = ({ label }) => { 18 | return ( 19 | 20 | 21 | 22 | 23 | 24 | PvP Utilities 25 | 26 | 27 | Randomizer 28 | PvP Compendium 29 | 30 | 31 | PvE Utilities 32 | 33 | 34 | Gym Rerun 35 | Income Check 36 | Legendary Calendar 37 | 38 | ) 39 | } 40 | 41 | export default DropdownNavbar -------------------------------------------------------------------------------- /components/ui/avatar.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as AvatarPrimitive from "@radix-ui/react-avatar" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const Avatar = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, ...props }, ref) => ( 12 | 20 | )) 21 | Avatar.displayName = AvatarPrimitive.Root.displayName 22 | 23 | const AvatarImage = React.forwardRef< 24 | React.ElementRef, 25 | React.ComponentPropsWithoutRef 26 | >(({ className, ...props }, ref) => ( 27 | 32 | )) 33 | AvatarImage.displayName = AvatarPrimitive.Image.displayName 34 | 35 | const AvatarFallback = React.forwardRef< 36 | React.ElementRef, 37 | React.ComponentPropsWithoutRef 38 | >(({ className, ...props }, ref) => ( 39 | 47 | )) 48 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName 49 | 50 | export { Avatar, AvatarImage, AvatarFallback } 51 | -------------------------------------------------------------------------------- /components/ListItem.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import { cn } from "@/lib/utils" 3 | import { 4 | NavigationMenuLink, 5 | } from "@/components/ui/navigation-menu" 6 | import { LucideIcon } from "lucide-react" 7 | import React from "react" 8 | 9 | interface ListItemProps extends React.ComponentPropsWithoutRef<"a"> { 10 | customIcon: LucideIcon 11 | isBlank?: boolean 12 | } 13 | 14 | const ListItem = React.forwardRef< 15 | React.ElementRef<"a">, 16 | ListItemProps 17 | >(({ className, title, children, isBlank, customIcon: Icon, ...props }, ref) => { 18 | 19 | return ( 20 |
  • 21 | 22 | 31 |
    {title}
    32 |
    33 | 34 |

    35 | {children} 36 |

    37 | 38 |
    39 | 40 |
    41 |
    42 |
  • 43 | ) 44 | }) 45 | ListItem.displayName = "ListItem" 46 | 47 | export default ListItem 48 | -------------------------------------------------------------------------------- /data/items.mock.data.json: -------------------------------------------------------------------------------- 1 | { 2 | "Items": [ 3 | "damp-rock", 4 | "choice-specs", 5 | "mental-herb", 6 | "leftovers", 7 | "life-orb", 8 | "choice-scarf", 9 | "rocky-helmet", 10 | "light-clay", 11 | "muscle-band", 12 | "occa-berry", 13 | "sitrus-berry", 14 | "lum-berry", 15 | "custap-berry", 16 | "choice-band", 17 | "iron-ball", 18 | "black-belt", 19 | "petaya-berry", 20 | "mind-plate", 21 | "focus-sash", 22 | "flame-plate", 23 | "miracle-seed", 24 | "razor-claw", 25 | "absorb-bulb", 26 | "sitrus-berry", 27 | "lum-berry", 28 | "aspear-berry", 29 | "cheri-berry", 30 | "chople-berry", 31 | "rindo-berry", 32 | "kebia-berry", 33 | "shuca-berry", 34 | "coba-berry", 35 | "tanga-berry", 36 | "babiri-berry", 37 | "haban-berry", 38 | "chesto-berry", 39 | "air-balloon", 40 | "passho-berry", 41 | "silverpowder", 42 | "aguav-berry", 43 | "big-root", 44 | "sharp-beak", 45 | "charcoal", 46 | "toxic-orb", 47 | "assault-vest", 48 | "bug-gem", 49 | "dark-gem", 50 | "dragon-gem", 51 | "electric-gem", 52 | "fighting-gem", 53 | "fire-gem", 54 | "flying-gem", 55 | "ghost-gem", 56 | "ground-gem", 57 | "grass-gem", 58 | "ice-gem", 59 | "normal-gem", 60 | "psychic-gem", 61 | "rock-gem", 62 | "steel-gem", 63 | "water-gem", 64 | "icy-rock", 65 | "smooth-rock", 66 | "heat-rock", 67 | "eviolite", 68 | "black-sludge", 69 | "blackglasses", 70 | "red-card", 71 | "shell-bell", 72 | "sticky-barb", 73 | "twistedspoon" 74 | ] 75 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pvp-randomizer", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint", 10 | "postinstall": "prisma generate" 11 | }, 12 | "dependencies": { 13 | "@pkmn/data": "^0.7.48", 14 | "@pkmn/dex": "^0.7.48", 15 | "@prisma/client": "^5.5.2", 16 | "@radix-ui/react-accordion": "^1.2.11", 17 | "@radix-ui/react-avatar": "^1.0.3", 18 | "@radix-ui/react-checkbox": "^1.0.4", 19 | "@radix-ui/react-dialog": "^1.0.4", 20 | "@radix-ui/react-dropdown-menu": "^2.0.6", 21 | "@radix-ui/react-label": "^2.0.2", 22 | "@radix-ui/react-menubar": "^1.0.3", 23 | "@radix-ui/react-navigation-menu": "^1.1.3", 24 | "@radix-ui/react-popover": "^1.0.6", 25 | "@radix-ui/react-select": "^1.2.2", 26 | "@radix-ui/react-slot": "^1.2.3", 27 | "@radix-ui/react-toast": "^1.1.5", 28 | "@radix-ui/react-tooltip": "^1.0.6", 29 | "@smogon/calc": "^0.7.0", 30 | "@tanstack/react-table": "^8.10.3", 31 | "@types/node": "20.4.4", 32 | "@types/react": "18.2.15", 33 | "@types/react-dom": "18.2.7", 34 | "@vercel/analytics": "^1.0.2", 35 | "@vercel/postgres": "^0.5.1", 36 | "autoprefixer": "10.4.14", 37 | "axios": "^1.5.0", 38 | "class-variance-authority": "^0.7.0", 39 | "clsx": "^2.0.0", 40 | "cmdk": "^0.2.0", 41 | "date-fns": "^2.30.0", 42 | "eslint": "8.45.0", 43 | "eslint-config-next": "^13.4.19", 44 | "framer-motion": "^12.12.1", 45 | "lucide-react": "^0.274.0", 46 | "next": "^13.4.19", 47 | "postcss": "8.4.27", 48 | "react": "^18.2.0", 49 | "react-day-picker": "^8.10.1", 50 | "react-device-detect": "^2.2.3", 51 | "react-dom": "^18.2.0", 52 | "tailwind-merge": "^1.14.0", 53 | "tailwindcss": "3.3.3", 54 | "tailwindcss-animate": "^1.0.7", 55 | "typescript": "5.1.6", 56 | "zustand": "^4.4.1" 57 | }, 58 | "devDependencies": { 59 | "prisma": "^5.5.2" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { Slot } from "@radix-ui/react-slot" 3 | import { cva, type VariantProps } from "class-variance-authority" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", 9 | { 10 | variants: { 11 | variant: { 12 | default: "bg-primary text-primary-foreground hover:bg-primary/90", 13 | destructive: 14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90", 15 | outline: 16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground", 17 | secondary: 18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80", 19 | ghost: "hover:bg-accent hover:text-accent-foreground", 20 | link: "text-primary underline-offset-4 hover:underline", 21 | }, 22 | size: { 23 | default: "h-10 px-4 py-2", 24 | sm: "h-9 rounded-md px-3", 25 | lg: "h-11 rounded-md px-8", 26 | icon: "h-10 w-10", 27 | }, 28 | }, 29 | defaultVariants: { 30 | variant: "default", 31 | size: "default", 32 | }, 33 | } 34 | ) 35 | 36 | export interface ButtonProps 37 | extends React.ButtonHTMLAttributes, 38 | VariantProps { 39 | asChild?: boolean 40 | } 41 | 42 | const Button = React.forwardRef( 43 | ({ className, variant, size, asChild = false, ...props }, ref) => { 44 | const Comp = asChild ? Slot : "button" 45 | return ( 46 | 51 | ) 52 | } 53 | ) 54 | Button.displayName = "Button" 55 | 56 | export { Button, buttonVariants } 57 | -------------------------------------------------------------------------------- /components/Pagination.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { FC } from 'react' 3 | 4 | import Link from 'next/link'; 5 | import { Button } from './ui/button'; 6 | 7 | interface PaginationProps { 8 | prevPage: number; 9 | nextPage: number; 10 | currentPage: number; 11 | totalPages: number; 12 | } 13 | 14 | const Pagination: FC = ({ prevPage, nextPage, currentPage, totalPages }) => { 15 | const pageNumbers = []; 16 | const offsetNumber = 3; 17 | for (let i = currentPage - offsetNumber; i <= currentPage + offsetNumber; i++) { 18 | if (i >= 1 && i <= totalPages) { 19 | pageNumbers.push(i); 20 | } 21 | } 22 | 23 | return ( 24 |
    25 | 29 | 32 | 33 | 34 | {pageNumbers.map((number, index) => { 35 | return ( 36 | 39 | 45 | 46 | 47 | ) 48 | })} 49 | 53 | 56 | 57 |
    58 | ) 59 | 60 | } 61 | 62 | export default Pagination -------------------------------------------------------------------------------- /components/ui/card.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | const Card = React.forwardRef< 6 | HTMLDivElement, 7 | React.HTMLAttributes 8 | >(({ className, ...props }, ref) => ( 9 |
    17 | )) 18 | Card.displayName = "Card" 19 | 20 | const CardHeader = React.forwardRef< 21 | HTMLDivElement, 22 | React.HTMLAttributes 23 | >(({ className, ...props }, ref) => ( 24 |
    29 | )) 30 | CardHeader.displayName = "CardHeader" 31 | 32 | const CardTitle = React.forwardRef< 33 | HTMLParagraphElement, 34 | React.HTMLAttributes 35 | >(({ className, ...props }, ref) => ( 36 |

    44 | )) 45 | CardTitle.displayName = "CardTitle" 46 | 47 | const CardDescription = React.forwardRef< 48 | HTMLDivElement, 49 | React.HTMLAttributes 50 | >(({ className, ...props }, ref) => ( 51 |
    56 | )) 57 | CardDescription.displayName = "CardDescription" 58 | 59 | const CardContent = React.forwardRef< 60 | HTMLDivElement, 61 | React.HTMLAttributes 62 | >(({ className, ...props }, ref) => ( 63 |
    64 | )) 65 | CardContent.displayName = "CardContent" 66 | 67 | const CardFooter = React.forwardRef< 68 | HTMLDivElement, 69 | React.HTMLAttributes 70 | >(({ className, ...props }, ref) => ( 71 |
    76 | )) 77 | CardFooter.displayName = "CardFooter" 78 | 79 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } 80 | -------------------------------------------------------------------------------- /components/ui/accordion.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as AccordionPrimitive from "@radix-ui/react-accordion" 5 | import { ChevronDown } from "lucide-react" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const Accordion = AccordionPrimitive.Root 10 | 11 | const AccordionItem = React.forwardRef< 12 | React.ElementRef, 13 | React.ComponentPropsWithoutRef 14 | >(({ className, ...props }, ref) => ( 15 | 20 | )) 21 | AccordionItem.displayName = "AccordionItem" 22 | 23 | const AccordionTrigger = React.forwardRef< 24 | React.ElementRef, 25 | React.ComponentPropsWithoutRef 26 | >(({ className, children, ...props }, ref) => ( 27 | 28 | svg]:rotate-180", 32 | className 33 | )} 34 | {...props} 35 | > 36 | {children} 37 | 38 | 39 | 40 | )) 41 | AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName 42 | 43 | const AccordionContent = React.forwardRef< 44 | React.ElementRef, 45 | React.ComponentPropsWithoutRef 46 | >(({ className, children, ...props }, ref) => ( 47 | 52 |
    {children}
    53 |
    54 | )) 55 | 56 | AccordionContent.displayName = AccordionPrimitive.Content.displayName 57 | 58 | export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } 59 | -------------------------------------------------------------------------------- /app/pvp/compendium/CompendiumClient.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @next/next/no-img-element */ 2 | 'use client'; 3 | import Pagination from '@/components/Pagination'; 4 | import TeamList from '@/components/TeamCompendium/TeamList'; 5 | import { Button } from '@/components/ui/button'; 6 | import { Team } from '@prisma/client'; 7 | import { FC, useState, useEffect } from 'react' 8 | 9 | interface CompendiumClientProps { 10 | teams: ({ 11 | members: { 12 | name: string 13 | }[] 14 | } & Team)[] | null; 15 | count: number; 16 | perPage: number; 17 | page: number; 18 | 19 | } 20 | 21 | const CompendiumClient: FC = ({ teams, count, perPage, page }) => { 22 | const [IsRendered, setIsRendered] = useState(false); 23 | 24 | 25 | const totalPages = Math.ceil(count / perPage); 26 | 27 | const prevPage = page - 1 > 0 ? page - 1 : 1; 28 | const nextPage = page + 1; 29 | const isPageOutoOfRange = page > totalPages; 30 | 31 | useEffect(() => { 32 | setIsRendered(true); 33 | }, []) 34 | 35 | return (IsRendered && 36 |
    37 |
    38 |

    Team List

    39 |

    Click on a team to see more details

    40 |

    Want to see your team here? DM me in discord or PokeMMO

    41 |
    42 | {isPageOutoOfRange ? (
    43 |

    This page doesn't exist

    ) : 44 | } 45 |
    46 | {isPageOutoOfRange ? () : ( 47 | ) 52 | } 53 |
    54 |
    55 | ) 56 | 57 | } 58 | 59 | export default CompendiumClient -------------------------------------------------------------------------------- /prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | // This is your Prisma schema file, 2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema 3 | 4 | generator client { 5 | provider = "prisma-client-js" 6 | } 7 | 8 | datasource db { 9 | provider = "postgresql" 10 | url = env("POSTGRES_PRISMA_URL") // uses connection pooling 11 | directUrl = env("POSTGRES_URL_NON_POOLING") // uses a direct connection 12 | } 13 | 14 | model Pokemon { 15 | id Int @id @default(autoincrement()) 16 | name String 17 | item String? 18 | ability String @default("No Ability") 19 | nature PokemonNature 20 | type PokemonType @default(NORMAL) 21 | type2 PokemonType? 22 | evs Evs[] 23 | moves Moves[] @relation("PokemonMoves") 24 | teams Team[] @relation("TeamMembers") 25 | } 26 | 27 | model Moves { 28 | id Int @id @default(autoincrement()) 29 | name String 30 | type PokemonType 31 | pokemon Pokemon[] @relation("PokemonMoves") 32 | pokemonId Int 33 | } 34 | 35 | model Evs { 36 | id Int @id @default(autoincrement()) 37 | hp Int? @default(0) 38 | atk Int? @default(0) 39 | def Int? @default(0) 40 | spaatk Int? @default(0) 41 | spd Int? @default(0) 42 | spe Int? @default(0) 43 | pokemon Pokemon @relation(fields: [pokemonId], references: [id], onDelete: Cascade) 44 | pokemonId Int 45 | } 46 | 47 | model Team { 48 | id String @id @default(cuid()) 49 | name String 50 | description String? 51 | Tier String? 52 | members Pokemon[] @relation("TeamMembers") 53 | createdAt DateTime @default(now()) 54 | author String? 55 | authorSocials String? 56 | } 57 | 58 | enum PokemonType { 59 | BUG 60 | DARK 61 | DRAGON 62 | ELECTRIC 63 | FIGHTING 64 | FIRE 65 | FLYING 66 | GHOST 67 | GRASS 68 | GROUND 69 | ICE 70 | NORMAL 71 | POISON 72 | PSYCHIC 73 | ROCK 74 | STEEL 75 | WATER 76 | } 77 | 78 | enum PokemonNature { 79 | HARDY 80 | LONELY 81 | BRAVE 82 | ADAMANT 83 | NAUGHTY 84 | BOLD 85 | DOCILE 86 | RELAXED 87 | IMPISH 88 | LAX 89 | TIMID 90 | HASTY 91 | SERIOUS 92 | JOLLY 93 | NAIVE 94 | MODEST 95 | MILD 96 | QUIET 97 | BASHFUL 98 | RASH 99 | CALM 100 | GENTLE 101 | SASSY 102 | CAREFUL 103 | QUIRKY 104 | } 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PokeMMO Utilities 2 | 3 | PokeMMO Utilities is a tool to help you to maximize your farming or have fun. You can timestamp your gym runs or use the randomizer to generate a troll team. 4 | 5 | ## Technologies Used 6 | 7 | - **HTML** 8 | - **CSS** 9 | - **Tailwind CSS**: For streamlined and responsive styling. 10 | - **TypeScript**: For static typing and improved developer experience. 11 | - **Zustand**: A state management library for React applications. 12 | - **React**: A JavaScript library for building user interfaces. 13 | - **Next.js 13**: A React framework for building server-side rendered applications. 14 | - **ShadcnUI** 15 | - **React Device** 16 | - **Axios**: A promise-based HTTP client for making requests. 17 | - **PostGRESQL** 18 | 19 | ## Usage 20 | 21 | To run this application, follow these steps: 22 | 23 | 1. **Install Dependencies**: Run `npm i` in your terminal to install all necessary dependencies. 24 | 2. **Start Development Server**: Execute `npm run dev` to start the development server. 25 | 26 | ## Database Setup (Using Prisma) 27 | 28 | 1. **Install Prisma CLI**: If you haven't already installed Prisma, you can do so by running `npm install prisma -D`. 29 | 2. **Generate Prisma Client**: Run `npx prisma generate` to generate the Prisma client based on your data models. 30 | 3. **Push Prisma DB**: Run `npx prisma db push` to push the prisma schema to your hosting database. 31 | 4. **Run Migrations**: If necessary, run `npx prisma migrate` to apply any pending migrations to your database. 32 | 33 | ## .env file 34 | ``` 35 | POSTGRES_URL= 36 | POSTGRES_URL_NON_POOLING= (if needed) 37 | POSTGRES_PRISMA_URL= 38 | POSTGRES_USER= 39 | POSTGRES_PASSWORD= 40 | POSTGRES_HOST= 41 | POSTGRES_DATABASE= 42 | ``` 43 | 44 | ## Maintainers 45 | 46 | This project is maintained by LemonMantis. For any questions or inquiries, please reach out on Discord at "lemonmantis". 47 | 48 | ## Preview 49 | 50 | ![Preview Image 1](https://github.com/LemonMantis5571/PokeMMO-Utilities/assets/85099589/e29ab0f0-fea9-49a8-9528-f3a293f3cc4b) 51 | 52 | ![Preview Image 2](https://github.com/LemonMantis5571/PokeMMO-Utilities/assets/85099589/bb82873d-056d-43d7-a35e-83b72a8a94c0) 53 | 54 | ![Preview Image 3](https://github.com/LemonMantis5571/PokeMMO-Utilities/assets/85099589/9c05183a-147c-4d59-80b1-0d9248516d8e) 55 | 56 | ![Preview Image 4](https://github.com/LemonMantis5571/PokeMMO-Utilities/assets/85099589/94a3095f-4aad-4823-8fd9-c15c106fff8a) 57 | 58 | ## APP Link 59 | 60 | - [PokeMMO Utilities](https://poke-mmo-utilities.vercel.app/) 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /components/ui/calendar.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import { ChevronLeft, ChevronRight } from "lucide-react" 5 | import { DayPicker } from "react-day-picker" 6 | 7 | import { cn } from "@/lib/utils" 8 | import { buttonVariants } from "@/components/ui/button" 9 | 10 | export type CalendarProps = React.ComponentProps 11 | 12 | function Calendar({ 13 | className, 14 | classNames, 15 | showOutsideDays = true, 16 | ...props 17 | }: CalendarProps) { 18 | return ( 19 | , 58 | IconRight: ({ ...props }) => , 59 | }} 60 | {...props} 61 | /> 62 | ) 63 | } 64 | Calendar.displayName = "Calendar" 65 | 66 | export { Calendar } 67 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | darkMode: ["class"], 4 | content: [ 5 | './pages/**/*.{ts,tsx}', 6 | './components/**/*.{ts,tsx}', 7 | './app/**/*.{ts,tsx}', 8 | './src/**/*.{ts,tsx}', 9 | './@/**/*.{ts,tsx}' 10 | ], 11 | theme: { 12 | container: { 13 | center: true, 14 | padding: '2rem', 15 | screens: { 16 | '2xl': '1400px' 17 | } 18 | }, 19 | extend: { 20 | colors: { 21 | border: 'hsl(var(--border))', 22 | input: 'hsl(var(--input))', 23 | ring: 'hsl(var(--ring))', 24 | background: 'hsl(var(--background))', 25 | foreground: 'hsl(var(--foreground))', 26 | primary: { 27 | DEFAULT: 'hsl(var(--primary))', 28 | foreground: 'hsl(var(--primary-foreground))' 29 | }, 30 | secondary: { 31 | DEFAULT: 'hsl(var(--secondary))', 32 | foreground: 'hsl(var(--secondary-foreground))' 33 | }, 34 | destructive: { 35 | DEFAULT: 'hsl(var(--destructive))', 36 | foreground: 'hsl(var(--destructive-foreground))' 37 | }, 38 | muted: { 39 | DEFAULT: 'hsl(var(--muted))', 40 | foreground: 'hsl(var(--muted-foreground))' 41 | }, 42 | accent: { 43 | DEFAULT: 'hsl(var(--accent))', 44 | foreground: 'hsl(var(--accent-foreground))' 45 | }, 46 | popover: { 47 | DEFAULT: 'hsl(var(--popover))', 48 | foreground: 'hsl(var(--popover-foreground))' 49 | }, 50 | card: { 51 | DEFAULT: 'hsl(var(--card))', 52 | foreground: 'hsl(var(--card-foreground))' 53 | } 54 | }, 55 | borderRadius: { 56 | lg: 'var(--radius)', 57 | md: 'calc(var(--radius) - 2px)', 58 | sm: 'calc(var(--radius) - 4px)' 59 | }, 60 | keyframes: { 61 | 'accordion-down': { 62 | from: { 63 | height: 0 64 | }, 65 | to: { 66 | height: 'var(--radix-accordion-content-height)' 67 | } 68 | }, 69 | 'accordion-up': { 70 | from: { 71 | height: 'var(--radix-accordion-content-height)' 72 | }, 73 | to: { 74 | height: 0 75 | } 76 | }, 77 | 'accordion-down': { 78 | from: { 79 | height: '0' 80 | }, 81 | to: { 82 | height: 'var(--radix-accordion-content-height)' 83 | } 84 | }, 85 | 'accordion-up': { 86 | from: { 87 | height: 'var(--radix-accordion-content-height)' 88 | }, 89 | to: { 90 | height: '0' 91 | } 92 | } 93 | }, 94 | animation: { 95 | 'accordion-down': 'accordion-down 0.2s ease-out', 96 | 'accordion-up': 'accordion-up 0.2s ease-out', 97 | 'accordion-down': 'accordion-down 0.2s ease-out', 98 | 'accordion-up': 'accordion-up 0.2s ease-out' 99 | } 100 | } 101 | }, 102 | plugins: [require("tailwindcss-animate")], 103 | } -------------------------------------------------------------------------------- /components/Modals/InfoModal.tsx: -------------------------------------------------------------------------------- 1 | 2 | import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '../ui/dialog' 3 | import { Button } from '../ui/button' 4 | 5 | 6 | const InfoModal = () => { 7 | return ( 8 | 9 | 10 | 11 | 12 | 13 | 14 | General Info 15 | 16 |
      17 |
    • 18 |

      19 | Evs & Nature are up to the player due to being really costly and not beginner friendly. 20 |

      21 |
    • 22 |
    • 23 |

      24 | The database such as pokemon, abilities and tiers are up to May 31th 2025, if any mistake please make an issue 25 | or a PR will be super apreciated, I had mocked the data from pokemmo shout wiki big thanks to them. 26 |

      27 |
    • 28 |
    29 |
    30 | Rules are optional 31 | 32 |
      33 |
    • 34 |

      35 | Moves that are not available for x or y reason can be changed. 36 |

      37 |
    • 38 |
    • 39 |

      40 | If there are 2 pokemon of the same species, you can reroll or change that pokemon for a new one. 41 |

      42 |
    • 43 |
    • 44 |

      45 | Items are interchangeable between pokemon 46 | eg: Garchomp w/ whiteherb can change its item to pikachu with choice band. 47 |

      48 |
    • 49 |
    50 |
    51 |
    52 | 53 |

    Made by LemonMantis5571

    54 |
    55 |
    56 |
    57 | ) 58 | } 59 | 60 | export default InfoModal -------------------------------------------------------------------------------- /components/LegendCalendar/LegendCalendar.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @next/next/no-img-element */ 2 | 'use client'; 3 | import { Calendar } from "@/components/ui/calendar" 4 | import React from 'react' 5 | import BirdDayComponent from "./BirdDayComponent"; 6 | import birds from '@/components/imgs/birds.png'; 7 | import dogs from '@/components/imgs/dogs.png'; 8 | import BeastDayComponent from "./BeastDayComponent"; 9 | import Footer from "./Footer"; 10 | 11 | 12 | 13 | const LegendCalendar = () => { 14 | const [birdDate, setBirdDate] = React.useState(new Date()); 15 | const [beastDate, setBeastDate] = React.useState(new Date()); 16 | const birdMonth = birdDate?.getMonth(); 17 | const beastMonth = beastDate?.getMonth(); 18 | 19 | 20 | 21 | return ( 22 |
    23 |
    24 | birds 28 |
    29 |
    30 |
    31 |

    Legendary Calendar

    32 |

    Select a date to see the legendary Pokemon of each month!

    33 |
    34 |
    35 | setBirdDate(month)} 42 | selected={birdDate} 43 | required 44 | onSelect={setBirdDate} 45 | className="rounded-md border w-fit" 46 | 47 | /> 48 | setBeastDate(month)} 55 | selected={beastDate} 56 | required 57 | onSelect={setBeastDate} 58 | className="rounded-md border w-fit" 59 | /> 60 |
    61 |
    62 |
    63 |
    64 | beasts 68 |
    69 |
    70 | 71 | ) 72 | } 73 | 74 | export default LegendCalendar -------------------------------------------------------------------------------- /components/PokemonWrapper.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import React, { FC, useEffect, useState } from 'react' 3 | import { Button } from './ui/button'; 4 | import { ShuffleIcon } from 'lucide-react'; 5 | import TierSelect from './TierSelect'; 6 | import useTier from '@/hooks/useTier'; 7 | import { ShufflePokemons } from '@/hooks/useShuffle'; 8 | import PokemonList from './PokemonList'; 9 | import InfoModal from './Modals/InfoModal'; 10 | 11 | interface PokemonWrapperProps { 12 | ShuffledList: { 13 | name: string; 14 | number: string; 15 | abilities: string[]; 16 | types: string[]; 17 | tier: string; 18 | items: string; 19 | moves: [string, string[]][]; 20 | newMoves: [string, { name: string; type: string; }][] | null; 21 | }[]; 22 | 23 | } 24 | 25 | const PokemonWrapper: FC = ({ ShuffledList }) => { 26 | const [isLoading, setIsLoading] = useState(false); 27 | const selectedTier = useTier(); 28 | const [IsRendered, setIsRendered] = useState(false); 29 | const [ShuffledPokemons, setShuffledPokemons] = useState(ShuffledList); 30 | // Yeah I know I can easily stop using ssr and just use the state but I'm in love with ssr and I want to keep it 31 | // I might replace in the future. 32 | // 2025 no regrets 33 | 34 | const handleReshuffleClick = async (tier: string) => { 35 | setIsLoading(true); 36 | try { 37 | const newShuffledPokemons = await ShufflePokemons(tier); 38 | if (newShuffledPokemons) { 39 | setShuffledPokemons(newShuffledPokemons); 40 | } 41 | } catch (error) { 42 | console.error("Error shuffling Pokémon:", error); 43 | } finally { 44 | setIsLoading(false); 45 | } 46 | }; 47 | 48 | 49 | useEffect(() => { 50 | setIsRendered(true); 51 | }, []); 52 | 53 | 54 | useEffect(() => { 55 | if (IsRendered && selectedTier.tier.value) { 56 | const ShuffleOnchange = async () => { 57 | try { 58 | await handleReshuffleClick(selectedTier.tier.value); 59 | } catch (error) { 60 | console.log(error); 61 | } 62 | } 63 | 64 | ShuffleOnchange(); 65 | } 66 | }, [selectedTier.tier.value, IsRendered]); 67 | 68 | 69 | 70 | return (IsRendered && 71 |
    72 |
    73 | 77 | 78 | 79 |
    80 | 81 |
    82 | ) 83 | } 84 | 85 | export default PokemonWrapper -------------------------------------------------------------------------------- /components/TierSelect.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import { FC, useCallback, useEffect, useState } from 'react' 3 | import { Button } from '@/components/ui/button'; 4 | import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem } from '@/components/ui/command'; 5 | import { cn } from '@/lib/utils'; 6 | import { Popover, PopoverContent, PopoverTrigger } from '@radix-ui/react-popover'; 7 | import { Check, ChevronsUpDown } from 'lucide-react'; 8 | import useTier from '@/hooks/useTier'; 9 | 10 | 11 | 12 | const Tiers = [ 13 | { 14 | value: "OU", 15 | label: "OverUsed", 16 | }, 17 | { 18 | value: "UU", 19 | label: "UnderUsed", 20 | }, 21 | { 22 | value: "NU", 23 | label: "NeverUsed", 24 | }, 25 | { 26 | value: "Untiered", 27 | label: "No-Tier", 28 | }, 29 | { 30 | value: "ALL", 31 | label: "ALL", 32 | }, 33 | ] 34 | 35 | const TierSelect: FC = () => { 36 | const SelectedTier = useTier((state) => state.updateTier); 37 | const [open, setOpen] = useState(false) 38 | const [value, setValue] = useState(); 39 | 40 | return ( 41 | 42 | 43 | 54 | 55 | 56 | 57 | 58 | No tier Found. 59 | 60 | {Tiers.map((tier, index) => ( 61 | { 64 | setValue(tier.value); 65 | SelectedTier({ value: tier.value }); 66 | setOpen(false) 67 | }} 68 | > 69 | 75 | {tier.label} 76 | 77 | ))} 78 | 79 | 80 | 81 | ) 82 | } 83 | 84 | export default TierSelect -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root 7 | { 8 | /* Name: custom color palette 9 | Author: Ilias Ism 10 | URL: https://gradient.page */ 11 | 12 | /* CSS: .bg-gradient { background: var(--gradient) } */ 13 | --gradient: linear-gradient(to top left, #00c6ff, #0072ff); 14 | 15 | --background: 206 65% 4%; 16 | --foreground: 206 10% 97.5%; 17 | 18 | --muted: 206 50% 15%; 19 | --muted-foreground: 206 10% 55%; 20 | 21 | --popover: 206 45% 6.5%; 22 | --popover-foreground: 206 10% 97.5%; 23 | 24 | --card: 206 45% 6.5%; 25 | --card-foreground: 206 10% 97.5%; 26 | 27 | --border: 206 50% 15%; 28 | --input: 206 50% 15%; 29 | 30 | --primary: 206 100% 50%; 31 | --primary-foreground: 206 10% 5%; 32 | 33 | --secondary: 206 50% 15%; 34 | --secondary-foreground: 206 10% 97.5%; 35 | 36 | --accent: 206 50% 15%; 37 | --accent-foreground: 206 10% 97.5%; 38 | 39 | --destructive: 0 62.8% 30.6%; 40 | --destructive-foreground: 206 10% 97.5%; 41 | 42 | --ring: 206 100% 50%; 43 | } 44 | } 45 | 46 | .dark { 47 | --background: 0 0% 3.9%; 48 | --foreground: 0 0% 98%; 49 | 50 | --card: 0 0% 3.9%; 51 | --card-foreground: 0 0% 98%; 52 | 53 | --popover: 0 0% 3.9%; 54 | --popover-foreground: 0 0% 98%; 55 | 56 | --primary: 0 0% 98%; 57 | --primary-foreground: 0 0% 9%; 58 | 59 | --secondary: 0 0% 14.9%; 60 | --secondary-foreground: 0 0% 98%; 61 | 62 | --muted: 0 0% 14.9%; 63 | --muted-foreground: 0 0% 63.9%; 64 | 65 | --accent: 0 0% 14.9%; 66 | --accent-foreground: 0 0% 98%; 67 | 68 | --destructive: 0 62.8% 30.6%; 69 | --destructive-foreground: 0 0% 98%; 70 | 71 | --border: 0 0% 14.9%; 72 | --input: 0 0% 14.9%; 73 | --ring: 0 0% 83.1%; 74 | } 75 | 76 | 77 | .Fire { 78 | color: #F08030; 79 | } 80 | 81 | .Normal { 82 | color: #A8A878; 83 | } 84 | 85 | .Fighting { 86 | color: #C03028; 87 | } 88 | 89 | .Water { 90 | color: #6890F0; 91 | } 92 | 93 | .Flying { 94 | color: #A890F0; 95 | } 96 | 97 | .Grass { 98 | color: #78C850; 99 | 100 | } 101 | 102 | .Poison { 103 | color: #A040A0; 104 | } 105 | 106 | .Electric { 107 | color: #F8D030; 108 | } 109 | 110 | .Ground { 111 | color: #E0C068; 112 | } 113 | 114 | .Psychic { 115 | color: #F85888; 116 | } 117 | 118 | .Rock { 119 | color: #B8A038; 120 | } 121 | 122 | .Ice { 123 | color: #98D8D8; 124 | } 125 | 126 | .Bug { 127 | color: #A8B820; 128 | } 129 | 130 | .Dragon { 131 | color: #7038F8; 132 | } 133 | 134 | .Ghost { 135 | color: #705898; 136 | } 137 | 138 | .Dark { 139 | color: #705848; 140 | } 141 | 142 | .Steel { 143 | color: #B8B8D0; 144 | } 145 | 146 | .Fairy { 147 | color: #EE99AC; 148 | } 149 | 150 | 151 | .orange_gradient { 152 | @apply bg-gradient-to-r from-amber-500 via-orange-600 to-yellow-500 bg-clip-text text-transparent; 153 | } 154 | 155 | 156 | @layer base { 157 | * { 158 | @apply border-zinc-900; 159 | } 160 | 161 | body { 162 | @apply bg-zinc-950 text-foreground; 163 | } 164 | } -------------------------------------------------------------------------------- /app/pvp/compendium/[teamId]/PokemonTeamCard.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @next/next/no-img-element */ 2 | 'use client'; 3 | import { FC } from 'react' 4 | import { useToast } from "@/components/ui/use-toast" 5 | import { PokePaste, PokemonMember } from '@/lib/types/PokemonMembertypes'; 6 | import { Icons } from '@/components/Icons'; 7 | import { convertToPokepaste } from '@/lib/pokepaste.generator'; 8 | import TeamMemberList from '@/components/TeamCompendium/TeamMemberList'; 9 | 10 | interface PokemonTeamCardProps { 11 | team: { 12 | name: string, 13 | description?: string | null, 14 | tier?: string | null, 15 | members: PokemonMember[] 16 | } 17 | } 18 | 19 | const PokemonTeamCard: FC = ({ team }) => { 20 | const { toast } = useToast(); 21 | 22 | const copytoclipboard = () => { 23 | navigator.clipboard.writeText(window.location.href); 24 | toast({ 25 | title: 'Copied to clipboard', 26 | description: 'You can share this link with your friends', 27 | duration: 2000, 28 | }); 29 | } 30 | 31 | const pokepastetoClipboard = (team: PokePaste) => { 32 | const pokepasteContent = convertToPokepaste(team); 33 | navigator.clipboard.writeText(pokepasteContent); 34 | toast({ 35 | title: 'PokePaste copied to clipboard', 36 | description: 'You can input this paste in showdown', 37 | duration: 2000, 38 | }); 39 | } 40 | 41 | 42 | return ( 43 |
    44 |
    45 | 46 |
    47 |
    54 |

    {team.name}

    55 |
    56 |

    Copy team url to clipboard

    57 |
    58 | 59 |
    60 |
    61 |
    62 |

    Copy team in pokepaste format

    63 |
    64 | pokepastetoClipboard(team)} /> 65 |
    66 |
    67 |
    68 |
    69 | ) 70 | } 71 | 72 | export default PokemonTeamCard -------------------------------------------------------------------------------- /components/ui/table.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | const Table = React.forwardRef< 6 | HTMLTableElement, 7 | React.HTMLAttributes 8 | >(({ className, ...props }, ref) => ( 9 |
    10 | 15 | 16 | )) 17 | Table.displayName = "Table" 18 | 19 | const TableHeader = React.forwardRef< 20 | HTMLTableSectionElement, 21 | React.HTMLAttributes 22 | >(({ className, ...props }, ref) => ( 23 | 24 | )) 25 | TableHeader.displayName = "TableHeader" 26 | 27 | const TableBody = React.forwardRef< 28 | HTMLTableSectionElement, 29 | React.HTMLAttributes 30 | >(({ className, ...props }, ref) => ( 31 | 36 | )) 37 | TableBody.displayName = "TableBody" 38 | 39 | const TableFooter = React.forwardRef< 40 | HTMLTableSectionElement, 41 | React.HTMLAttributes 42 | >(({ className, ...props }, ref) => ( 43 | 48 | )) 49 | TableFooter.displayName = "TableFooter" 50 | 51 | const TableRow = React.forwardRef< 52 | HTMLTableRowElement, 53 | React.HTMLAttributes 54 | >(({ className, ...props }, ref) => ( 55 | 63 | )) 64 | TableRow.displayName = "TableRow" 65 | 66 | const TableHead = React.forwardRef< 67 | HTMLTableCellElement, 68 | React.ThHTMLAttributes 69 | >(({ className, ...props }, ref) => ( 70 |
    78 | )) 79 | TableHead.displayName = "TableHead" 80 | 81 | const TableCell = React.forwardRef< 82 | HTMLTableCellElement, 83 | React.TdHTMLAttributes 84 | >(({ className, ...props }, ref) => ( 85 | 90 | )) 91 | TableCell.displayName = "TableCell" 92 | 93 | const TableCaption = React.forwardRef< 94 | HTMLTableCaptionElement, 95 | React.HTMLAttributes 96 | >(({ className, ...props }, ref) => ( 97 |
    102 | )) 103 | TableCaption.displayName = "TableCaption" 104 | 105 | export { 106 | Table, 107 | TableHeader, 108 | TableBody, 109 | TableFooter, 110 | TableHead, 111 | TableRow, 112 | TableCell, 113 | TableCaption, 114 | } 115 | -------------------------------------------------------------------------------- /lib/pokemon.generators.ts: -------------------------------------------------------------------------------- 1 | import { MovepoolItem } from "@/app/pvp/randomizer/page"; 2 | import { Generations } from "@pkmn/data"; 3 | import { Dex } from "@pkmn/dex"; 4 | import { Pokemon } from "./utils"; 5 | import moves from "../data/pokemon_moves.json" 6 | 7 | type MovesData = { 8 | [key: string]: { 9 | moves: [ 10 | { 11 | id: number, 12 | level: number, 13 | name: string, 14 | type: string, 15 | } 16 | ] 17 | } 18 | }; 19 | 20 | export const getRandomPokemons = (pokemons: Pokemon[], count: number, tier: string) => { 21 | const filteredPokemons = pokemons.filter((pokemon) => 22 | tier === 'ALL' ? pokemon.tier !== 'Uber' : pokemon.tier === tier); 23 | const shuffledPokemons = [...filteredPokemons]; // Make a copy to avoid modifying the original array 24 | const randomPokemons = []; 25 | 26 | for (let i = 0; i < shuffledPokemons.length; i++) { 27 | const randomIndex = Math.floor(Math.random() * shuffledPokemons.length); 28 | randomPokemons.push(shuffledPokemons.splice(randomIndex, 1)[0]) 29 | 30 | } 31 | 32 | return randomPokemons.splice(0, count); 33 | }; 34 | 35 | export const getRandomAbility = (Abilities: string[]) => { 36 | const shuffleAbilities = [...Abilities]; 37 | const randomIndex = Math.floor(Math.random() * shuffleAbilities.length); 38 | return shuffleAbilities[randomIndex]; 39 | } 40 | 41 | export const getRandomItems = (Items: string[]) => { 42 | const shuffleItems = [...Items]; 43 | const randomIndex = Math.floor(Math.random() * shuffleItems.length); 44 | return shuffleItems[randomIndex]; 45 | } 46 | 47 | export const getRandomMoves = async (pokemon: string) => { 48 | 49 | const gens = new Generations(Dex); 50 | const movepool = await gens.get(5).learnsets.learnable(pokemon); 51 | const moves = Object.entries(movepool as MovepoolItem) 52 | const filteredMoves = moves.filter(([key]) => !key.includes('doubleteam')); // DoubleTeam is useless in PokeMMO 53 | 54 | const shuffleMoves = [...filteredMoves].sort(() => Math.random() - 0.5); 55 | const randomMoves = shuffleMoves.slice(0, 4); 56 | 57 | return randomMoves; 58 | 59 | } 60 | 61 | 62 | export const newGetRandomMoves = async (pokemon: string) => { 63 | try { 64 | const randomMoves: MovesData = moves as any; 65 | const movesDataForPokemon = Object.entries(randomMoves[pokemon].moves.map((move) => { 66 | return { name: move.name, type: move.type, } 67 | })); 68 | const shuffleMoves = [...movesDataForPokemon].sort(() => Math.random() - 0.5); 69 | const randomMovesForPokemon = shuffleMoves.slice(0, 4); 70 | return randomMovesForPokemon; 71 | 72 | } catch (error) { 73 | console.log(error, pokemon); 74 | return null; 75 | } 76 | 77 | 78 | } 79 | 80 | export const getRandomPokemonsWithMoves = async (pokemons: Pokemon[], count: number, Items: string[], tier: string) => { 81 | const randomPokemons = getRandomPokemons(pokemons, count, tier); 82 | 83 | 84 | const pokemonsWithMoves = await Promise.all( 85 | randomPokemons.map(async (pokemon) => { 86 | const randomMoves = await getRandomMoves(pokemon.name); 87 | const newRandomMoves = await newGetRandomMoves(pokemon.name.toLowerCase()); 88 | const randomItems = getRandomItems(Items); 89 | return { ...pokemon, moves: randomMoves, items: randomItems, newMoves: newRandomMoves}; 90 | }) 91 | ); 92 | 93 | return pokemonsWithMoves; 94 | }; -------------------------------------------------------------------------------- /components/TeamCompendium/TeamList.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @next/next/no-img-element */ 2 | import { Team } from '@prisma/client'; 3 | import { FC } from 'react' 4 | import { Icons } from '../Icons'; 5 | import { Label } from '../ui/label'; 6 | import { useRouter } from 'next/navigation'; 7 | import { format } from 'date-fns'; 8 | 9 | interface TeamListProps { 10 | teams: ({ 11 | members: { 12 | name: string 13 | }[] 14 | } & Team)[] | null; 15 | } 16 | 17 | const TeamList: FC = ({ teams }) => { 18 | let socials = teams?.map((team) => team.authorSocials); 19 | 20 | const getSocials = (socials: string | null) => { 21 | if (socials !== null) { 22 | const matchResult = socials.match(/@([^/]+)/); 23 | const url = matchResult ? matchResult[1] : null; 24 | return url; 25 | } 26 | return null; 27 | } 28 | 29 | const router = useRouter(); 30 | return ( 31 | teams?.map((team, index) => { 32 | return ( 33 |
    router.push(`compendium/${team.id}`)} className='rounded 34 | mx-auto border 35 | max-w-xl max-h-fit 36 | bg-zinc-900 37 | border-black 38 | hover:bg-zinc-950 39 | opacity-90 40 | cursor-pointer' 41 | key={index}> 42 |
    43 |
    44 |

    45 | {team.name} 46 |

    47 |
    48 |
    49 |
    50 | {team.members.map((pokemon, index) => { 51 | return ( 52 | pokemon 60 | ) 61 | })} 62 |
    63 |
    64 |
    65 | 66 | 67 |
    68 | {team.authorSocials &&
    69 | 70 | 71 |
    } 72 | {!team.authorSocials &&
    73 | 74 | 75 |
    } 76 |
    77 |
    78 | 79 | ) 80 | }) 81 | ) 82 | } 83 | 84 | export default TeamList -------------------------------------------------------------------------------- /components/PokemonCard.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @next/next/no-img-element */ 2 | 'use client' 3 | import { FC, useEffect, useState } from 'react' 4 | import { 5 | Card, 6 | CardContent, 7 | CardDescription, 8 | CardFooter, 9 | CardHeader, 10 | CardTitle, 11 | } from "@/components/ui/card" 12 | 13 | import Moves from './Moves' 14 | import { getRandomAbility } from '@/lib/pokemon.generators' 15 | import { Pokemon } from '@/lib/utils' 16 | 17 | interface PokemonCard extends Pokemon { 18 | moves: [string, string[]][]; 19 | newMoves: [string, { name: string; type: string; }][] | null; 20 | Item: string; 21 | } 22 | 23 | 24 | const PokemonCard: FC = ({ name, types, abilities, number, tier, moves, Item, newMoves }) => { 25 | const [domLoaded, setDomLoaded] = useState(false); 26 | const pokemonIMG = `https://play.pokemonshowdown.com/sprites/xyani/${name.toLowerCase().replace(/\./g, '')}.gif`; 27 | const itemIMG = Item == 'assault-vest' 28 | ? `https://archives.bulbagarden.net/media/upload/b/b1/Dream_Assault_Vest_Sprite.png` 29 | : `https://play.pokemonshowdown.com/sprites/itemicons/${Item}.png`; // I cannot find the assault vest sprite LFMAO 30 | 31 | const randomAbility = getRandomAbility(abilities); 32 | 33 | const renderTypes = () => { 34 | if (!domLoaded) return null; 35 | return ( 36 |
    37 |
    {types[0]}
    38 | {types[1] &&
    {types[1]}
    } 39 |
    40 | ); 41 | }; 42 | 43 | const mappedMoves = newMoves?.map(([id, move]) => ({ 44 | id: parseInt(id), // Convert id to number if needed 45 | name: move.name, 46 | type: move.type 47 | })); 48 | 49 | const renderMoves = () => { 50 | return ( 51 |
    52 | {moves.map(([move], index) => ( 53 | 54 | ))} 55 |
    56 | ); 57 | }; 58 | 59 | const renderAccurateMoves = () => { 60 | return ( 61 |
    62 | {mappedMoves?.map((move) => ( 63 | 64 | ))} 65 |
    66 | ); 67 | } 68 | 69 | 70 | 71 | useEffect(() => { 72 | setDomLoaded(true); 73 | }, []); 74 | 75 | return ( 76 | 77 | 78 | 79 | {name} 80 | 81 | 82 | {renderTypes()} 83 | 84 | 85 | 86 |
    87 | pokemon 88 | 89 |
    90 | icon-img 91 | {Item} 92 |
    93 |
    94 | {newMoves ? renderAccurateMoves() : renderMoves()} 95 |
    96 | 97 |
    98 |
    99 | Ability: 100 |

    101 | {randomAbility} 102 |

    103 |
    104 |
    105 | Tier: 106 |

    {tier}

    107 |
    108 |
    109 |
    110 |
    111 | ) 112 | } 113 | 114 | export default PokemonCard -------------------------------------------------------------------------------- /components/Labels.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import React, { FC } from 'react' 3 | import { 4 | NavigationMenu, 5 | NavigationMenuContent, 6 | NavigationMenuItem, 7 | NavigationMenuLink, 8 | NavigationMenuList, 9 | NavigationMenuTrigger, 10 | } from "@/components/ui/navigation-menu" 11 | import ListItem from './ListItem' 12 | import { CalculatorIcon, Calendar, CircuitBoardIcon, CoinsIcon, SkullIcon, TimerIcon, TrophyIcon } from 'lucide-react' 13 | interface LabelsProps { 14 | 15 | } 16 | 17 | const Labels: FC = ({ }) => { 18 | return ( 19 | 20 | 21 | 22 | PvP Utilities 23 | 24 | 46 | 47 | 48 | 49 | PvE Utilities 50 | 51 | 73 | 74 | 75 | 76 | ) 77 | } 78 | 79 | 80 | export default Labels -------------------------------------------------------------------------------- /components/ui/dialog.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as DialogPrimitive from "@radix-ui/react-dialog" 5 | import { X } from "lucide-react" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const Dialog = DialogPrimitive.Root 10 | 11 | const DialogTrigger = DialogPrimitive.Trigger 12 | 13 | const DialogPortal = ({ 14 | className, 15 | ...props 16 | }: DialogPrimitive.DialogPortalProps) => ( 17 | 18 | ) 19 | DialogPortal.displayName = DialogPrimitive.Portal.displayName 20 | 21 | const DialogOverlay = React.forwardRef< 22 | React.ElementRef, 23 | React.ComponentPropsWithoutRef 24 | >(({ className, ...props }, ref) => ( 25 | 33 | )) 34 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName 35 | 36 | const DialogContent = React.forwardRef< 37 | React.ElementRef, 38 | React.ComponentPropsWithoutRef 39 | >(({ className, children, ...props }, ref) => ( 40 | 41 | 42 | 50 | {children} 51 | 52 | 53 | Close 54 | 55 | 56 | 57 | )) 58 | DialogContent.displayName = DialogPrimitive.Content.displayName 59 | 60 | const DialogHeader = ({ 61 | className, 62 | ...props 63 | }: React.HTMLAttributes) => ( 64 |
    71 | ) 72 | DialogHeader.displayName = "DialogHeader" 73 | 74 | const DialogFooter = ({ 75 | className, 76 | ...props 77 | }: React.HTMLAttributes) => ( 78 |
    85 | ) 86 | DialogFooter.displayName = "DialogFooter" 87 | 88 | const DialogTitle = React.forwardRef< 89 | React.ElementRef, 90 | React.ComponentPropsWithoutRef 91 | >(({ className, ...props }, ref) => ( 92 | 100 | )) 101 | DialogTitle.displayName = DialogPrimitive.Title.displayName 102 | 103 | const DialogDescription = React.forwardRef< 104 | React.ElementRef, 105 | React.ComponentPropsWithoutRef 106 | >(({ className, ...props }, ref) => ( 107 | 112 | )) 113 | DialogDescription.displayName = DialogPrimitive.Description.displayName 114 | 115 | export { 116 | Dialog, 117 | DialogTrigger, 118 | DialogContent, 119 | DialogHeader, 120 | DialogFooter, 121 | DialogTitle, 122 | DialogDescription, 123 | } 124 | -------------------------------------------------------------------------------- /components/TeamCompendium/TeamMemberCard.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @next/next/no-img-element */ 2 | import { FC } from 'react' 3 | import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '../ui/card' 4 | import { PokemonMember } from '@/lib/types/PokemonMembertypes' 5 | import Moves from '../Moves'; 6 | interface TeamMemberCardProps { 7 | pokemon: PokemonMember; 8 | } 9 | 10 | const TeamMemberCard: FC = ({ pokemon }) => { 11 | 12 | const capitalizeFirstLetter = (str: string) => { 13 | const capitalized = str.charAt(0).toUpperCase() + str.toLowerCase().slice(1); 14 | return capitalized; 15 | }; 16 | return (<> 17 | 18 | 19 | {pokemon.name} 20 | 21 |
    22 |
    23 |
    24 | {pokemon.type.toLowerCase()} 25 |
    26 |
    27 | {pokemon.type2 ? pokemon.type2.toLowerCase() : null} 28 |
    29 |
    30 |
    31 | {pokemon.evs.map((ev, index) => { 32 | return ( 33 |
      34 |
    • {ev.hp ? `Hp: ${ev.hp}` : null}
    • 35 |
    • {ev.atk ? `Atk: ${ev.atk}` : null}
    • 36 |
    • {ev.def ? `Def: ${ev.def}` : null}
    • 37 |
    • {ev.spaatk ? `SpA: ${ev.spaatk}` : null}
    • 38 |
    • {ev.spd ? `SpD: ${ev.spd}` : null}
    • 39 |
    • {ev.spe ? `Spe: ${ev.spe}` : null}
    • 40 |
    41 | 42 | ) 43 | })} 44 |
    45 |
    46 |
    47 |
    48 | 49 |
    50 | pokemon 55 | 56 |
    57 | icon-img 62 | {pokemon.item} 63 |
    64 |
    65 |
    66 | {pokemon.moves.map((move, index) => { 67 | return ( 68 | 69 | ) 70 | })} 71 |
    72 |
    73 | 74 | Ability: 75 | {pokemon.ability} 76 | Nature: 77 | {capitalizeFirstLetter(pokemon.nature)} 78 | 79 |
    ) 80 | } 81 | 82 | export default TeamMemberCard -------------------------------------------------------------------------------- /components/ui/use-toast.ts: -------------------------------------------------------------------------------- 1 | // Inspired by react-hot-toast library 2 | import * as React from "react" 3 | 4 | import type { 5 | ToastActionElement, 6 | ToastProps, 7 | } from "@/components/ui/toast" 8 | 9 | const TOAST_LIMIT = 1 10 | const TOAST_REMOVE_DELAY = 1000000 11 | 12 | type ToasterToast = ToastProps & { 13 | id: string 14 | title?: React.ReactNode 15 | description?: React.ReactNode 16 | action?: ToastActionElement 17 | } 18 | 19 | const actionTypes = { 20 | ADD_TOAST: "ADD_TOAST", 21 | UPDATE_TOAST: "UPDATE_TOAST", 22 | DISMISS_TOAST: "DISMISS_TOAST", 23 | REMOVE_TOAST: "REMOVE_TOAST", 24 | } as const 25 | 26 | let count = 0 27 | 28 | function genId() { 29 | count = (count + 1) % Number.MAX_SAFE_INTEGER 30 | return count.toString() 31 | } 32 | 33 | type ActionType = typeof actionTypes 34 | 35 | type Action = 36 | | { 37 | type: ActionType["ADD_TOAST"] 38 | toast: ToasterToast 39 | } 40 | | { 41 | type: ActionType["UPDATE_TOAST"] 42 | toast: Partial 43 | } 44 | | { 45 | type: ActionType["DISMISS_TOAST"] 46 | toastId?: ToasterToast["id"] 47 | } 48 | | { 49 | type: ActionType["REMOVE_TOAST"] 50 | toastId?: ToasterToast["id"] 51 | } 52 | 53 | interface State { 54 | toasts: ToasterToast[] 55 | } 56 | 57 | const toastTimeouts = new Map>() 58 | 59 | const addToRemoveQueue = (toastId: string) => { 60 | if (toastTimeouts.has(toastId)) { 61 | return 62 | } 63 | 64 | const timeout = setTimeout(() => { 65 | toastTimeouts.delete(toastId) 66 | dispatch({ 67 | type: "REMOVE_TOAST", 68 | toastId: toastId, 69 | }) 70 | }, TOAST_REMOVE_DELAY) 71 | 72 | toastTimeouts.set(toastId, timeout) 73 | } 74 | 75 | export const reducer = (state: State, action: Action): State => { 76 | switch (action.type) { 77 | case "ADD_TOAST": 78 | return { 79 | ...state, 80 | toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), 81 | } 82 | 83 | case "UPDATE_TOAST": 84 | return { 85 | ...state, 86 | toasts: state.toasts.map((t) => 87 | t.id === action.toast.id ? { ...t, ...action.toast } : t 88 | ), 89 | } 90 | 91 | case "DISMISS_TOAST": { 92 | const { toastId } = action 93 | 94 | // ! Side effects ! - This could be extracted into a dismissToast() action, 95 | // but I'll keep it here for simplicity 96 | if (toastId) { 97 | addToRemoveQueue(toastId) 98 | } else { 99 | state.toasts.forEach((toast) => { 100 | addToRemoveQueue(toast.id) 101 | }) 102 | } 103 | 104 | return { 105 | ...state, 106 | toasts: state.toasts.map((t) => 107 | t.id === toastId || toastId === undefined 108 | ? { 109 | ...t, 110 | open: false, 111 | } 112 | : t 113 | ), 114 | } 115 | } 116 | case "REMOVE_TOAST": 117 | if (action.toastId === undefined) { 118 | return { 119 | ...state, 120 | toasts: [], 121 | } 122 | } 123 | return { 124 | ...state, 125 | toasts: state.toasts.filter((t) => t.id !== action.toastId), 126 | } 127 | } 128 | } 129 | 130 | const listeners: Array<(state: State) => void> = [] 131 | 132 | let memoryState: State = { toasts: [] } 133 | 134 | function dispatch(action: Action) { 135 | memoryState = reducer(memoryState, action) 136 | listeners.forEach((listener) => { 137 | listener(memoryState) 138 | }) 139 | } 140 | 141 | type Toast = Omit 142 | 143 | function toast({ ...props }: Toast) { 144 | const id = genId() 145 | 146 | const update = (props: ToasterToast) => 147 | dispatch({ 148 | type: "UPDATE_TOAST", 149 | toast: { ...props, id }, 150 | }) 151 | const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }) 152 | 153 | dispatch({ 154 | type: "ADD_TOAST", 155 | toast: { 156 | ...props, 157 | id, 158 | open: true, 159 | onOpenChange: (open) => { 160 | if (!open) dismiss() 161 | }, 162 | }, 163 | }) 164 | 165 | return { 166 | id: id, 167 | dismiss, 168 | update, 169 | } 170 | } 171 | 172 | function useToast() { 173 | const [state, setState] = React.useState(memoryState) 174 | 175 | React.useEffect(() => { 176 | listeners.push(setState) 177 | return () => { 178 | const index = listeners.indexOf(setState) 179 | if (index > -1) { 180 | listeners.splice(index, 1) 181 | } 182 | } 183 | }, [state]) 184 | 185 | return { 186 | ...state, 187 | toast, 188 | dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }), 189 | } 190 | } 191 | 192 | export { useToast, toast } 193 | -------------------------------------------------------------------------------- /components/ui/select.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as SelectPrimitive from "@radix-ui/react-select" 5 | import { Check, ChevronDown } from "lucide-react" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const Select = SelectPrimitive.Root 10 | 11 | const SelectGroup = SelectPrimitive.Group 12 | 13 | const SelectValue = SelectPrimitive.Value 14 | 15 | const SelectTrigger = React.forwardRef< 16 | React.ElementRef, 17 | React.ComponentPropsWithoutRef 18 | >(({ className, children, ...props }, ref) => ( 19 | 27 | {children} 28 | 29 | 30 | 31 | 32 | )) 33 | SelectTrigger.displayName = SelectPrimitive.Trigger.displayName 34 | 35 | const SelectContent = React.forwardRef< 36 | React.ElementRef, 37 | React.ComponentPropsWithoutRef 38 | >(({ className, children, position = "popper", ...props }, ref) => ( 39 | 40 | 51 | 58 | {children} 59 | 60 | 61 | 62 | )) 63 | SelectContent.displayName = SelectPrimitive.Content.displayName 64 | 65 | const SelectLabel = React.forwardRef< 66 | React.ElementRef, 67 | React.ComponentPropsWithoutRef 68 | >(({ className, ...props }, ref) => ( 69 | 74 | )) 75 | SelectLabel.displayName = SelectPrimitive.Label.displayName 76 | 77 | const SelectItem = React.forwardRef< 78 | React.ElementRef, 79 | React.ComponentPropsWithoutRef 80 | >(({ className, children, ...props }, ref) => ( 81 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | {children} 96 | 97 | )) 98 | SelectItem.displayName = SelectPrimitive.Item.displayName 99 | 100 | const SelectSeparator = React.forwardRef< 101 | React.ElementRef, 102 | React.ComponentPropsWithoutRef 103 | >(({ className, ...props }, ref) => ( 104 | 109 | )) 110 | SelectSeparator.displayName = SelectPrimitive.Separator.displayName 111 | 112 | export { 113 | Select, 114 | SelectGroup, 115 | SelectValue, 116 | SelectTrigger, 117 | SelectContent, 118 | SelectLabel, 119 | SelectItem, 120 | SelectSeparator, 121 | } 122 | -------------------------------------------------------------------------------- /components/Icons.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import { LucideProps, MessageSquare, User } from 'lucide-react' 3 | 4 | export const Icons = { 5 | user: User, 6 | logo: (props: LucideProps) => ( 7 | 8 | 9 | 13 | 17 | 21 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | ), 35 | google: (props: LucideProps) => ( 36 | 37 | 41 | 45 | 49 | 53 | 54 | 55 | ), 56 | externalLink: (props: LucideProps) => ( 57 | 58 | ), 59 | author: (props: LucideProps) => ( 60 | 61 | ), 62 | date: (props: LucideProps) => ( 63 | 64 | ), 65 | copytoclipboard: (props: LucideProps) => ( 66 | 67 | ), 68 | Youtube: (props: LucideProps) => ( 69 | 70 | ), 71 | Calendar: (props: LucideProps) => ( 72 | 73 | ), 74 | commentReply: MessageSquare, 75 | } -------------------------------------------------------------------------------- /components/ui/toast.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as ToastPrimitives from "@radix-ui/react-toast" 3 | import { cva, type VariantProps } from "class-variance-authority" 4 | import { X } from "lucide-react" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const ToastProvider = ToastPrimitives.Provider 9 | 10 | const ToastViewport = React.forwardRef< 11 | React.ElementRef, 12 | React.ComponentPropsWithoutRef 13 | >(({ className, ...props }, ref) => ( 14 | 22 | )) 23 | ToastViewport.displayName = ToastPrimitives.Viewport.displayName 24 | 25 | const toastVariants = cva( 26 | "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 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", 27 | { 28 | variants: { 29 | variant: { 30 | default: "border bg-background text-foreground", 31 | destructive: 32 | "destructive group border-destructive bg-destructive text-destructive-foreground", 33 | }, 34 | }, 35 | defaultVariants: { 36 | variant: "default", 37 | }, 38 | } 39 | ) 40 | 41 | const Toast = React.forwardRef< 42 | React.ElementRef, 43 | React.ComponentPropsWithoutRef & 44 | VariantProps 45 | >(({ className, variant, ...props }, ref) => { 46 | return ( 47 | 52 | ) 53 | }) 54 | Toast.displayName = ToastPrimitives.Root.displayName 55 | 56 | const ToastAction = React.forwardRef< 57 | React.ElementRef, 58 | React.ComponentPropsWithoutRef 59 | >(({ className, ...props }, ref) => ( 60 | 68 | )) 69 | ToastAction.displayName = ToastPrimitives.Action.displayName 70 | 71 | const ToastClose = React.forwardRef< 72 | React.ElementRef, 73 | React.ComponentPropsWithoutRef 74 | >(({ className, ...props }, ref) => ( 75 | 84 | 85 | 86 | )) 87 | ToastClose.displayName = ToastPrimitives.Close.displayName 88 | 89 | const ToastTitle = React.forwardRef< 90 | React.ElementRef, 91 | React.ComponentPropsWithoutRef 92 | >(({ className, ...props }, ref) => ( 93 | 98 | )) 99 | ToastTitle.displayName = ToastPrimitives.Title.displayName 100 | 101 | const ToastDescription = React.forwardRef< 102 | React.ElementRef, 103 | React.ComponentPropsWithoutRef 104 | >(({ className, ...props }, ref) => ( 105 | 110 | )) 111 | ToastDescription.displayName = ToastPrimitives.Description.displayName 112 | 113 | type ToastProps = React.ComponentPropsWithoutRef 114 | 115 | type ToastActionElement = React.ReactElement 116 | 117 | export { 118 | type ToastProps, 119 | type ToastActionElement, 120 | ToastProvider, 121 | ToastViewport, 122 | Toast, 123 | ToastTitle, 124 | ToastDescription, 125 | ToastClose, 126 | ToastAction, 127 | } 128 | -------------------------------------------------------------------------------- /components/ui/command.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import { DialogProps } from "@radix-ui/react-dialog" 5 | import { Command as CommandPrimitive } from "cmdk" 6 | import { Search } from "lucide-react" 7 | 8 | import { cn } from "@/lib/utils" 9 | import { Dialog, DialogContent } from "@/components/ui/dialog" 10 | 11 | const Command = React.forwardRef< 12 | React.ElementRef, 13 | React.ComponentPropsWithoutRef 14 | >(({ className, ...props }, ref) => ( 15 | 23 | )) 24 | Command.displayName = CommandPrimitive.displayName 25 | 26 | interface CommandDialogProps extends DialogProps {} 27 | 28 | const CommandDialog = ({ children, ...props }: CommandDialogProps) => { 29 | return ( 30 | 31 | 32 | 33 | {children} 34 | 35 | 36 | 37 | ) 38 | } 39 | 40 | const CommandInput = React.forwardRef< 41 | React.ElementRef, 42 | React.ComponentPropsWithoutRef 43 | >(({ className, ...props }, ref) => ( 44 |
    45 | 46 | 54 |
    55 | )) 56 | 57 | CommandInput.displayName = CommandPrimitive.Input.displayName 58 | 59 | const CommandList = React.forwardRef< 60 | React.ElementRef, 61 | React.ComponentPropsWithoutRef 62 | >(({ className, ...props }, ref) => ( 63 | 68 | )) 69 | 70 | CommandList.displayName = CommandPrimitive.List.displayName 71 | 72 | const CommandEmpty = React.forwardRef< 73 | React.ElementRef, 74 | React.ComponentPropsWithoutRef 75 | >((props, ref) => ( 76 | 81 | )) 82 | 83 | CommandEmpty.displayName = CommandPrimitive.Empty.displayName 84 | 85 | const CommandGroup = React.forwardRef< 86 | React.ElementRef, 87 | React.ComponentPropsWithoutRef 88 | >(({ className, ...props }, ref) => ( 89 | 97 | )) 98 | 99 | CommandGroup.displayName = CommandPrimitive.Group.displayName 100 | 101 | const CommandSeparator = React.forwardRef< 102 | React.ElementRef, 103 | React.ComponentPropsWithoutRef 104 | >(({ className, ...props }, ref) => ( 105 | 110 | )) 111 | CommandSeparator.displayName = CommandPrimitive.Separator.displayName 112 | 113 | const CommandItem = React.forwardRef< 114 | React.ElementRef, 115 | React.ComponentPropsWithoutRef 116 | >(({ className, ...props }, ref) => ( 117 | 125 | )) 126 | 127 | CommandItem.displayName = CommandPrimitive.Item.displayName 128 | 129 | const CommandShortcut = ({ 130 | className, 131 | ...props 132 | }: React.HTMLAttributes) => { 133 | return ( 134 | 141 | ) 142 | } 143 | CommandShortcut.displayName = "CommandShortcut" 144 | 145 | export { 146 | Command, 147 | CommandDialog, 148 | CommandInput, 149 | CommandList, 150 | CommandEmpty, 151 | CommandGroup, 152 | CommandItem, 153 | CommandShortcut, 154 | CommandSeparator, 155 | } 156 | -------------------------------------------------------------------------------- /components/ui/navigation-menu.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu" 3 | import { cva } from "class-variance-authority" 4 | import { ChevronDown } from "lucide-react" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const NavigationMenu = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, children, ...props }, ref) => ( 12 | 20 | {children} 21 | 22 | 23 | )) 24 | NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName 25 | 26 | const NavigationMenuList = React.forwardRef< 27 | React.ElementRef, 28 | React.ComponentPropsWithoutRef 29 | >(({ className, ...props }, ref) => ( 30 | 38 | )) 39 | NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName 40 | 41 | const NavigationMenuItem = NavigationMenuPrimitive.Item 42 | 43 | const navigationMenuTriggerStyle = cva( 44 | "group inline-flex h-10 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" 45 | ) 46 | 47 | const NavigationMenuTrigger = React.forwardRef< 48 | React.ElementRef, 49 | React.ComponentPropsWithoutRef 50 | >(({ className, children, ...props }, ref) => ( 51 | 56 | {children}{" "} 57 | 62 | )) 63 | NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName 64 | 65 | const NavigationMenuContent = React.forwardRef< 66 | React.ElementRef, 67 | React.ComponentPropsWithoutRef 68 | >(({ className, ...props }, ref) => ( 69 | 77 | )) 78 | NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName 79 | 80 | const NavigationMenuLink = NavigationMenuPrimitive.Link 81 | 82 | const NavigationMenuViewport = React.forwardRef< 83 | React.ElementRef, 84 | React.ComponentPropsWithoutRef 85 | >(({ className, ...props }, ref) => ( 86 |
    87 | 95 |
    96 | )) 97 | NavigationMenuViewport.displayName = 98 | NavigationMenuPrimitive.Viewport.displayName 99 | 100 | const NavigationMenuIndicator = React.forwardRef< 101 | React.ElementRef, 102 | React.ComponentPropsWithoutRef 103 | >(({ className, ...props }, ref) => ( 104 | 112 |
    113 | 114 | )) 115 | NavigationMenuIndicator.displayName = 116 | NavigationMenuPrimitive.Indicator.displayName 117 | 118 | export { 119 | navigationMenuTriggerStyle, 120 | NavigationMenu, 121 | NavigationMenuList, 122 | NavigationMenuItem, 123 | NavigationMenuContent, 124 | NavigationMenuTrigger, 125 | NavigationMenuLink, 126 | NavigationMenuIndicator, 127 | NavigationMenuViewport, 128 | } 129 | -------------------------------------------------------------------------------- /components/Footer.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import type { FC } from "react" 4 | import Image from "next/image" 5 | import Link from "next/link" 6 | import { motion } from "framer-motion" 7 | import { getLegendaryPokemonForMonth } from "@/lib/utils" 8 | import { Button } from "@/components/ui/button" 9 | import { ChevronUp, Github, Twitter, ExternalLink } from "lucide-react" 10 | 11 | interface FooterProps { 12 | birdMonth: number | undefined 13 | beastMonth: number | undefined 14 | } 15 | 16 | const Footer: FC = ({ birdMonth, beastMonth }) => { 17 | const legendary = getLegendaryPokemonForMonth( 18 | ["Entei", "Suicune", "Raikou"], 19 | ["Zapdos", "Moltres", "Articuno"], 20 | beastMonth, 21 | birdMonth, 22 | ) 23 | 24 | const { currenBeast, currentBird } = legendary 25 | 26 | const scrollToTop = () => { 27 | window.scrollTo({ 28 | top: 0, 29 | behavior: "smooth", 30 | }) 31 | } 32 | 33 | return ( 34 |
    35 |
    36 |
    37 |
    38 |
    39 |
    40 | PokeMMO Tools 41 |
    42 | PokeMMO Tools 43 |
    44 |

    45 | A collection of tools to enhance your PokeMMO experience, created by the community, for the community. 46 |

    47 |
    48 | 53 |
    54 |
    55 | 56 |
    57 |

    PvP Tools

    58 |
      59 | {[{ 60 | name: "Team Randomizer", 61 | link: "/pvp/randomizer" 62 | }, 63 | { 64 | name: "Compendium", 65 | link: "/pvp/compendium" 66 | }, 67 | ].map((item, i) => ( 68 |
    • 69 | 70 | {item.name} 71 | 72 |
    • 73 | ))} 74 |
    75 |
    76 | 77 |
    78 |

    PvE Tools

    79 |
      80 | {[{ 81 | name: "Income Check", 82 | link: "/pve/incomecheck" 83 | }, 84 | { 85 | name: "Legendary Calendar", 86 | link: "/pve/legendCalendar" 87 | }, 88 | ].map((item, i) => ( 89 |
    • 90 | 91 | {item.name} 92 | 93 |
    • 94 | ))} 95 |
    96 |
    97 | 98 |
    99 |

    Legendary of the Month

    100 |
    101 |
    102 |

    BIRD

    103 | 104 | {currentBird 110 | 111 |

    {currentBird}

    112 |
    113 | 114 |
    115 |

    BEAST

    116 | 117 | {currenBeast 123 | 124 |

    {currenBeast}

    125 |
    126 |
    127 |
    128 |
    129 | 130 |
    131 |

    132 | © {new Date().getFullYear()} PokeMMO Tools. Not affiliated with PokeMMO. 133 |

    134 | 135 | 143 |
    144 |
    145 |
    146 | ) 147 | } 148 | 149 | export default Footer 150 | -------------------------------------------------------------------------------- /components/ui/dropdown-menu.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" 5 | import { Check, ChevronRight, Circle } from "lucide-react" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const DropdownMenu = DropdownMenuPrimitive.Root 10 | 11 | const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger 12 | 13 | const DropdownMenuGroup = DropdownMenuPrimitive.Group 14 | 15 | const DropdownMenuPortal = DropdownMenuPrimitive.Portal 16 | 17 | const DropdownMenuSub = DropdownMenuPrimitive.Sub 18 | 19 | const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup 20 | 21 | const DropdownMenuSubTrigger = React.forwardRef< 22 | React.ElementRef, 23 | React.ComponentPropsWithoutRef & { 24 | inset?: boolean 25 | } 26 | >(({ className, inset, children, ...props }, ref) => ( 27 | 36 | {children} 37 | 38 | 39 | )) 40 | DropdownMenuSubTrigger.displayName = 41 | DropdownMenuPrimitive.SubTrigger.displayName 42 | 43 | const DropdownMenuSubContent = React.forwardRef< 44 | React.ElementRef, 45 | React.ComponentPropsWithoutRef 46 | >(({ className, ...props }, ref) => ( 47 | 55 | )) 56 | DropdownMenuSubContent.displayName = 57 | DropdownMenuPrimitive.SubContent.displayName 58 | 59 | const DropdownMenuContent = React.forwardRef< 60 | React.ElementRef, 61 | React.ComponentPropsWithoutRef 62 | >(({ className, sideOffset = 4, ...props }, ref) => ( 63 | 64 | 73 | 74 | )) 75 | DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName 76 | 77 | const DropdownMenuItem = React.forwardRef< 78 | React.ElementRef, 79 | React.ComponentPropsWithoutRef & { 80 | inset?: boolean 81 | } 82 | >(({ className, inset, ...props }, ref) => ( 83 | 92 | )) 93 | DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName 94 | 95 | const DropdownMenuCheckboxItem = React.forwardRef< 96 | React.ElementRef, 97 | React.ComponentPropsWithoutRef 98 | >(({ className, children, checked, ...props }, ref) => ( 99 | 108 | 109 | 110 | 111 | 112 | 113 | {children} 114 | 115 | )) 116 | DropdownMenuCheckboxItem.displayName = 117 | DropdownMenuPrimitive.CheckboxItem.displayName 118 | 119 | const DropdownMenuRadioItem = React.forwardRef< 120 | React.ElementRef, 121 | React.ComponentPropsWithoutRef 122 | >(({ className, children, ...props }, ref) => ( 123 | 131 | 132 | 133 | 134 | 135 | 136 | {children} 137 | 138 | )) 139 | DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName 140 | 141 | const DropdownMenuLabel = React.forwardRef< 142 | React.ElementRef, 143 | React.ComponentPropsWithoutRef & { 144 | inset?: boolean 145 | } 146 | >(({ className, inset, ...props }, ref) => ( 147 | 156 | )) 157 | DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName 158 | 159 | const DropdownMenuSeparator = React.forwardRef< 160 | React.ElementRef, 161 | React.ComponentPropsWithoutRef 162 | >(({ className, ...props }, ref) => ( 163 | 168 | )) 169 | DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName 170 | 171 | const DropdownMenuShortcut = ({ 172 | className, 173 | ...props 174 | }: React.HTMLAttributes) => { 175 | return ( 176 | 180 | ) 181 | } 182 | DropdownMenuShortcut.displayName = "DropdownMenuShortcut" 183 | 184 | export { 185 | DropdownMenu, 186 | DropdownMenuTrigger, 187 | DropdownMenuContent, 188 | DropdownMenuItem, 189 | DropdownMenuCheckboxItem, 190 | DropdownMenuRadioItem, 191 | DropdownMenuLabel, 192 | DropdownMenuSeparator, 193 | DropdownMenuShortcut, 194 | DropdownMenuGroup, 195 | DropdownMenuPortal, 196 | DropdownMenuSub, 197 | DropdownMenuSubContent, 198 | DropdownMenuSubTrigger, 199 | DropdownMenuRadioGroup, 200 | } 201 | -------------------------------------------------------------------------------- /data/trainers.mock.data.json: -------------------------------------------------------------------------------- 1 | { 2 | "E4": { 3 | "Kanto": { 4 | "income": 66000 5 | }, 6 | "Johto": { 7 | "income": 66000 8 | }, 9 | "Hoenn": { 10 | "income": 66000 11 | }, 12 | "Sinnoh": { 13 | "income": 66000 14 | }, 15 | "Teselia": { 16 | "income": 66000 17 | } 18 | }, 19 | "SpecialTrainers": [ 20 | { 21 | "name": "Morimoto", 22 | "city": "Castelia City", 23 | "income": 15660, 24 | "region": "Teselia" 25 | }, 26 | { 27 | "name": "Cynthia", 28 | "city": "Undella Town", 29 | "income": 16560, 30 | "region": "Teselia" 31 | }, 32 | { 33 | "name": "Red", 34 | "city": "Mt. Silver", 35 | "income": 110400, 36 | "region": "Johto" 37 | } 38 | ], 39 | "GymLeaders": [ 40 | { 41 | "name": "Brock", 42 | "city": "Pewter City", 43 | "income": 8632, 44 | "region": "Kanto" 45 | }, 46 | { 47 | "name": "Misty", 48 | "city": "Cerulean City", 49 | "income": 8736, 50 | "region": "Kanto" 51 | }, 52 | { 53 | "name": "Lt. Surge", 54 | "city": "Vermilion City", 55 | "income": 8840, 56 | "region": "Kanto" 57 | }, 58 | { 59 | "name": "Erika", 60 | "city": "Celadon City", 61 | "income": 8944, 62 | "region": "Kanto" 63 | }, 64 | { 65 | "name": "Koga", 66 | "city": "Fuchsia City", 67 | "income": 9048, 68 | "region": "Kanto" 69 | }, 70 | { 71 | "name": "Sabrina", 72 | "city": "Saffron City", 73 | "income": 9152, 74 | "region": "Kanto" 75 | }, 76 | { 77 | "name": "Braine", 78 | "city": "Cinnabar Island", 79 | "income": 9256, 80 | "region": "Kanto" 81 | }, 82 | { 83 | "name": "Roxanne", 84 | "city": "Rustboro City", 85 | "income": 8632, 86 | "region": "Hoenn" 87 | }, 88 | { 89 | "name": "Brawly", 90 | "city": "Dewford Town", 91 | "income": 8736, 92 | "region": "Hoenn" 93 | }, 94 | { 95 | "name": "Wattson", 96 | "city": "Mauville City", 97 | "income": 8840, 98 | "region": "Hoenn" 99 | }, 100 | { 101 | "name": "Flannery", 102 | "city": "Lavaridge Town", 103 | "income": 8944, 104 | "region": "Hoenn" 105 | }, 106 | { 107 | "name": "Norman", 108 | "city": "Petalburg City", 109 | "income": 9048, 110 | "region": "Hoenn" 111 | }, 112 | { 113 | "name": "Winona", 114 | "city": "Fortree City", 115 | "income": 9152, 116 | "region": "Hoenn" 117 | }, 118 | { 119 | "name": "Liza and Tate", 120 | "city": "Mossdeep City", 121 | "income": 9256, 122 | "region": "Hoenn" 123 | }, 124 | { 125 | "name": "Juan", 126 | "city": "Sootopolis City", 127 | "income": 9360, 128 | "region": "Hoenn" 129 | }, 130 | { 131 | "name": "Falkner", 132 | "city": "Violet City", 133 | "income": 8632, 134 | "region": "Johto" 135 | }, 136 | { 137 | "name": "Bugsy", 138 | "city": "Azalea Town", 139 | "income": 8736, 140 | "region": "Johto" 141 | }, 142 | { 143 | "name": "Whitney", 144 | "city": "Goldenrod City", 145 | "income": 8840, 146 | "region": "Johto" 147 | }, 148 | { 149 | "name": "Morty", 150 | "city": "Ecruteak City", 151 | "income": 8944, 152 | "region": "Johto" 153 | }, 154 | { 155 | "name": "Chuck", 156 | "city": "Cianwood City", 157 | "income": 9048, 158 | "region": "Johto" 159 | }, 160 | { 161 | "name": "Jasmine", 162 | "city": "Olivine City", 163 | "income": 9152, 164 | "region": "Johto" 165 | }, 166 | { 167 | "name": "Pryce", 168 | "city": "Mahogany Town", 169 | "income": 9256, 170 | "region": "Johto" 171 | }, 172 | { 173 | "name": "Clair", 174 | "city": "Blackthorn City", 175 | "income": 9360, 176 | "region": "Johto" 177 | }, 178 | { 179 | "name": "Red", 180 | "city": "Mt. Silver", 181 | "income": 110400, 182 | "region": "Johto" 183 | }, 184 | { 185 | "name": "Roark", 186 | "city": "Oreburgh City", 187 | "income": 8632, 188 | "region": "Sinnoh" 189 | }, 190 | { 191 | "name": "Gardenia", 192 | "city": "Eterna City", 193 | "income": 8736, 194 | "region": "Sinnoh" 195 | }, 196 | { 197 | "name": "Fantina", 198 | "city": "Hearthome City", 199 | "income": 8840, 200 | "region": "Sinnoh" 201 | }, 202 | { 203 | "name": "Maylene", 204 | "city": "Veilstone City", 205 | "income": 8944, 206 | "region": "Sinnoh" 207 | }, 208 | { 209 | "name": "Crasher Wake", 210 | "city": "Pastoria City", 211 | "income": 9048, 212 | "region": "Sinnoh" 213 | }, 214 | { 215 | "name": "Byron", 216 | "city": "Canalave City", 217 | "income": 9152, 218 | "region": "Sinnoh" 219 | }, 220 | { 221 | "name": "Candice", 222 | "city": "Snowpoint City", 223 | "income": 9256, 224 | "region": "Sinnoh" 225 | }, 226 | { 227 | "name": "Volkner", 228 | "city": "Sunyshore City", 229 | "income": 9360, 230 | "region": "Sinnoh" 231 | }, 232 | { 233 | "name": "Chili", 234 | "city": "Striaton City", 235 | "income": 8632, 236 | "region": "Teselia" 237 | }, 238 | { 239 | "name": "Cilan", 240 | "city": "Striaton City", 241 | "income": 8632, 242 | "region": "Teselia" 243 | }, 244 | { 245 | "name": "Cress", 246 | "city": "Striaton City", 247 | "income": 8632, 248 | "region": "Teselia" 249 | }, 250 | { 251 | "name": "Lenora", 252 | "city": "Nacrene City", 253 | "income": 8736, 254 | "region": "Teselia" 255 | }, 256 | { 257 | "name": "Burgh", 258 | "city": "Castelia City", 259 | "income": 8840, 260 | "region": "Teselia" 261 | }, 262 | { 263 | "name": "Elesa", 264 | "city": "Nimbasa City", 265 | "income": 8944, 266 | "region": "Teselia" 267 | }, 268 | { 269 | "name": "Clay", 270 | "city": "Driftveil City", 271 | "income": 9048, 272 | "region": "Teselia" 273 | }, 274 | { 275 | "name": "Skyla", 276 | "city": "Mistralton City", 277 | "income": 9152, 278 | "region": "Teselia" 279 | }, 280 | { 281 | "name": "Brycen", 282 | "city": "Icirrus City", 283 | "income": 9256, 284 | "region": "Teselia" 285 | }, 286 | { 287 | "name": "Iris", 288 | "city": "Opelucid City", 289 | "income": 9360, 290 | "region": "Teselia" 291 | }, 292 | { 293 | "name": "Morimoto", 294 | "city": "Castelia City", 295 | "income": 15660, 296 | "region": "Teselia" 297 | }, 298 | { 299 | "name": "Cynthia", 300 | "city": "Undella Town", 301 | "income": 16560, 302 | "region": "Teselia" 303 | } 304 | ] 305 | } -------------------------------------------------------------------------------- /components/ui/menubar.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as MenubarPrimitive from "@radix-ui/react-menubar" 5 | import { Check, ChevronRight, Circle } from "lucide-react" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const MenubarMenu = MenubarPrimitive.Menu 10 | 11 | const MenubarGroup = MenubarPrimitive.Group 12 | 13 | const MenubarPortal = MenubarPrimitive.Portal 14 | 15 | const MenubarSub = MenubarPrimitive.Sub 16 | 17 | const MenubarRadioGroup = MenubarPrimitive.RadioGroup 18 | 19 | const Menubar = React.forwardRef< 20 | React.ElementRef, 21 | React.ComponentPropsWithoutRef 22 | >(({ className, ...props }, ref) => ( 23 | 31 | )) 32 | Menubar.displayName = MenubarPrimitive.Root.displayName 33 | 34 | const MenubarTrigger = React.forwardRef< 35 | React.ElementRef, 36 | React.ComponentPropsWithoutRef 37 | >(({ className, ...props }, ref) => ( 38 | 46 | )) 47 | MenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName 48 | 49 | const MenubarSubTrigger = React.forwardRef< 50 | React.ElementRef, 51 | React.ComponentPropsWithoutRef & { 52 | inset?: boolean 53 | } 54 | >(({ className, inset, children, ...props }, ref) => ( 55 | 64 | {children} 65 | 66 | 67 | )) 68 | MenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName 69 | 70 | const MenubarSubContent = React.forwardRef< 71 | React.ElementRef, 72 | React.ComponentPropsWithoutRef 73 | >(({ className, ...props }, ref) => ( 74 | 82 | )) 83 | MenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName 84 | 85 | const MenubarContent = React.forwardRef< 86 | React.ElementRef, 87 | React.ComponentPropsWithoutRef 88 | >( 89 | ( 90 | { className, align = "start", alignOffset = -4, sideOffset = 8, ...props }, 91 | ref 92 | ) => ( 93 | 94 | 105 | 106 | ) 107 | ) 108 | MenubarContent.displayName = MenubarPrimitive.Content.displayName 109 | 110 | const MenubarItem = React.forwardRef< 111 | React.ElementRef, 112 | React.ComponentPropsWithoutRef & { 113 | inset?: boolean 114 | } 115 | >(({ className, inset, ...props }, ref) => ( 116 | 125 | )) 126 | MenubarItem.displayName = MenubarPrimitive.Item.displayName 127 | 128 | const MenubarCheckboxItem = React.forwardRef< 129 | React.ElementRef, 130 | React.ComponentPropsWithoutRef 131 | >(({ className, children, checked, ...props }, ref) => ( 132 | 141 | 142 | 143 | 144 | 145 | 146 | {children} 147 | 148 | )) 149 | MenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName 150 | 151 | const MenubarRadioItem = React.forwardRef< 152 | React.ElementRef, 153 | React.ComponentPropsWithoutRef 154 | >(({ className, children, ...props }, ref) => ( 155 | 163 | 164 | 165 | 166 | 167 | 168 | {children} 169 | 170 | )) 171 | MenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName 172 | 173 | const MenubarLabel = React.forwardRef< 174 | React.ElementRef, 175 | React.ComponentPropsWithoutRef & { 176 | inset?: boolean 177 | } 178 | >(({ className, inset, ...props }, ref) => ( 179 | 188 | )) 189 | MenubarLabel.displayName = MenubarPrimitive.Label.displayName 190 | 191 | const MenubarSeparator = React.forwardRef< 192 | React.ElementRef, 193 | React.ComponentPropsWithoutRef 194 | >(({ className, ...props }, ref) => ( 195 | 200 | )) 201 | MenubarSeparator.displayName = MenubarPrimitive.Separator.displayName 202 | 203 | const MenubarShortcut = ({ 204 | className, 205 | ...props 206 | }: React.HTMLAttributes) => { 207 | return ( 208 | 215 | ) 216 | } 217 | MenubarShortcut.displayname = "MenubarShortcut" 218 | 219 | export { 220 | Menubar, 221 | MenubarMenu, 222 | MenubarTrigger, 223 | MenubarContent, 224 | MenubarItem, 225 | MenubarSeparator, 226 | MenubarLabel, 227 | MenubarCheckboxItem, 228 | MenubarRadioGroup, 229 | MenubarRadioItem, 230 | MenubarPortal, 231 | MenubarSubContent, 232 | MenubarSubTrigger, 233 | MenubarGroup, 234 | MenubarSub, 235 | MenubarShortcut, 236 | } 237 | -------------------------------------------------------------------------------- /app/pve/incomecheck/DataTable.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @next/next/no-img-element */ 2 | "use client"; 3 | 4 | import { 5 | ColumnDef, 6 | flexRender, 7 | getCoreRowModel, 8 | getPaginationRowModel, 9 | getFilteredRowModel, 10 | useReactTable, 11 | ColumnFiltersState, 12 | getSortedRowModel, 13 | SortingState, 14 | getFacetedUniqueValues, 15 | } from "@tanstack/react-table" 16 | 17 | import { 18 | Table, 19 | TableBody, 20 | TableCaption, 21 | TableCell, 22 | TableHead, 23 | TableHeader, 24 | TableRow, 25 | } from "@/components/ui/table" 26 | import { Button } from "@/components/ui/button"; 27 | import { Input } from "@/components/ui/input"; 28 | import React, { useMemo, useState } from "react"; 29 | import richesCharm from '@/components/imgs/richesCharm.png'; 30 | import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; 31 | 32 | 33 | interface DataTableProps { 34 | columns: ColumnDef[] 35 | data: TData[] 36 | } 37 | 38 | export function DataTable({ 39 | columns, 40 | data, 41 | 42 | }: DataTableProps) { 43 | const [columnFilters, setColumnFilters] = React.useState([]); 44 | const [sorting, setSorting] = React.useState([]); 45 | const [initialRegion, setIniiialRegion] = useState('All Regions'); 46 | 47 | const [amuletCoin, setAmuletCoin] = useState(0); 48 | const [richesCharm75, setRichesCharm75] = useState(0); 49 | const [richesCharm100, setRichesCharm100] = useState(0); 50 | const [numberOfAmuletCoin, setNumberOfAmuletCoin] = useState(1); 51 | const [numberOfRichesCharm75, setNumberOfRichesCharm75] = useState(1); 52 | const [numberOfRichesCharm100, setNumberOfRichesCharm100] = useState(1); 53 | 54 | const regionsObj = [ 55 | "Kanto", 56 | "Johto", 57 | "Hoenn", 58 | "Sinnoh", 59 | "Teselia" 60 | ] 61 | 62 | const checkboxObj = useMemo(() => [ 63 | { 64 | name: 'No-Boost', 65 | amountOfItems: 0, 66 | img: 'https://marriland.com/wp-content/plugins/marriland-core/images/pokemon/sprites/home/full/unown.png', 67 | initialValue: 0, 68 | costValue: 0, 69 | multiplier: 1, 70 | }, 71 | { 72 | name: 'Amulet Coin', 73 | img: 'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/items/amulet-coin.png', 74 | initialValue: 17000, 75 | amountOfItems: numberOfAmuletCoin, 76 | costValue: amuletCoin, 77 | multiplier: 1.5, 78 | }, 79 | { 80 | name: 'Riches Charm 75%', 81 | img: richesCharm.src, 82 | initialValue: 64000, 83 | amountOfItems: numberOfRichesCharm75, 84 | costValue: richesCharm75, 85 | multiplier: 1.75, 86 | }, 87 | { 88 | name: 'Riches Charm 100%', 89 | img: richesCharm.src, 90 | initialValue: 98000, 91 | amountOfItems: numberOfRichesCharm100, 92 | costValue: richesCharm100, 93 | multiplier: 2, 94 | } 95 | ], [ 96 | numberOfAmuletCoin, 97 | amuletCoin, 98 | numberOfRichesCharm75, 99 | richesCharm75, 100 | numberOfRichesCharm100, 101 | richesCharm100, 102 | ]); 103 | 104 | const table = useReactTable({ 105 | data, 106 | columns, 107 | getCoreRowModel: getCoreRowModel(), 108 | getPaginationRowModel: getPaginationRowModel(), 109 | onColumnFiltersChange: setColumnFilters, 110 | getFilteredRowModel: getFilteredRowModel(), 111 | enableRowSelection: true, 112 | onSortingChange: setSorting, 113 | state: { 114 | columnFilters, 115 | }, 116 | initialState: { 117 | 118 | pagination: { 119 | pageSize: 8, 120 | } 121 | }, 122 | }); 123 | 124 | // Calcuate income of every selected row 125 | 126 | const selectedRows = table.getSelectedRowModel().flatRows; 127 | const selectedRowsBaseIncome = table.getSelectedRowModel().flatRows.map((row) => row.original.trainers.income); 128 | const calculateIncome = (multiplier: number, initialCost: number, CostValue = 0, amountOfItems: number) => { 129 | 130 | // if theres no initial value just calculate based of the default GTL cost 131 | 132 | if (selectedRows.length > 0 && CostValue === 0) { 133 | return table.getSelectedRowModel().flatRows.map((row) => row.original.trainers.income).reduce((a, b) => (a + b), 0) * multiplier - initialCost * amountOfItems; 134 | } 135 | 136 | // if user inputed a GTL cost calculate based of that 137 | 138 | if (selectedRows.length > 0 && CostValue !== 0) { 139 | return table.getSelectedRowModel().flatRows.map((row) => row.original.trainers.income).reduce((a, b) => (a + b), 0) * multiplier - CostValue * amountOfItems; 140 | } 141 | 142 | return 0; 143 | }; 144 | 145 | 146 | return ( 147 |
    148 |
    149 | 153 | table.getColumn("name")?.setFilterValue(event.target.value) 154 | } 155 | className="max-w-sm" 156 | /> 157 | 158 | 159 | 162 | 163 | 164 | {regionsObj.map((region, index) => { 165 | return ( 166 | { 170 | table.getColumn("region")?.setFilterValue(checked ? region : undefined); 171 | setIniiialRegion(checked ? region : 'All Regions'); 172 | }} 173 | > 174 | {region} 175 | 176 | ) 177 | })} 178 | 179 | 180 |
    181 |
    182 | 183 | 184 | {table.getHeaderGroups().map((headerGroup) => ( 185 | 186 | {headerGroup.headers.map((header) => { 187 | return ( 188 | 189 | {header.isPlaceholder 190 | ? null 191 | : flexRender( 192 | header.column.columnDef.header, 193 | header.getContext() 194 | )} 195 | 196 | ) 197 | })} 198 | 199 | ))} 200 | 201 | 202 | {table.getRowModel().rows?.length ? ( 203 | table.getRowModel().rows.map((row) => ( 204 | 208 | {row.getVisibleCells().map((cell) => ( 209 | 210 | {flexRender(cell.column.columnDef.cell, cell.getContext())} 211 | 212 | ))} 213 | 214 | )) 215 | ) : ( 216 | 217 | 218 | No results. 219 | 220 | 221 | )} 222 | 223 |
    224 |
    225 |
    226 | 234 | 242 |
    243 |

    Input GTL Cost

    244 |
    245 |
    246 | 247 |

    Amulet Coin

    248 | 252 | setAmuletCoin(Number(event.target.value)) 253 | } 254 | className="sm:w-[100px] w-[50px]" 255 | /> 256 |

    Number of Coins

    257 | setNumberOfAmuletCoin(Number(event.target.value))} className="sm:w-[100px] w-[50px]" /> 258 | 259 |
    260 |
    261 |

    Charm 75%

    262 | 266 | setRichesCharm75(Number(event.target.value)) 267 | } 268 | className="sm:w-[100px] w-[50px]" 269 | /> 270 |

    Number of Booster

    271 | setNumberOfRichesCharm75(Number(event.target.value))} className="sm:w-[100px] w-[50px]" /> 272 |
    273 |
    274 |

    Charm 100%

    275 | 279 | setRichesCharm100(Number(event.target.value)) 280 | } 281 | className="sm:w-[100px] w-[50px]" 282 | /> 283 |

    Number of Booster

    284 | setNumberOfRichesCharm100(Number(event.target.value))} className="sm:w-[100px] w-[50px]" /> 285 |
    286 |
    287 |
    288 | 289 | Total Income 290 | 291 | 292 | Boost 293 | Multiplier 294 | Amount Used 295 | Number of Gyms 296 | Gym Base Income * Multiplier 297 | Booster Cost 298 | Profit Amount 299 | 300 | 301 | 302 | {checkboxObj.map((item, index) => { 303 | return ( 304 | 305 | 306 | {item.name} 307 | {item.name} 308 | 309 | {item.multiplier} 310 | {item.amountOfItems} 311 | {selectedRows.length} 312 | {selectedRowsBaseIncome.reduce((a, b) => (a + b), 0) * item.multiplier} 313 | {item.costValue === 0 ? item.initialValue * item.amountOfItems : item.costValue * item.amountOfItems} 314 | {calculateIncome(item.multiplier, item.initialValue, item.costValue, item.amountOfItems)}¥ 322 | 323 | ) 324 | })} 325 | 326 |
    327 |
    328 |
    329 | ) 330 | } -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | import Footer from "@/components/Footer" 3 | import LemonMantis5571 from "@/components/imgs/LemonMantis5571.png" 4 | import { Badge } from "@/components/ui/badge" 5 | import { Button } from "@/components/ui/button" 6 | import { motion, useScroll, useTransform, AnimatePresence } from "framer-motion" 7 | import { Github, Zap } from "lucide-react" 8 | import Image from "next/image" 9 | import { useRouter } from "next/navigation" 10 | import { useRef, useEffect, useState } from "react" 11 | 12 | /* eslint-disable @next/next/no-img-element */ 13 | export default function Home() { 14 | const router = useRouter() 15 | const [isLoaded, setIsLoaded] = useState(false) 16 | 17 | 18 | 19 | 20 | // Main scroll progress 21 | const { scrollYProgress } = useScroll() 22 | const opacity = useTransform(scrollYProgress, [0, 0.2], [1, 0.8]) 23 | 24 | // FAQ items for staggered animation 25 | const faqItems = [ 26 | { 27 | question: "What is PokeMMO?", 28 | answer: 29 | "PokeMMO is a free to play mmorpg, come join a growing community as you level up and discover new monsters.", 30 | }, 31 | { 32 | question: "What is this tool used for?", 33 | answer: 34 | "PokeMMO Utilities is a tool to help you to maximize your farming or have fun. You can timestamp your gym runs or use the randomizer to generate a troll team.", 35 | }, 36 | { 37 | question: "PokeMMO already has an random pvp mode.", 38 | answer: 39 | "Quite right, but it isn't completely random, PokeMMO makes all moves and builds playable, this version truly randomizes and makes it a challenge to win with.", 40 | }, 41 | { 42 | question: "Hey, why this pokemon doesn't match the tier from the game?", 43 | answer: 44 | "Sadly PokeMMO doesn't have a way to get the tier of a pokemon, so I have to manually set the correct tier, this could take a while.", 45 | }, 46 | ] 47 | 48 | 49 | useEffect(() => { 50 | setIsLoaded(true) 51 | }, []) 52 | 53 | 54 | const containerVariants = { 55 | hidden: { opacity: 0 }, 56 | visible: { 57 | opacity: 1, 58 | transition: { 59 | when: "beforeChildren", 60 | staggerChildren: 0.2, 61 | duration: 0.3, 62 | }, 63 | }, 64 | } 65 | 66 | const itemVariants = { 67 | hidden: { y: 20, opacity: 0 }, 68 | visible: { 69 | y: 0, 70 | opacity: 1, 71 | transition: { duration: 0.5 }, 72 | }, 73 | } 74 | 75 | const fadeInVariants = { 76 | hidden: { opacity: 0 }, 77 | visible: { 78 | opacity: 1, 79 | transition: { duration: 0.8 }, 80 | }, 81 | } 82 | 83 | const slideInVariants = { 84 | hidden: { x: -50, opacity: 0 }, 85 | visible: { 86 | x: 0, 87 | opacity: 1, 88 | transition: { duration: 0.5 }, 89 | }, 90 | } 91 | 92 | return ( 93 | 94 | {isLoaded && ( 95 | 101 | {/* Hero Section */} 102 | 109 | 110 | 114 | PokeMMO Tools 115 | 116 | 117 | Compilation of tools to help make your experience on PokeMMO a little bit better with PvP or PvE. 118 | 119 | 120 | 137 | 138 | 139 | 140 | 141 | {/* FAQ Section */} 142 | 149 | 150 | 154 | FAQ 155 | 156 | 157 | {faqItems.map((item, index) => ( 158 | 165 | {item.question} 166 | 170 | {item.answer} 171 | 172 | 173 | ))} 174 | 175 | 176 | 188 | 189 | 190 | 191 | 198 |
    199 |
    200 | 201 | 207 | 208 | Available for Projects 209 | 210 | 211 | 212 | 219 | Meet the Developer 220 | 221 | 222 | 229 | LemonMantis5571 230 | 231 | 232 | 239 | I'm LemonMantis5571, a developer and avid PokeMMO PvP player. I created these tools to enhance the 240 | PokeMMO experience for myself and the community. If you have ideas for specific tools or features, 241 | I'm always open to suggestions and collaborations. 242 | 243 | 244 | 251 | I speak Spanish, English, and some Chinese, so feel free to reach out in any of these languages! 252 | 253 | 254 | 261 | 268 | 272 | 273 | 280 | 284 | 285 | 286 | 287 | 288 | 295 | 300 | 311 |
    312 | LemonMantis5571 319 |
    320 |
    321 |
    322 |
    323 |
    324 |
    325 | 326 | 327 | 334 |
    335 | 339 | 345 | Shiny Bidoof 351 | 352 | 353 | 354 | 359 | Disclaimer 360 | 361 | 366 | Some data may be inaccurate. PokeMMO does not provide any API or help to developers. This page is 367 | not affiliated with PokeMMO by any means. 368 | 369 | 370 | 371 |
    372 |
    373 | 374 |