├── .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 |
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 |
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 |
--------------------------------------------------------------------------------
/components/Hero.tsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 |
3 | function Hero() {
4 | return (
5 |
23 | );
24 | }
25 |
26 | export default Hero;
27 |
--------------------------------------------------------------------------------
/public/tiktok.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 |
31 |
42 | >
43 | );
44 | }
45 |
46 | export default LoadMore;
47 |
--------------------------------------------------------------------------------
/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/twitter.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/episodes.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 |
47 |
48 |
49 |
50 |
51 | {anime.name}
52 |
53 |
54 |
55 | {anime.kind}
56 |
57 |
58 |
59 |
60 |
61 |
68 |
69 | {anime.episodes || anime.episodes_aired}
70 |
71 |
72 |
73 |
80 |
{anime.score}
81 |
82 |
83 |
84 |
85 | );
86 | }
87 |
88 | export default AnimeCard;
89 |
--------------------------------------------------------------------------------
/public/instagram.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------