├── .eslintrc.json ├── .gitignore ├── .vscode └── settings.json ├── README.md ├── next.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── next.svg └── vercel.svg ├── src ├── app │ ├── api │ │ └── hello │ │ │ └── route.ts │ ├── favicon.ico │ ├── globals.css │ ├── layout.tsx │ ├── page.tsx │ └── pokemon │ │ └── [slug] │ │ └── page.tsx ├── components │ ├── animated-value.tsx │ ├── command-search.tsx │ ├── icons.tsx │ ├── loading.tsx │ ├── main-nav.tsx │ ├── pokemon │ │ ├── pokedata-card.tsx │ │ ├── pokemon-card.tsx │ │ ├── pokemon-details.tsx │ │ ├── pokemon-list.tsx │ │ └── species-info.tsx │ ├── query-provider.tsx │ ├── ratio-bar.tsx │ ├── site-footer.tsx │ ├── site-header.tsx │ ├── stats-bar.tsx │ ├── store │ │ └── store.ts │ └── ui │ │ ├── aspect-ratio.tsx │ │ ├── badge.tsx │ │ ├── button.tsx │ │ ├── card.tsx │ │ ├── command.tsx │ │ ├── dialog.tsx │ │ └── separator.tsx ├── hooks │ └── use-pokeapi.ts ├── lib │ ├── data │ │ └── pokemon-search.ts │ ├── gsap │ │ └── index.ts │ └── utils.ts └── types.d.ts ├── tailwind.config.js └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.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 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | next-env.d.ts 36 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib", 3 | "typescript.enablePromptUseWorkspaceTsdk": true 4 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # another-pokedex 3 | 4 | ## Introduction 5 | Another pokedex app ... BUT!!! Using new shiny tools 🤩 6 | 7 | yes, this is another pokedex app, but after learning from my previous projects, and the amazing tools that have been coming out, I wanted to revisit an rebuild one of my first web app that introduced me to web development. 8 | 9 | **project features Includes**: 10 | 11 | - Caching 12 | - Animations 13 | - Pagination 14 | - Search 15 | 16 | **TODO**: 17 | 18 | 19 | - Favorites 20 | - PWA 21 | 22 | 23 | **Current Status** : Work In Progress 24 | 25 | 26 | ## Tech Stack 27 | 28 | In this project I utilized: 29 | * Tailwind 30 | * React 31 | * Next-13 32 | * Gsap 33 | * Zustand 34 | * [Tanstack React Query](https://tanstack.com/query/latest/) 35 | * [Shadcn/ui](https://ui.shadcn.com/) 36 | 37 | 38 | 39 | 40 | ## Screenshots 41 | 42 | ![App Screenshot1](https://media.discordapp.net/attachments/801156110752284702/1102697440706056222/Screenshot_2023-05-01_at_4.45.51_PM.png?width=1820&height=1050) 43 | 44 | ![App Screenshot2](https://media.discordapp.net/attachments/801156110752284702/1102697269968511137/Screenshot_2023-05-01_at_4.44.44_PM.png?width=1820&height=1050) 45 | 46 | 47 | ## Roadmap 48 | 49 | - File cleanup and refactoring code 50 | 51 | - Update visuals and graphics 52 | 53 | - Add more interactivity 54 | 55 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | experimental: { 4 | appDir: true, 5 | }, 6 | } 7 | 8 | module.exports = nextConfig 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "another-pokedex", 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 | }, 11 | "dependencies": { 12 | "@radix-ui/react-aspect-ratio": "^1.0.2", 13 | "@radix-ui/react-dialog": "^1.0.3", 14 | "@radix-ui/react-progress": "^1.0.2", 15 | "@radix-ui/react-separator": "^1.0.2", 16 | "@tanstack/react-query": "^4.29.5", 17 | "@tanstack/react-query-devtools": "^4.29.6", 18 | "@types/node": "18.16.1", 19 | "@types/react": "18.2.0", 20 | "@types/react-dom": "18.2.1", 21 | "autoprefixer": "10.4.14", 22 | "axios": "^1.3.6", 23 | "class-variance-authority": "^0.6.0", 24 | "clsx": "^1.2.1", 25 | "cmdk": "^0.2.0", 26 | "eslint": "8.39.0", 27 | "eslint-config-next": "13.3.1", 28 | "gsap": "^3.11.5", 29 | "lucide-react": "^0.182.0", 30 | "next": "13.3.1", 31 | "postcss": "8.4.23", 32 | "react": "18.2.0", 33 | "react-dom": "18.2.0", 34 | "tailwind-merge": "^1.12.0", 35 | "tailwindcss": "3.3.2", 36 | "tailwindcss-animate": "^1.0.5", 37 | "typescript": "5.0.4", 38 | "zustand": "^4.3.7" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/api/hello/route.ts: -------------------------------------------------------------------------------- 1 | export async function GET(request: Request) { 2 | return new Response('Hello, Next.js!') 3 | } 4 | -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kyuuari/another-pokedex/14022481c688ed738ff0bdb34ae405b760a9784e/src/app/favicon.ico -------------------------------------------------------------------------------- /src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --background: 0 0% 100%; 8 | --foreground: 222.2 47.4% 11.2%; 9 | 10 | --muted: 210 40% 96.1%; 11 | --muted-foreground: 215.4 16.3% 46.9%; 12 | 13 | --popover: 0 0% 100%; 14 | --popover-foreground: 222.2 47.4% 11.2%; 15 | 16 | --card: 0 0% 100%; 17 | --card-foreground: 222.2 47.4% 11.2%; 18 | 19 | --border: 214.3 31.8% 91.4%; 20 | --input: 214.3 31.8% 91.4%; 21 | 22 | --primary: 222.2 47.4% 11.2%; 23 | --primary-foreground: 210 40% 98%; 24 | 25 | --secondary: 210 40% 96.1%; 26 | --secondary-foreground: 222.2 47.4% 11.2%; 27 | 28 | --accent: 210 40% 96.1%; 29 | --accent-foreground: 222.2 47.4% 11.2%; 30 | 31 | --destructive: 0 100% 50%; 32 | --destructive-foreground: 210 40% 98%; 33 | 34 | --ring: 215 20.2% 65.1%; 35 | 36 | --radius: 0.5rem; 37 | } 38 | 39 | .dark { 40 | --background: 224 71% 4%; 41 | --foreground: 213 31% 91%; 42 | 43 | --muted: 223 47% 11%; 44 | --muted-foreground: 215.4 16.3% 56.9%; 45 | 46 | --popover: 224 71% 4%; 47 | --popover-foreground: 215 20.2% 65.1%; 48 | 49 | --card: 0 0% 100%; 50 | --card-foreground: 222.2 47.4% 11.2%; 51 | 52 | --border: 216 34% 17%; 53 | --input: 216 34% 17%; 54 | 55 | --primary: 210 40% 98%; 56 | --primary-foreground: 222.2 47.4% 1.2%; 57 | 58 | --secondary: 222.2 47.4% 11.2%; 59 | --secondary-foreground: 210 40% 98%; 60 | 61 | --accent: 216 34% 17%; 62 | --accent-foreground: 210 40% 98%; 63 | 64 | --destructive: 0 63% 31%; 65 | --destructive-foreground: 210 40% 98%; 66 | 67 | --ring: 216 34% 17%; 68 | 69 | --radius: 0.5rem; 70 | } 71 | } 72 | 73 | @layer base { 74 | * { 75 | @apply border-border; 76 | } 77 | body { 78 | @apply bg-background text-foreground; 79 | font-feature-settings: "rlig" 1, "calt" 1; 80 | } 81 | } -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import "./globals.css"; 2 | import { Inter } from "next/font/google"; 3 | import QueryProvider from "@/components/query-provider"; 4 | import SiteHeader from "@/components/site-header"; 5 | import { SiteFooter } from "@/components/site-footer"; 6 | 7 | const inter = Inter({ subsets: ["latin"] }); 8 | 9 | export const metadata = { 10 | title: "Another Pokedex", 11 | description: "Yet another pokemon app ... but, it uses new shiny tools 🤩", 12 | }; 13 | 14 | export default function RootLayout({ 15 | children, 16 | }: { 17 | children: React.ReactNode; 18 | }) { 19 | return ( 20 | 21 | 22 | 23 | {children} 24 | 25 | 26 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /src/app/page.tsx: -------------------------------------------------------------------------------- 1 | import PokemonList from "@/components/pokemon/pokemon-list"; 2 | 3 | export default function Home() { 4 | return ( 5 |
6 |
7 | 8 |
9 |
10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /src/app/pokemon/[slug]/page.tsx: -------------------------------------------------------------------------------- 1 | import PokemonDetails from "@/components/pokemon/pokemon-details"; 2 | import { Button } from "@/components/ui/button"; 3 | import Link from "next/link"; 4 | 5 | export default function Page({ params }: { params: { slug: string } }) { 6 | return ( 7 |
8 |
9 | 10 |
11 | 12 | 13 | 14 |
15 |
16 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/components/animated-value.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { useEffect, useRef } from "react"; 3 | import { gsap } from "@/lib/gsap/index"; 4 | 5 | type Props = { 6 | value: number; 7 | round?: number; 8 | }; 9 | 10 | export const AnimatedValue = ({ value, round }: Props) => { 11 | const valueRef = useRef(null); 12 | 13 | useEffect(() => { 14 | const context = gsap.context(() => { 15 | gsap.effects.counterAnimation( 16 | valueRef.current, 17 | { 18 | end: value, 19 | ease: "linear", 20 | increment: 0.5, 21 | round: round, 22 | // duration: 0.5, 23 | } 24 | // "-=0.02" 25 | ); 26 | }); 27 | 28 | return () => { 29 | context.revert(); 30 | }; 31 | }, []); 32 | 33 | return ( 34 | 35 | 0.00 36 | 37 | ); 38 | }; 39 | -------------------------------------------------------------------------------- /src/components/command-search.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { 3 | CommandDialog, 4 | CommandEmpty, 5 | CommandGroup, 6 | CommandInput, 7 | CommandItem, 8 | CommandList, 9 | } from "@/components/ui/command"; 10 | import { useCallback, useEffect, useState } from "react"; 11 | import { PokemonSearchData } from "@/lib/data/pokemon-search"; 12 | import { useRouter } from "next/navigation"; 13 | import { Button } from "./ui/button"; 14 | import { capitalize, cn } from "@/lib/utils"; 15 | import { DialogProps } from "@radix-ui/react-dialog"; 16 | 17 | export function CommandSearch({ ...props }: DialogProps) { 18 | const pokemonSearch = PokemonSearchData.results; 19 | const router = useRouter(); 20 | const [open, setOpen] = useState(false); 21 | 22 | useEffect(() => { 23 | const down = (e: KeyboardEvent) => { 24 | if (e.key === "k" && (e.metaKey || e.ctrlKey)) { 25 | e.preventDefault(); 26 | setOpen((open) => !open); 27 | } 28 | }; 29 | 30 | document.addEventListener("keydown", down); 31 | return () => document.removeEventListener("keydown", down); 32 | }, []); 33 | 34 | const runCommand = useCallback((command: () => unknown) => { 35 | setOpen(false); 36 | command(); 37 | }, []); 38 | 39 | return ( 40 | <> 41 | 55 | 56 | No results found. 57 | 58 | 59 | 60 | {pokemonSearch.map((pokemon, index) => ( 61 | { 64 | runCommand(() => router.push(`/pokemon/${pokemon.name}`)); 65 | }} 66 | > 67 | {capitalize(pokemon.name)} 68 | 69 | ))} 70 | 71 | 72 | 73 | 74 | ); 75 | } 76 | -------------------------------------------------------------------------------- /src/components/icons.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | AlertTriangle, 3 | ArrowRight, 4 | Check, 5 | ChevronLeft, 6 | ChevronRight, 7 | ClipboardCheck, 8 | Copy, 9 | CreditCard, 10 | File, 11 | FileText, 12 | HelpCircle, 13 | Image, 14 | Instagram, 15 | Laptop, 16 | Loader2, 17 | LucideProps, 18 | Moon, 19 | MoreVertical, 20 | Pizza, 21 | Plus, 22 | Settings, 23 | SunMedium, 24 | Trash, 25 | User, 26 | X, 27 | type Icon as LucideIcon, 28 | } from "lucide-react"; 29 | 30 | export type Icon = LucideIcon; 31 | 32 | export const Icons = { 33 | logo: (props: LucideProps) => ( 34 | 40 | 82 | 83 | ), 84 | close: X, 85 | spinner: Loader2, 86 | chevronLeft: ChevronLeft, 87 | chevronRight: ChevronRight, 88 | trash: Trash, 89 | post: FileText, 90 | page: File, 91 | media: Image, 92 | settings: Settings, 93 | billing: CreditCard, 94 | ellipsis: MoreVertical, 95 | add: Plus, 96 | warning: AlertTriangle, 97 | user: User, 98 | arrowRight: ArrowRight, 99 | help: HelpCircle, 100 | pizza: Pizza, 101 | check: Check, 102 | copy: Copy, 103 | copyDone: ClipboardCheck, 104 | sun: SunMedium, 105 | moon: Moon, 106 | laptop: Laptop, 107 | instagram: Instagram, 108 | gitHub: (props: LucideProps) => ( 109 | 110 | 114 | 115 | ), 116 | radix: (props: LucideProps) => ( 117 | 118 | 122 | 123 | 127 | 128 | ), 129 | aria: (props: LucideProps) => ( 130 | 131 | 132 | 133 | ), 134 | npm: (props: LucideProps) => ( 135 | 136 | 137 | 138 | ), 139 | yarn: (props: LucideProps) => ( 140 | 141 | 142 | 143 | ), 144 | pnpm: (props: LucideProps) => ( 145 | 146 | 147 | 148 | ), 149 | react: (props: LucideProps) => ( 150 | 151 | 152 | 153 | ), 154 | tailwind: (props: LucideProps) => ( 155 | 156 | 157 | 158 | ), 159 | google: (props: LucideProps) => ( 160 | 161 | 165 | 166 | ), 167 | apple: (props: LucideProps) => ( 168 | 169 | 173 | 174 | ), 175 | paypal: (props: LucideProps) => ( 176 | 177 | 181 | 182 | ), 183 | linkedin: (props: LucideProps) => ( 184 | 185 | 189 | 190 | ), 191 | behance: (props: LucideProps) => ( 192 | 193 | 197 | 198 | ), 199 | twitter: (props: LucideProps) => ( 200 | 201 | 205 | 206 | ), 207 | }; 208 | -------------------------------------------------------------------------------- /src/components/loading.tsx: -------------------------------------------------------------------------------- 1 | import { Icons } from "./icons"; 2 | 3 | type Props = {}; 4 | 5 | export default function loading({}: Props) { 6 | return ( 7 |
8 |
9 | 10 |

Loading

11 |
12 |
13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /src/components/main-nav.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import React from "react"; 3 | import { Icons } from "./icons"; 4 | 5 | type Props = {}; 6 | 7 | export default function MainNav({}: Props) { 8 | return ( 9 |
10 | 11 | 12 | {"Pokedex"} 13 | 14 |
15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /src/components/pokemon/pokedata-card.tsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense } from "react"; 2 | import { Card, CardContent, CardHeader, CardTitle } from "../ui/card"; 3 | import { AnimatedValue } from "../animated-value"; 4 | import { StatsBar } from "../stats-bar"; 5 | import { PokemonData } from "@/types"; 6 | import { capitalize } from "@/lib/utils"; 7 | import { Badge } from "../ui/badge"; 8 | import { Separator } from "../ui/separator"; 9 | 10 | type Props = { 11 | pokemonData: PokemonData; 12 | pokemonImageURL: string; 13 | }; 14 | 15 | export default function PokedataCard({ pokemonData, pokemonImageURL }: Props) { 16 | return ( 17 | 18 | 19 | 20 | 21 | {capitalize(pokemonData.name)} 22 | 23 | 24 | #{pokemonData.id ?? 0} 25 | 26 | 27 |
28 | {pokemonData.types.map((type, index) => ( 29 | 30 | {capitalize(type.type.name)} 31 | 32 | ))} 33 |
34 |
35 | Loading...
}> 36 | pokemon image 41 | 42 | 43 |
44 | 45 | 46 |
47 |
48 |

Height

49 |

50 | m 51 |

52 |
53 | 54 |
55 |

Weight

56 |

57 | kg 58 |

59 |
60 |
61 | 62 |
63 | {pokemonData.stats.map((stats, index) => ( 64 |
65 |

66 | {capitalize(stats.stat.name)} :{" "} 67 | 68 |

69 | 70 |
71 | ))} 72 |
73 |
74 |
75 | ); 76 | } 77 | -------------------------------------------------------------------------------- /src/components/pokemon/pokemon-card.tsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense } from "react"; 2 | 3 | import Link from "next/link"; 4 | import { capitalize } from "@/lib/utils"; 5 | import { Card, CardContent, CardHeader, CardTitle } from "../ui/card"; 6 | 7 | type Props = { 8 | pokemonName: string; 9 | pokemonImgUrl?: string; 10 | }; 11 | 12 | export default function PokemonCard({ pokemonName, pokemonImgUrl }: Props) { 13 | return ( 14 | 15 | 16 | 17 | {capitalize(pokemonName)} 18 | 19 | 20 | Loading...}> 21 | {`${pokemonName} 26 | 27 | 28 | 29 | 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /src/components/pokemon/pokemon-details.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import PokedataCard from "./pokedata-card"; 3 | import SpeciesInfo from "./species-info"; 4 | import { Suspense } from "react"; 5 | import Loading from "../loading"; 6 | import { findPokemonDBImage, useGetPokemon } from "@/hooks/use-pokeapi"; 7 | import { isError } from "@tanstack/react-query"; 8 | 9 | type Props = { 10 | pokemonName: string; 11 | }; 12 | 13 | export default function PokemonDetails({ pokemonName }: Props) { 14 | const { data, isLoading, isError } = useGetPokemon(pokemonName); 15 | 16 | if (isLoading) { 17 | return ; 18 | } 19 | 20 | if (isError) { 21 | return
No Data Found
; 22 | } 23 | 24 | return ( 25 |
26 | {data && ( 27 |
28 |
29 | 33 |
34 | 35 |
36 | }> 37 | 38 | 39 |
40 |
41 | )} 42 |
43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /src/components/pokemon/pokemon-list.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React, { useEffect, useState } from "react"; 3 | import PokemonCard from "./pokemon-card"; 4 | import { useGetAllPokemonPage } from "@/hooks/use-pokeapi"; 5 | import Loading from "../loading"; 6 | import { PokemonData } from "@/types"; 7 | import usePaginationStore from "../store/store"; 8 | import { Button } from "../ui/button"; 9 | 10 | type Props = {}; 11 | 12 | export default function PokemonList({}: Props) { 13 | const { currentPage, itemsPerPage, updatePagePosition } = 14 | usePaginationStore(); 15 | 16 | const { 17 | isLoading, 18 | data: pokemonPage, 19 | error, 20 | } = useGetAllPokemonPage(itemsPerPage, currentPage); 21 | 22 | const [pokemonList, setPokemonList] = useState([]); 23 | 24 | useEffect(() => { 25 | setPokemonList(pokemonPage ?? []); 26 | }, [pokemonPage]); 27 | 28 | if (isLoading) { 29 | return ; 30 | } 31 | 32 | if (error) { 33 | return
Error
; 34 | } 35 | 36 | return ( 37 |
38 | {pokemonList.map((pokemon, index) => ( 39 | 44 | ))} 45 | 46 |
47 | 50 | 53 |
54 |
55 | ); 56 | } 57 | -------------------------------------------------------------------------------- /src/components/pokemon/species-info.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { useGetSpeciesInfo } from "@/hooks/use-pokeapi"; 3 | import { capitalize } from "@/lib/utils"; 4 | import { PokemonData } from "@/types"; 5 | import React from "react"; 6 | import { RatioBar } from "../ratio-bar"; 7 | import { Separator } from "@radix-ui/react-separator"; 8 | import Loading from "../loading"; 9 | 10 | type Props = { 11 | pokemonData: PokemonData; 12 | }; 13 | 14 | export default function SpeciesInfo({ pokemonData }: Props) { 15 | const { data: speciesInfo, isLoading } = useGetSpeciesInfo( 16 | pokemonData.species.url 17 | ); 18 | const { flavor_text_entries, color, egg_groups, gender_rate, capture_rate } = 19 | speciesInfo ?? {}; 20 | 21 | const enText = flavor_text_entries?.find( 22 | (text) => text.language.name === "en" 23 | )?.flavor_text; 24 | const jpText = flavor_text_entries?.find( 25 | (text) => text.language.name === "ja-Hrkt" 26 | )?.flavor_text; 27 | 28 | const catchRate = Math.round((100 / 255) * speciesInfo?.capture_rate!); 29 | 30 | if (isLoading) { 31 | return ; 32 | } 33 | 34 | return ( 35 | <> 36 | {speciesInfo && ( 37 | 91 | )} 92 | 93 | ); 94 | } 95 | -------------------------------------------------------------------------------- /src/components/query-provider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; 3 | // import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; 4 | import { ReactNode, useState } from "react"; 5 | 6 | export default function QueryProvider({ children }: { children: ReactNode }) { 7 | const [queryClient] = useState(() => new QueryClient()); 8 | 9 | return ( 10 | 11 | {children} 12 | {/* */} 13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /src/components/ratio-bar.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | type Props = { 4 | value: number; 5 | }; 6 | 7 | export const RatioBar = ({ value }: Props) => { 8 | const femaleRate = value; 9 | const genderRatioFemale = 12.5 * femaleRate; 10 | const genderRatioMale = 12.5 * (8 - femaleRate); 11 | 12 | return ( 13 | <> 14 | {femaleRate > 0 ? ( 15 | {`Female: ${genderRatioFemale}% | Male: ${genderRatioMale}%`} 16 | ) : ( 17 | Genderless 18 | )} 19 | 20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /src/components/site-footer.tsx: -------------------------------------------------------------------------------- 1 | export function SiteFooter() { 2 | return ( 3 |
4 |
5 |
6 |

7 | Made with ❤️ 8 |

9 |
10 |
11 |
12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /src/components/site-header.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import MainNav from "./main-nav"; 3 | import { CommandSearch } from "./command-search"; 4 | import { Icons } from "./icons"; 5 | 6 | type Props = {}; 7 | 8 | export default function SiteHeader({}: Props) { 9 | return ( 10 |
11 |
12 | 13 |
14 | 21 | 22 | 23 | 30 | 31 | 32 |
33 | 34 |
35 |
36 |
37 |
38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /src/components/stats-bar.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React, { useEffect, useRef } from "react"; 3 | import gsap from "gsap"; 4 | 5 | type Props = { 6 | value: number; 7 | }; 8 | 9 | export const StatsBar = ({ value }: Props) => { 10 | const barRef = useRef(null!); 11 | 12 | useEffect(() => { 13 | gsap.to(barRef.current, { 14 | width: `${(value / 255) * 100}%`, 15 | duration: 0.7, 16 | ease: `rough({ template: bounce.out, strength: 1, points: 10, taper: out, randomize: true, clamp: false})`, 17 | }); 18 | }, [value]); 19 | 20 | return ( 21 |
22 |
27 |
28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /src/components/store/store.ts: -------------------------------------------------------------------------------- 1 | import { create } from "zustand"; 2 | 3 | type PaginationStore = { 4 | currentPage: number; 5 | itemsPerPage: number; 6 | setCurrentPage: (pageNumber: number) => void; 7 | setItemsPerPage: (itemsPerPage: number) => void; 8 | updatePagePosition: (pagePositionDelta: number) => void; 9 | }; 10 | 11 | const usePaginationStore = create((set) => ({ 12 | currentPage: 0, 13 | itemsPerPage: 12, 14 | setCurrentPage: (pageNumber: number) => 15 | set((state) => ({ 16 | ...state, 17 | currentPage: pageNumber < 0 ? 0 : pageNumber, 18 | })), 19 | setItemsPerPage: (itemsPerPage: number) => 20 | set((state) => ({ ...state, itemsPerPage: itemsPerPage })), 21 | updatePagePosition: (pagePositionDelta: number) => 22 | set((state) => ({ 23 | ...state, 24 | currentPage: 25 | state.currentPage + pagePositionDelta < 0 26 | ? 0 27 | : state.currentPage + pagePositionDelta, 28 | })), 29 | })); 30 | 31 | export default usePaginationStore; 32 | -------------------------------------------------------------------------------- /src/components/ui/aspect-ratio.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio" 4 | 5 | const AspectRatio = AspectRatioPrimitive.Root 6 | 7 | export { AspectRatio } 8 | -------------------------------------------------------------------------------- /src/components/ui/badge.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { VariantProps, cva } from "class-variance-authority"; 3 | 4 | import { cn } from "@/lib/utils"; 5 | 6 | const badgeVariants = cva( 7 | "inline-flex items-center border rounded-full 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: "bg-primary border-transparent text-primary-foreground", 12 | secondary: 13 | "bg-secondary hover:bg-secondary/80 border-transparent text-secondary-foreground", 14 | destructive: 15 | "bg-destructive hover:bg-destructive/80 border-transparent text-destructive-foreground", 16 | outline: "text-foreground", 17 | }, 18 | type: { 19 | normal: "bg-stone-500 border-transparent text-primary-foreground", 20 | fire: "bg-orange-500 border-transparent text-primary-foreground", 21 | water: "bg-sky-500 border-transparent text-primary-foreground", 22 | grass: "bg-green-600 border-transparent text-primary-foreground", 23 | electric: "bg-yellow-400 border-transparent text-primary-foreground", 24 | ice: "bg-blue-300 border-transparent text-primary-foreground", 25 | fighting: "bg-red-500 border-transparent text-primary-foreground", 26 | poison: "bg-fuchsia-600 border-transparent text-primary-foreground", 27 | ground: "bg-orange-300 border-transparent text-primary-foreground", 28 | 29 | flying: "bg-violet-300 border-transparent text-primary-foreground", 30 | psychic: "bg-pink-600 border-transparent text-primary-foreground", 31 | bug: "bg-lime-500 border-transparent text-primary-foreground", 32 | rock: "bg-stone-600 border-transparent text-primary-foreground", 33 | ghost: "bg-violet-400 border-transparent text-primary-foreground", 34 | dark: "bg-stone-800 border-transparent text-primary-foreground", 35 | dragon: "bg-violet-600 border-transparent text-primary-foreground", 36 | steel: "bg-slate-400 border-transparent text-primary-foreground", 37 | fairy: "bg-pink-300 border-transparent text-primary-foreground", 38 | }, 39 | }, 40 | defaultVariants: { 41 | variant: "default", 42 | }, 43 | } 44 | ); 45 | 46 | export interface BadgeProps 47 | extends React.HTMLAttributes, 48 | VariantProps {} 49 | 50 | function Badge({ className, variant, type, ...props }: BadgeProps) { 51 | return ( 52 |
56 | ); 57 | } 58 | 59 | export { Badge, badgeVariants }; 60 | -------------------------------------------------------------------------------- /src/components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { VariantProps, cva } from "class-variance-authority" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | const buttonVariants = cva( 7 | "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background", 8 | { 9 | variants: { 10 | variant: { 11 | default: "bg-primary text-primary-foreground hover:bg-primary/90", 12 | destructive: 13 | "bg-destructive text-destructive-foreground hover:bg-destructive/90", 14 | outline: 15 | "border border-input hover:bg-accent hover:text-accent-foreground", 16 | secondary: 17 | "bg-secondary text-secondary-foreground hover:bg-secondary/80", 18 | ghost: "hover:bg-accent hover:text-accent-foreground", 19 | link: "underline-offset-4 hover:underline text-primary", 20 | }, 21 | size: { 22 | default: "h-10 py-2 px-4", 23 | sm: "h-9 px-3 rounded-md", 24 | lg: "h-11 px-8 rounded-md", 25 | }, 26 | }, 27 | defaultVariants: { 28 | variant: "default", 29 | size: "default", 30 | }, 31 | } 32 | ) 33 | 34 | export interface ButtonProps 35 | extends React.ButtonHTMLAttributes, 36 | VariantProps {} 37 | 38 | const Button = React.forwardRef( 39 | ({ className, variant, size, ...props }, ref) => { 40 | return ( 41 |