├── .eslintrc.json ├── .vscode └── settings.json ├── bun.lockb ├── .gitattributes ├── src ├── components │ ├── slider.css │ ├── Player │ │ ├── base.css │ │ └── components │ │ │ ├── title.tsx │ │ │ ├── time-group.tsx │ │ │ ├── layouts │ │ │ ├── video-layout.module.css │ │ │ ├── video-layout.tsx │ │ │ └── captions.module.css │ │ │ ├── sliders.tsx │ │ │ └── buttons.tsx │ ├── ProgressBar.tsx │ ├── Video.tsx │ ├── ScrollToTop.tsx │ ├── footer.tsx │ ├── Characters.tsx │ ├── Changelogs.tsx │ ├── EpisodesList.tsx │ ├── Card.tsx │ ├── Nav.tsx │ └── Hero.tsx ├── app │ ├── favicon.ico │ ├── globals.css │ ├── providers.tsx │ ├── renderNav.tsx │ ├── settings │ │ ├── home │ │ │ └── page.tsx │ │ ├── player │ │ │ └── page.tsx │ │ ├── page.tsx │ │ ├── homeSettings.tsx │ │ └── playerSettings.tsx │ ├── robots.ts │ ├── info │ │ └── [id] │ │ │ ├── Img.tsx │ │ │ ├── Accordions.tsx │ │ │ └── page.tsx │ ├── sitemap.ts │ ├── api │ │ ├── v1 │ │ │ ├── search │ │ │ │ └── [query] │ │ │ │ │ └── route.ts │ │ │ ├── source │ │ │ │ └── route.ts │ │ │ ├── episodesMetadata │ │ │ │ └── [id] │ │ │ │ │ └── route.ts │ │ │ ├── characters │ │ │ │ └── [id] │ │ │ │ │ └── route.ts │ │ │ └── episodes │ │ │ │ └── [id] │ │ │ │ └── route.ts │ │ ├── v2 │ │ │ ├── advancedSearch │ │ │ │ └── route.ts │ │ │ ├── thumbnails │ │ │ │ └── [id] │ │ │ │ │ └── route.ts │ │ │ └── studios │ │ │ │ └── [id] │ │ │ │ └── route.ts │ │ └── temp │ │ │ └── episodes │ │ │ └── [id] │ │ │ └── route.ts │ ├── studio │ │ └── [id] │ │ │ ├── page.tsx │ │ │ └── Card.tsx │ ├── ThemeProvider.tsx │ ├── dmca │ │ └── page.tsx │ ├── trending │ │ ├── page.tsx │ │ └── Card.tsx │ ├── popular │ │ ├── page.tsx │ │ └── Card.tsx │ ├── layout.tsx │ ├── privacy │ │ └── page.tsx │ ├── watch │ │ └── [id] │ │ │ └── [episode] │ │ │ ├── Accordions.tsx │ │ │ ├── Share.tsx │ │ │ └── page.tsx │ ├── catalog │ │ ├── Card.tsx │ │ └── page.tsx │ └── page.tsx ├── hooks │ ├── useSSRLocalStorage.ts │ ├── useTruncate.ts │ ├── useDebounce.ts │ └── VideoProgressSave.ts ├── functions │ ├── cache.ts │ ├── jsxUtilityFunctions.tsx │ ├── utilityFunctions.ts │ └── clientRequests.ts └── types │ ├── anify.ts │ ├── consumet.ts │ └── site.ts ├── .prettierrc ├── postcss.config.mjs ├── .env.example ├── .gitignore ├── public ├── vercel.svg └── next.svg ├── tsconfig.json ├── next.config.mjs ├── package.json ├── README.md └── tailwind.config.ts /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": ["Rever"] 3 | } 4 | -------------------------------------------------------------------------------- /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shimizudev/reveraki/HEAD/bun.lockb -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /src/components/slider.css: -------------------------------------------------------------------------------- 1 | .swiper { 2 | width: 100%; 3 | height: 100%; 4 | } 5 | -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shimizudev/reveraki/HEAD/src/app/favicon.ico -------------------------------------------------------------------------------- /src/components/Player/base.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --media-brand: 245 245 245; 3 | --media-focus: 78 156 246; 4 | } 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "endOfLine": "crlf", 4 | "plugins": ["prettier-plugin-tailwindcss"] 5 | } 6 | -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer utilities { 6 | .text-balance { 7 | text-wrap: balance; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/app/providers.tsx: -------------------------------------------------------------------------------- 1 | import { NextUIProvider } from '@nextui-org/react'; 2 | 3 | export function Providers({ children }: { children: React.ReactNode }) { 4 | return {children}; 5 | } 6 | -------------------------------------------------------------------------------- /src/app/renderNav.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { usePathname } from 'next/navigation'; 3 | import NavComp from '@/components/Nav'; 4 | 5 | export default function NavBarRenderer() { 6 | console.log(usePathname().replace(/\//, '')); 7 | return ; 8 | } 9 | -------------------------------------------------------------------------------- /src/app/settings/home/page.tsx: -------------------------------------------------------------------------------- 1 | import dynamic from 'next/dynamic'; 2 | import { Suspense } from 'react'; 3 | 4 | const Settings = dynamic(() => import('../homeSettings'), { ssr: false }); 5 | 6 | export default function HomeSettingsPage() { 7 | return ( 8 | 9 | 10 | 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /src/app/settings/player/page.tsx: -------------------------------------------------------------------------------- 1 | import dynamic from 'next/dynamic'; 2 | import { Suspense } from 'react'; 3 | 4 | const Settings = dynamic(() => import('../playerSettings'), { ssr: false }); 5 | 6 | export default function PlayerSettingsPage() { 7 | return ( 8 | 9 | 10 | 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | REDIS_URI = "" # Your Redis URI. Not required. Remove this if you don't need Redis for caching. It will use node-cache by default. 2 | 3 | CONSUMET_API = "" # Consumet API. Host from: https://github.com/consumet/api.consumet.org/ 4 | NEXT_PUBLIC_DOMAIN = "http://localhost:3000" # Your website URL (required for info and episode), replace it with your website URL 5 | -------------------------------------------------------------------------------- /src/components/Player/components/title.tsx: -------------------------------------------------------------------------------- 1 | import { ChapterTitle } from '@vidstack/react'; 2 | 3 | export function Title() { 4 | return ( 5 | 6 | | 7 | 8 | 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /src/components/Player/components/time-group.tsx: -------------------------------------------------------------------------------- 1 | import { Time } from '@vidstack/react'; 2 | 3 | export function TimeGroup() { 4 | return ( 5 |
6 |
10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /src/hooks/useSSRLocalStorage.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable react-hooks/rules-of-hooks */ 2 | import useLocalStorage from 'react-use-localstorage'; 3 | import React from 'react'; 4 | 5 | export const useSSRLocalStorage = ( 6 | key: string, 7 | initial: string, 8 | ): [string, React.Dispatch] => { 9 | return typeof window === 'undefined' 10 | ? [initial, (value: string) => undefined] 11 | : useLocalStorage(key, initial); 12 | }; 13 | -------------------------------------------------------------------------------- /src/components/ProgressBar.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { AppProgressBar as ProgressBar } from 'next-nprogress-bar'; 4 | import { Suspense } from 'react'; 5 | 6 | const TopProgressBar = () => { 7 | return ( 8 | 9 | 15 | 16 | ); 17 | }; 18 | 19 | export default TopProgressBar; 20 | -------------------------------------------------------------------------------- /src/app/robots.ts: -------------------------------------------------------------------------------- 1 | import { MetadataRoute } from 'next'; 2 | 3 | export default function robots(): MetadataRoute.Robots { 4 | return { 5 | rules: [ 6 | { 7 | userAgent: [ 8 | 'Applebot', 9 | 'Bingbot', 10 | 'Googlebot', 11 | 'Slurp', 12 | 'DuckDuckBot', 13 | 'Baiduspider', 14 | 'Yandex', 15 | ], 16 | allow: ['/'], 17 | disallow: '/api/', 18 | }, 19 | ], 20 | sitemap: 'https://reveraki-delta.vercel.app/sitemap.xml', 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/hooks/useTruncate.ts: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useState, useEffect } from 'react'; 4 | 5 | interface Options { 6 | length: number; 7 | omission?: string; 8 | } 9 | 10 | export const useTruncate = (text: string, options: Options): string => { 11 | const { length, omission = '...' } = options; 12 | const [truncatedText, setTruncatedText] = useState(text); 13 | 14 | useEffect(() => { 15 | if (text.length > length) { 16 | setTruncatedText(text.slice(0, length - omission.length) + omission); 17 | } else { 18 | setTruncatedText(text); 19 | } 20 | }, [text, length, omission]); 21 | 22 | return truncatedText; 23 | }; 24 | 25 | export default useTruncate; 26 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "paths": { 21 | "@/*": ["./src/*"] 22 | } 23 | }, 24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 25 | "exclude": ["node_modules"] 26 | } 27 | -------------------------------------------------------------------------------- /src/app/info/[id]/Img.tsx: -------------------------------------------------------------------------------- 1 | import Image from 'next/image'; 2 | 3 | export const InfoImg = ({ info }: { info: any }) => { 4 | return ( 5 |
6 | {info.coverImage ? ( 7 | {info.color} 14 | ) : ( 15 | {info.color 22 | )} 23 |
24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /src/hooks/useDebounce.ts: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useState, useEffect } from 'react'; 4 | import _ from 'lodash'; 5 | 6 | const useDebounce = (value: T, delay: number): T => { 7 | const [debouncedValue, setDebouncedValue] = useState(value); 8 | 9 | useEffect(() => { 10 | const handler = _.debounce(() => { 11 | setDebouncedValue(value); 12 | }, delay); 13 | 14 | return () => { 15 | handler.cancel(); 16 | }; 17 | }, [value, delay]); 18 | 19 | useEffect(() => { 20 | const handler = _.debounce(() => { 21 | setDebouncedValue(value); 22 | }, delay); 23 | 24 | handler(); 25 | 26 | return () => { 27 | handler.cancel(); 28 | }; 29 | }, [value, delay]); 30 | 31 | return debouncedValue; 32 | }; 33 | 34 | export default useDebounce; 35 | -------------------------------------------------------------------------------- /src/app/sitemap.ts: -------------------------------------------------------------------------------- 1 | import { getTrendingAnime } from '@/functions/clientRequests'; 2 | import { ConsumetAnimePage } from '@/types/consumet'; 3 | import { MetadataRoute } from 'next'; 4 | 5 | export default async function sitemap(): Promise { 6 | const anime = (await getTrendingAnime()) as ConsumetAnimePage; 7 | const animeEntries: MetadataRoute.Sitemap = anime.results.map( 8 | ({ id, title }) => ({ 9 | url: `${process.env.NEXT_PUBLIC_DOMAIN}/watch/${id}/1`, 10 | lastModefied: new Date(), 11 | changeFrequency: 'daily', 12 | priority: 1, 13 | }), 14 | ); 15 | 16 | return [ 17 | { 18 | url: `${process.env.NEXT_PUBLIC_DOMAIN}`, 19 | lastModified: new Date(), 20 | changeFrequency: 'daily', 21 | priority: 0.8, 22 | }, 23 | ...animeEntries, 24 | ]; 25 | } 26 | -------------------------------------------------------------------------------- /src/components/Video.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | 'use client'; 4 | 5 | import { motion } from 'framer-motion'; 6 | import { useState } from 'react'; 7 | 8 | export default function RenderVideo({ 9 | trailer, 10 | }: Readonly<{ trailer: string }>) { 11 | const [isPlaying, setIsPlaying] = useState(true); 12 | 13 | const handleVideoEnded = () => { 14 | setIsPlaying(true); 15 | }; 16 | 17 | return ( 18 |
19 | 27 |
28 | ); 29 | } 30 | 31 | -------------------------------------------------------------------------------- /src/app/api/v1/search/[query]/route.ts: -------------------------------------------------------------------------------- 1 | import { ConsumetSearchResult } from '@/types/consumet'; 2 | import { NextRequest, NextResponse } from 'next/server'; 3 | 4 | export const GET = async ( 5 | request: NextRequest, 6 | { params }: { params: { query: string } }, 7 | ) => { 8 | try { 9 | const h = request.headers; 10 | 11 | if (!h.get('x-site') || h.get('x-site') !== 'ezanime') { 12 | return NextResponse.json( 13 | { 14 | message: 'Bad Request', 15 | status: 400, 16 | }, 17 | { status: 400 }, 18 | ); 19 | } 20 | 21 | const response = await fetch( 22 | `${process.env.CONSUMET_API!}/meta/anilist/${params.query}?perPage=10`, 23 | ); 24 | return NextResponse.json((await response.json()) as ConsumetSearchResult); 25 | } catch (error) { 26 | return NextResponse.json( 27 | { 28 | message: 'Internal Server Error', 29 | status: 500, 30 | }, 31 | { status: 500 }, 32 | ); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /src/app/studio/[id]/page.tsx: -------------------------------------------------------------------------------- 1 | import { StudioInfo } from '@/app/api/v2/studios/[id]/route'; 2 | import { getStudio } from '@/functions/requests'; 3 | import { Card } from './Card'; 4 | 5 | export default async function Studio({ params }: { params: { id: string } }) { 6 | const studio = (await getStudio(params.id)) as StudioInfo; 7 | 8 | return ( 9 |
10 |
11 |

{studio.name}

12 |

13 | {studio.favoritesNumber}{' '} 14 | {studio.isAnimationStudio ? '| Animation Studio' : ''} 15 |

16 |
17 |

Released Anime

18 |
19 | {studio.releasedAnime?.map((anime) => ( 20 |
21 | 22 |
23 | ))} 24 |
25 |
26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /src/hooks/VideoProgressSave.ts: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useState } from 'react'; 4 | 5 | type VideoProgress = { 6 | [id: string]: any; 7 | }; 8 | 9 | type UseVideoProgressSave = () => [ 10 | (id: string) => any, 11 | (id: string, data: any) => void, 12 | ]; 13 | 14 | const useVideoProgressSave: UseVideoProgressSave = () => { 15 | const [settings, setSettings] = useState(() => { 16 | const storedSettings = localStorage?.getItem('vidstack_settings'); 17 | return storedSettings ? JSON.parse(storedSettings) : {}; 18 | }); 19 | 20 | const getVideoProgress = (id: string) => { 21 | return settings[id]; 22 | }; 23 | 24 | const updateVideoProgress = (id: string, data: any) => { 25 | const updatedSettings = { ...settings, [id]: data }; 26 | setSettings(updatedSettings); 27 | 28 | localStorage.setItem('vidstack_settings', JSON.stringify(updatedSettings)); 29 | }; 30 | 31 | return [getVideoProgress, updateVideoProgress]; 32 | }; 33 | 34 | export default useVideoProgressSave; 35 | -------------------------------------------------------------------------------- /src/app/ThemeProvider.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React, { useEffect, useState } from 'react'; 4 | 5 | type Theme = 6 | | 'dark' 7 | | 'secondaryDark' 8 | | 'success' 9 | | 'danger' 10 | | 'warning' 11 | | 'navyBlue'; 12 | 13 | interface ThemeProviderProps { 14 | children: React.ReactNode; 15 | } 16 | 17 | const ThemeProvider: React.FC = ({ children }) => { 18 | const [theme, setTheme] = useState('dark'); 19 | 20 | useEffect(() => { 21 | const savedTheme = localStorage.getItem('themeColor'); 22 | if ( 23 | savedTheme && 24 | (savedTheme === 'dark' || 25 | savedTheme === 'secondaryDark' || 26 | savedTheme === 'success' || 27 | savedTheme === 'danger' || 28 | savedTheme === 'warning' || 29 | savedTheme === 'navyBlue') 30 | ) { 31 | setTheme(savedTheme); 32 | document.body.classList.add(`${savedTheme}`); 33 | document.body.classList.add(`bg-background`); 34 | } 35 | }, []); 36 | 37 | return <>{children}; 38 | }; 39 | 40 | export default ThemeProvider; 41 | -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | images: { 4 | remotePatterns: [ 5 | { 6 | hostname: 's4.anilist.co', 7 | port: '', 8 | pathname: '/**', 9 | protocol: 'https', 10 | }, 11 | { 12 | hostname: 'artworks.thetvdb.com', 13 | port: '', 14 | pathname: '/**', 15 | protocol: 'https', 16 | }, 17 | { 18 | hostname: 'media.kitsu.io', 19 | port: '', 20 | pathname: '/**', 21 | protocol: 'https', 22 | }, 23 | { 24 | hostname: 'image.tmdb.org', 25 | port: '', 26 | pathname: '/**', 27 | protocol: 'https', 28 | }, 29 | { 30 | hostname: 'upload.wikimedia.org', 31 | port: '', 32 | pathname: '/**', 33 | protocol: 'https', 34 | }, 35 | { 36 | hostname: 'img1.ak.crunchyroll.com', 37 | port: '', 38 | pathname: '/**', 39 | protocol: 'https', 40 | }, 41 | ], 42 | }, 43 | }; 44 | 45 | export default nextConfig; 46 | -------------------------------------------------------------------------------- /src/app/dmca/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { Breadcrumbs, BreadcrumbItem, Link } from '@nextui-org/react'; 4 | 5 | export default function DMCA() { 6 | return ( 7 |
8 | 9 | Home 10 | DMCA 11 | 12 | 13 |

DMCA Note

14 |

15 | ReverAki operates independently and is not formally associated with nor 16 | endorsed by any of the anime studios responsible for the creation of the 17 | anime featured on this platform. Our website serves solely as a user 18 | interface, facilitating access to self-hosted files sourced from various 19 | third-party providers across the internet. It's important to note 20 | that ReverAki never initiates downloads of video content from these 21 | providers. Instead, links are provided in response to user requests, 22 | thereby absolving the platform from any potential DMCA compliance 23 | issues. 24 |

25 |
26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reveraki", 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 | "@nextui-org/react": "^2.3.6", 13 | "@vidstack/react": "^1.11.21", 14 | "axios": "^1.6.8", 15 | "framer-motion": "^11.1.7", 16 | "hls.js": "^1.5.8", 17 | "ioredis": "^5.4.1", 18 | "lodash": "^4.17.21", 19 | "marked": "^12.0.2", 20 | "media-icons": "^1.1.4", 21 | "next": "14.2.3", 22 | "next-nprogress-bar": "^2.3.11", 23 | "node-cache": "^5.1.2", 24 | "react": "^18", 25 | "react-dom": "^18", 26 | "react-icons": "^5.2.0", 27 | "react-use-localstorage": "^3.5.3", 28 | "sharp": "^0.33.3", 29 | "swiper": "^11.1.1" 30 | }, 31 | "devDependencies": { 32 | "@types/lodash": "^4.17.0", 33 | "@types/node": "^20", 34 | "@types/react": "^18", 35 | "@types/react-dom": "^18", 36 | "eslint": "^8", 37 | "eslint-config-next": "14.2.3", 38 | "postcss": "^8", 39 | "prettier": "^3.2.5", 40 | "prettier-plugin-tailwindcss": "^0.5.14", 41 | "tailwindcss": "^3.4.1", 42 | "typescript": "^5" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/ScrollToTop.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { Button, Tooltip } from '@nextui-org/react'; 4 | import { useEffect, useState } from 'react'; 5 | import { BiArrowFromBottom } from 'react-icons/bi'; 6 | 7 | export const ScrollToTop = () => { 8 | const [isVisible, setIsVisible] = useState(false); 9 | 10 | const toggleVisibility = () => { 11 | if (window.scrollY > 300) { 12 | setIsVisible(true); 13 | } else { 14 | setIsVisible(false); 15 | } 16 | }; 17 | 18 | const scrollToTop = () => { 19 | window.scrollTo({ 20 | top: 0, 21 | behavior: 'smooth', 22 | }); 23 | }; 24 | 25 | useEffect(() => { 26 | window.addEventListener('scroll', toggleVisibility); 27 | 28 | return () => { 29 | window.removeEventListener('scroll', toggleVisibility); 30 | }; 31 | }, []); 32 | 33 | return ( 34 |
35 | 36 | 47 | 48 |
49 | ); 50 | }; 51 | -------------------------------------------------------------------------------- /src/app/trending/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { getTrendingAnime } from '@/functions/clientRequests'; 4 | import { ExtendedAnimePage } from '@/types/consumet'; 5 | import { useEffect, useState } from 'react'; 6 | import { Card } from './Card'; 7 | import { Pagination } from '@nextui-org/react'; 8 | 9 | export default function Trending() { 10 | const [page, setPage] = useState(1); 11 | const [trending, setTrending] = useState(null); 12 | 13 | useEffect(() => { 14 | fetchTrendingAnime(page); 15 | }, [page]); 16 | 17 | const fetchTrendingAnime = async (currentPage: number) => { 18 | const response = await getTrendingAnime(currentPage, 46); 19 | setTrending(response!); 20 | }; 21 | 22 | const handlePageChange = (newPage: number) => { 23 | setPage(newPage); 24 | }; 25 | 26 | return ( 27 |
28 |
29 | {trending?.results.map((a) => { 30 | return ; 31 | })} 32 |
33 |
34 | {trending && ( 35 | 40 | )} 41 |
42 |
43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /src/app/popular/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { getPopularAnime } from '@/functions/clientRequests'; 4 | import { ConsumetAnimePage, ExtendedAnimePage } from '@/types/consumet'; 5 | import { useEffect, useState } from 'react'; 6 | import { Card } from './Card'; 7 | import { Pagination } from '@nextui-org/react'; 8 | 9 | export default function Popular() { 10 | const [page, setPage] = useState(1); 11 | const [popular, setPopular] = useState(null); 12 | 13 | useEffect(() => { 14 | fetchPopularAnime(page); 15 | }, [page]); 16 | 17 | const fetchPopularAnime = async (currentPage: number) => { 18 | const response = await getPopularAnime(currentPage, 46); 19 | setPopular(response!); 20 | }; 21 | 22 | const handlePageChange = (newPage: number) => { 23 | setPage(newPage); 24 | }; 25 | 26 | return ( 27 |
28 |
29 | {popular?.results.map((a) => { 30 | return ; 31 | })} 32 |
33 |
34 | {popular && ( 35 | 40 | )} 41 |
42 |
43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from 'next'; 2 | import { Exo } from 'next/font/google'; 3 | import './globals.css'; 4 | import { Providers } from './providers'; 5 | import TopProgressBar from '../components/ProgressBar'; 6 | import Footer from '@/components/footer'; 7 | import { ScrollToTop } from '@/components/ScrollToTop'; 8 | import dynamic from 'next/dynamic'; 9 | import NavBarRenderer from './renderNav'; 10 | 11 | const Changelogs = dynamic(() => import('@/components/Changelogs'), { 12 | ssr: false, 13 | }); 14 | 15 | const ThemeProvider = dynamic(() => import('./ThemeProvider'), { 16 | ssr: false, 17 | }); 18 | 19 | const exo = Exo({ subsets: ['latin'] }); 20 | 21 | export const metadata: Metadata = { 22 | title: 'Reveraki', 23 | description: 'An ad-free anime streaming website', 24 | }; 25 | 26 | export default function RootLayout({ 27 | children, 28 | }: Readonly<{ 29 | children: React.ReactNode; 30 | }>) { 31 | return ( 32 | 33 | 34 | 35 | 36 | 37 |
{children}
38 |