├── .eslintrc.json ├── app ├── favicon.ico ├── globals.css ├── action.tsx ├── page.tsx ├── layout.tsx └── _data.ts ├── public ├── anime.png ├── hero.png ├── vercel.svg ├── star.svg ├── tiktok.svg ├── spinner.svg ├── next.svg ├── twitter.svg ├── episodes.svg ├── instagram.svg └── logo.svg ├── postcss.config.js ├── components ├── MotionDiv.tsx ├── Hero.tsx ├── Footer.tsx ├── LoadMore.tsx └── AnimeCard.tsx ├── README.md ├── next.config.js ├── .gitignore ├── tailwind.config.ts ├── .vscode └── settings.json ├── tsconfig.json └── package.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emredkyc/anime_world/HEAD/app/favicon.ico -------------------------------------------------------------------------------- /public/anime.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emredkyc/anime_world/HEAD/public/anime.png -------------------------------------------------------------------------------- /public/hero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emredkyc/anime_world/HEAD/public/hero.png -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /components/MotionDiv.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { motion } from "framer-motion"; 4 | 5 | export const MotionDiv = motion.div; 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Build Modern Next 14 Server Side App with Server Actions, Infinite Scroll & Framer Motion Animations 2 | 3 | anime-world-banner 4 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | images: { 4 | remotePatterns: [ 5 | { 6 | protocol: "https", 7 | hostname: "*", 8 | }, 9 | ], 10 | }, 11 | }; 12 | 13 | module.exports = nextConfig; 14 | -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | body { 6 | background-color: #000; 7 | } 8 | 9 | .red-gradient { 10 | background: linear-gradient(92deg, #ff5956 2.87%, #ee1e38 96.18%); 11 | background-clip: text; 12 | -webkit-background-clip: text; 13 | -webkit-text-fill-color: transparent; 14 | } 15 | 16 | :root { 17 | color-scheme: dark; 18 | } 19 | -------------------------------------------------------------------------------- /app/action.tsx: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | import AnimeCard, { AnimeProp } from "@/components/AnimeCard"; 4 | 5 | const MAX_LIMIT = 8; 6 | 7 | export async function fetchAnime(page: number) { 8 | const response = await fetch( 9 | `https://shikimori.one/api/animes?page=${page}&limit=${MAX_LIMIT}&order=popularity` 10 | ); 11 | 12 | const data = await response.json(); 13 | 14 | return data.map((item: AnimeProp, index: number) => ( 15 | 16 | )); 17 | } 18 | -------------------------------------------------------------------------------- /.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 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | import { fetchAnime } from "./action"; 2 | import LoadMore from "../components/LoadMore"; 3 | 4 | async function Home() { 5 | const data = await fetchAnime(1); 6 | 7 | return ( 8 |
9 |

Explore Anime

10 | 11 |
12 | {data} 13 |
14 | 15 |
16 | ); 17 | } 18 | 19 | export default Home; 20 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | 3 | const config: Config = { 4 | content: [ 5 | "./pages/**/*.{js,ts,jsx,tsx,mdx}", 6 | "./components/**/*.{js,ts,jsx,tsx,mdx}", 7 | "./app/**/*.{js,ts,jsx,tsx,mdx}", 8 | ], 9 | theme: { 10 | extend: { 11 | backgroundImage: { 12 | "gradient-radial": "radial-gradient(var(--tw-gradient-stops))", 13 | "gradient-conic": 14 | "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))", 15 | hero: 'url("/hero.png")', 16 | }, 17 | }, 18 | }, 19 | plugins: [], 20 | }; 21 | export default config; 22 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "esbenp.prettier-vscode", 3 | "editor.formatOnSave": true, 4 | "editor.codeActionsOnSave": { 5 | "source.fixAll.eslint": "explicit", 6 | "source.addMissingImports": "explicit" 7 | }, 8 | "[typescriptreact]": { 9 | "editor.defaultFormatter": "esbenp.prettier-vscode" 10 | }, 11 | "[typescript]": { 12 | "editor.defaultFormatter": "esbenp.prettier-vscode" 13 | }, 14 | "[javascript]": { 15 | "editor.defaultFormatter": "esbenp.prettier-vscode" 16 | }, 17 | "typescript.tsdk": "node_modules/typescript/lib", 18 | "svg.preview.background": "transparent" 19 | } 20 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "anime_world", 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 | "framer-motion": "^10.16.16", 13 | "next": "14.0.3", 14 | "react": "^18", 15 | "react-dom": "^18", 16 | "react-intersection-observer": "^9.5.3" 17 | }, 18 | "devDependencies": { 19 | "@types/node": "^20", 20 | "@types/react": "^18", 21 | "@types/react-dom": "^18", 22 | "autoprefixer": "^10.0.1", 23 | "eslint": "^8", 24 | "eslint-config-next": "14.0.3", 25 | "postcss": "^8", 26 | "tailwindcss": "^3.3.0", 27 | "typescript": "^5" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { DM_Sans } from "next/font/google"; 3 | 4 | import Hero from "@/components/Hero"; 5 | import Footer from "@/components/Footer"; 6 | 7 | import "./globals.css"; 8 | 9 | const dmSans = DM_Sans({ subsets: ["latin"] }); 10 | 11 | export const metadata: Metadata = { 12 | title: "Anime World", 13 | description: "Your favorite anime, all in one place.", 14 | }; 15 | 16 | export default function RootLayout({ 17 | children, 18 | }: { 19 | children: React.ReactNode; 20 | }) { 21 | return ( 22 | 23 | 24 |
25 | 26 | {children} 27 |
28 |
29 | 30 | 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /public/star.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /components/Hero.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | 3 | function Hero() { 4 | return ( 5 |
6 |
7 | logo 14 |

15 | Explore The Diverse Realms of 16 | Anime Magic 17 |

18 |
19 |
20 | anime 21 |
22 |
23 | ); 24 | } 25 | 26 | export default Hero; 27 | -------------------------------------------------------------------------------- /public/tiktok.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/spinner.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/Footer.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | 3 | function Footer() { 4 | return ( 5 | 38 | ); 39 | } 40 | 41 | export default Footer; 42 | -------------------------------------------------------------------------------- /components/LoadMore.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { fetchAnime } from "@/app/action"; 4 | import Image from "next/image"; 5 | import { useEffect, useState } from "react"; 6 | import { useInView } from "react-intersection-observer"; 7 | import AnimeCard from "./AnimeCard"; 8 | 9 | let page = 2; 10 | 11 | export type AnimeCard = JSX.Element; 12 | 13 | function LoadMore() { 14 | const { ref, inView } = useInView(); 15 | const [data, setData] = useState([]); 16 | 17 | useEffect(() => { 18 | if (inView) { 19 | fetchAnime(page).then((res) => { 20 | setData([...data, ...res]); 21 | page++; 22 | }); 23 | } 24 | }, [inView, data]); 25 | 26 | return ( 27 | <> 28 |
29 | {data} 30 |
31 |
32 |
33 | spinner 40 |
41 |
42 | 43 | ); 44 | } 45 | 46 | export default LoadMore; 47 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/twitter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/episodes.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /components/AnimeCard.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import { MotionDiv } from "./MotionDiv"; 3 | 4 | export interface AnimeProp { 5 | id: string; 6 | name: string; 7 | image: { 8 | original: string; 9 | }; 10 | kind: string; 11 | episodes: number; 12 | episodes_aired: number; 13 | score: string; 14 | } 15 | 16 | interface Prop { 17 | anime: AnimeProp; 18 | index: number; 19 | } 20 | 21 | const variants = { 22 | hidden: { opacity: 0 }, 23 | visible: { opacity: 1 }, 24 | }; 25 | 26 | function AnimeCard({ anime, index }: Prop) { 27 | return ( 28 | 40 |
41 | {anime.name} 47 |
48 |
49 |
50 |

51 | {anime.name} 52 |

53 |
54 |

55 | {anime.kind} 56 |

57 |
58 |
59 |
60 |
61 | episodes 68 |

69 | {anime.episodes || anime.episodes_aired} 70 |

71 |
72 |
73 | star 80 |

{anime.score}

81 |
82 |
83 |
84 |
85 | ); 86 | } 87 | 88 | export default AnimeCard; 89 | -------------------------------------------------------------------------------- /public/instagram.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/_data.ts: -------------------------------------------------------------------------------- 1 | export const data = [ 2 | { 3 | id: "1", 4 | name: "bleach", 5 | image: { 6 | original: 7 | "https://m.media-amazon.com/images/M/MV5BZjE0YjVjODQtZGY2NS00MDcyLThhMDAtZGQwMTZiOWNmNjRiXkEyXkFqcGdeQXVyNTA4NzY1MzY@._V1_FMjpg_UX1000_.jpg", 8 | }, 9 | kind: "TV", 10 | episodes: 366, 11 | episodes_aired: 366, 12 | score: "7.92", 13 | }, 14 | { 15 | id: "2", 16 | name: "black_clover", 17 | image: { 18 | original: 19 | "https://m.media-amazon.com/images/M/MV5BNTAzYTlkMWEtOTNjZC00ZDU0LWI5ODUtYTRmYzY0MTAzYWZlXkEyXkFqcGdeQXVyMzgxODM4NjM@._V1_FMjpg_UX1000_.jpg", 20 | }, 21 | kind: "TV", 22 | episodes: 170, 23 | episodes_aired: 170, 24 | score: "7.16", 25 | }, 26 | { 27 | id: "3", 28 | name: "dragon_ball", 29 | image: { 30 | original: 31 | "https://m.media-amazon.com/images/M/MV5BMGMyOThiMGUtYmFmZi00YWM0LWJiM2QtZGMwM2Q2ODE4MzhhXkEyXkFqcGdeQXVyMjc2Nzg5OTQ@._V1_FMjpg_UX1000_.jpg", 32 | }, 33 | kind: "TV", 34 | episodes: 153, 35 | episodes_aired: 153, 36 | score: "8.68", 37 | }, 38 | { 39 | id: "4", 40 | name: "jujutsu_kaisen", 41 | image: { 42 | original: 43 | "https://static.wikia.nocookie.net/jujutsu-kaisen/images/8/88/Anime_Key_Visual_2.png/revision/latest?cb=20201212034001", 44 | }, 45 | kind: "TV", 46 | episodes: 24, 47 | episodes_aired: 24, 48 | score: "8.78", 49 | }, 50 | { 51 | id: "5", 52 | name: "fma_brotherhood", 53 | image: { 54 | original: 55 | "https://m.media-amazon.com/images/M/MV5BZmEzN2YzOTItMDI5MS00MGU4LWI1NWQtOTg5ZThhNGQwYTEzXkEyXkFqcGdeQXVyNTA4NzY1MzY@._V1_.jpg", 56 | }, 57 | kind: "TV", 58 | episodes: 64, 59 | episodes_aired: 64, 60 | score: "9.24", 61 | }, 62 | { 63 | id: "6", 64 | name: "naruto", 65 | image: { 66 | original: 67 | "https://m.media-amazon.com/images/M/MV5BZmQ5NGFiNWEtMmMyMC00MDdiLTg4YjktOGY5Yzc2MDUxMTE1XkEyXkFqcGdeQXVyNTA4NzY1MzY@._V1_.jpg", 68 | }, 69 | kind: "TV", 70 | episodes: 220, 71 | episodes_aired: 220, 72 | score: "8.3", 73 | }, 74 | { 75 | id: "7", 76 | name: "gintama", 77 | image: { 78 | original: 79 | "https://m.media-amazon.com/images/M/MV5BMDkxZTJjZTEtMDRjMy00Yzk1LWI5YjItMjIwYmVlYzhlZWZhXkEyXkFqcGdeQXVyNDQxNjcxNQ@@._V1_FMjpg_UX1000_.jpg", 80 | }, 81 | kind: "TV", 82 | episodes: 367, 83 | episodes_aired: 367, 84 | score: "9.0", 85 | }, 86 | { 87 | id: "9", 88 | name: "one_piece", 89 | image: { 90 | original: 91 | "https://m.media-amazon.com/images/M/MV5BODcwNWE3OTMtMDc3MS00NDFjLWE1OTAtNDU3NjgxODMxY2UyXkEyXkFqcGdeQXVyNTAyODkwOQ@@._V1_.jpg", 92 | }, 93 | kind: "TV", 94 | episodes: 1030, 95 | episodes_aired: 1030, 96 | score: "8.58", 97 | }, 98 | { 99 | id: "10", 100 | name: "demon_slayer", 101 | image: { 102 | original: 103 | "https://m.media-amazon.com/images/M/MV5BZjZjNzI5MDctY2Y4YS00NmM4LTljMmItZTFkOTExNGI3ODRhXkEyXkFqcGdeQXVyNjc3MjQzNTI@._V1_.jpg", 104 | }, 105 | kind: "TV", 106 | episodes: 26, 107 | episodes_aired: 26, 108 | score: "8.6", 109 | }, 110 | { 111 | id: "11", 112 | name: "attack_on_titan", 113 | image: { 114 | original: "https://flxt.tmsimg.com/assets/p10701949_b_v8_ah.jpg", 115 | }, 116 | kind: "TV", 117 | episodes: 75, 118 | episodes_aired: 75, 119 | score: "9.16", 120 | }, 121 | { 122 | id: "12", 123 | name: "hunter_x_hunter", 124 | image: { 125 | original: 126 | "https://m.media-amazon.com/images/M/MV5BZjNmZDhkN2QtNDYyZC00YzJmLTg0ODUtN2FjNjhhMzE3ZmUxXkEyXkFqcGdeQXVyNjc2NjA5MTU@._V1_FMjpg_UX1000_.jpg", 127 | }, 128 | kind: "TV", 129 | episodes: 148, 130 | episodes_aired: 148, 131 | score: "9.1", 132 | }, 133 | { 134 | id: "13", 135 | name: "boku_no_hero_academia", 136 | image: { 137 | original: 138 | "https://i.pinimg.com/736x/0f/7f/ee/0f7feeb4655ffc029d1b9823bafb2ff8.jpg", 139 | }, 140 | kind: "TV", 141 | episodes: 114, 142 | episodes_aired: 114, 143 | score: "8.39", 144 | }, 145 | ]; 146 | -------------------------------------------------------------------------------- /public/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | --------------------------------------------------------------------------------