├── .env.local.example ├── .eslintrc.json ├── .gitignore ├── .prettierignore ├── .prettierrc ├── README.md ├── components ├── detailspage │ ├── DetailsTable.jsx │ ├── Files.jsx │ ├── NFO.jsx │ ├── Proof.jsx │ ├── RetailInfo_Discogs.jsx │ ├── RetailInfo_Movie_TMDB.jsx │ ├── RetailInfo_TV.jsx │ └── RetailInfo_TV_TMDB.jsx ├── layout │ ├── Footer.jsx │ ├── GithubLink.jsx │ ├── Layout.jsx │ ├── Logo.jsx │ ├── MobileMenu.jsx │ ├── Navbar.jsx │ └── ThemeSwitcher.jsx ├── modals │ └── ModalSubscribe.jsx ├── notifications │ └── NotificationCard.jsx ├── profile │ ├── ActionGroup.jsx │ └── DeleteAccountModal.jsx ├── releases │ ├── CategoryBadge.jsx │ ├── CopyButon.jsx │ ├── NoResults.jsx │ ├── Pagination.jsx │ ├── ReleaseList.jsx │ └── ReleaseRow.jsx └── search │ ├── SearchAdvanced.jsx │ ├── SearchSimple.jsx │ ├── SearchTabs.jsx │ └── Toolbar.jsx ├── context └── SearchContext.jsx ├── hooks ├── useDebounce.js ├── useFetch.js ├── useLocalStorage.js └── useSocket.js ├── next.config.js ├── package.json ├── pages ├── _app.jsx ├── about.jsx ├── api │ └── hello.js ├── index.jsx ├── login.jsx ├── logout.jsx ├── notifications.jsx ├── profile.jsx ├── register.jsx ├── release │ └── [_id].jsx ├── restore.jsx ├── rss.jsx └── stats.jsx ├── public ├── dolittle.2020.multi.complete.uhd.bluray-orca-proof.jpg ├── favicon.ico ├── movie-placeholder-dark.png ├── movie-placeholder-light.png └── settings-knobs.svg ├── redux ├── slices │ ├── authSlice.js │ ├── notificationSlice.js │ ├── releasesSlice.js │ ├── searchSlice.js │ ├── statsSlice.js │ └── toastSlice.js └── store.js ├── styles ├── components │ ├── button.js │ ├── container.js │ ├── index.js │ └── input.js ├── foundations │ ├── colors.js │ ├── index.js │ ├── radii.js │ └── typography.js ├── global.css ├── index.js └── styles.js └── utils ├── classify.js ├── helpers.js ├── routes.js └── sites.js /.env.local.example: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_API_BASE=https://api.predb.live -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals", 3 | "rules": { 4 | "react/no-unescaped-entities": 0 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.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 | .directory 21 | package-lock.json 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env.local 30 | .env.development.local 31 | .env.test.local 32 | .env.production.local 33 | 34 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | coverage 4 | .next 5 | build 6 | public -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "bracketSpacing": true, 3 | "jsxBracketSameLine": false, 4 | "jsxSingleQuote": false, 5 | "printWidth": 90, 6 | "proseWrap": "always", 7 | "semi": true, 8 | "singleQuote": false, 9 | "tabWidth": 2, 10 | "trailingComma": "all" 11 | } 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | ``` 12 | 13 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 14 | 15 | You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file. 16 | 17 | ## Learn More 18 | 19 | To learn more about Next.js, take a look at the following resources: 20 | 21 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 22 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 23 | 24 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 25 | 26 | ## Deploy on Vercel 27 | 28 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/import?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 29 | 30 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 31 | -------------------------------------------------------------------------------- /components/detailspage/DetailsTable.jsx: -------------------------------------------------------------------------------- 1 | import { Box, Flex, Grid, Heading, Text, useColorModeValue } from "@chakra-ui/react"; 2 | import TimeAgo from "timeago-react"; 3 | 4 | const DetailsTable = ({ data, borderColor }) => { 5 | return ( 6 | 14 | 15 | 16 | Release info 17 | 18 | 19 | 20 | 21 | 22 | Release 23 | 24 | {data.name} 25 | 26 | 27 | Group 28 | 29 | {data.group} 30 | 31 | 32 | Added 33 | 34 | 35 | {new Date(data.added).toLocaleString()} ( 36 | ) 37 | 38 | 39 | 40 | Section 41 | 42 | {data.section} 43 | 44 | 45 | No. of files 46 | 47 | {data.files || "-"} 48 | 49 | 50 | Size 51 | 52 | {data.size ? `${data.size} MB` : "-"} 53 | 54 | 55 | Genre 56 | 57 | {data.genre || "-"} 58 | 59 | 60 | Retail link 61 | 62 | {data.url || "-"} 63 | 64 | 65 | Tracers 66 | 67 | 68 | {data.traces 69 | ? data.traces.map((tr) => `#${tr.rank} ${tr.site}`).join(", ") 70 | : "-"} 71 | 72 | 73 | 74 | Nukes 75 | 76 | 77 | {!data.nukes || data.nukes.length === 0 ? ( 78 | "-" 79 | ) : ( 80 | 81 | {data.nukes.map((nuke) => ( 82 |
  • 83 | 91 | [{nuke.type}]{" "} 92 | 93 | {nuke.reason} 94 |
  • 95 | ))} 96 |
    97 | )} 98 |
    99 |
    100 |
    101 | ); 102 | }; 103 | 104 | export default DetailsTable; 105 | -------------------------------------------------------------------------------- /components/detailspage/Files.jsx: -------------------------------------------------------------------------------- 1 | import { Box, Heading, useColorMode } from "@chakra-ui/react"; 2 | 3 | const RetailInfo_Movie_TMDB = ({ data, borderColor }) => { 4 | const { colorMode } = useColorMode(); 5 | 6 | console.log(data); 7 | if (!data) return null; 8 | 9 | return ( 10 | 17 | 18 | 19 | Files 20 | 21 | 22 | 23 | ); 24 | }; 25 | 26 | export default RetailInfo_Movie_TMDB; 27 | -------------------------------------------------------------------------------- /components/detailspage/NFO.jsx: -------------------------------------------------------------------------------- 1 | import { Box, Button, Image, useColorMode } from "@chakra-ui/react"; 2 | import { FiDownload } from "react-icons/fi"; 3 | import { API_BASE, API_ENDPOINT } from "../../utils/routes"; 4 | 5 | const NFO = ({ data, borderColor }) => { 6 | const { colorMode } = useColorMode(); 7 | 8 | if (!data.nfo) return null; 9 | 10 | const downloadLink = `${API_BASE + API_ENDPOINT.DOWNLOAD}/${data.name}/${ 11 | data.nfo[0].filename 12 | }`; 13 | 14 | return ( 15 | 22 | 23 | 34 | 35 | 36 | Loading...} 39 | mx="auto" 40 | alt="" 41 | /> 42 | 43 | ); 44 | }; 45 | 46 | export default NFO; 47 | -------------------------------------------------------------------------------- /components/detailspage/Proof.jsx: -------------------------------------------------------------------------------- 1 | import { DownloadIcon, Flex, IconButton, Image, Text } from "@chakra-ui/react"; 2 | 3 | const Proof = ({ proof }) => { 4 | if (!proof) return null; 5 | 6 | return ( 7 | 8 | 9 | dolittle.2020.multi.complete.uhd.bluray-orca-proof.jpg 10 | 17 | 18 | 26 | 27 | ); 28 | }; 29 | 30 | export default Proof; 31 | -------------------------------------------------------------------------------- /components/detailspage/RetailInfo_Discogs.jsx: -------------------------------------------------------------------------------- 1 | export const RetailInfo_Discogs = ({ data }) => { 2 | return
    {JSON.stringify(data, null, 4)}
    ; 3 | }; 4 | -------------------------------------------------------------------------------- /components/detailspage/RetailInfo_Movie_TMDB.jsx: -------------------------------------------------------------------------------- 1 | import { 2 | Box, 3 | Flex, 4 | Grid, 5 | Heading, 6 | Image, 7 | Link, 8 | Stack, 9 | Text, 10 | useColorMode, 11 | } from "@chakra-ui/react"; 12 | import TimeAgo from "timeago-react"; 13 | 14 | const RetailInfo_Movie_TMDB = ({ data, borderColor }) => { 15 | const { colorMode } = useColorMode(); 16 | const fallbackSrc = { 17 | dark: "/movie-placeholder-dark.png", 18 | light: "/movie-placeholder-light.png", 19 | }; 20 | 21 | if (!data) return null; 22 | 23 | return ( 24 | 31 | 32 | 33 | Retail info 34 | 35 | 36 | 37 | 43 | 44 | 45 | Title 46 | 47 | 48 | {data.original_title} 49 | {data.title !== data.original_title && ` (${data.title})`} 50 | 51 | 52 | 53 | Language 54 | 55 | 56 | {data.original_language ? data.original_language.toUpperCase() : "-"} 57 | 58 | 59 | 60 | Produced by 61 | 62 | 63 | {data.production_companies.length > 0 64 | ? data.production_companies.map((n) => n.name).join(", ") 65 | : "-"} 66 | 67 | 68 | 69 | Country 70 | 71 | 72 | {data.production_countries.length > 0 73 | ? data.production_countries.map((n) => n.name).join(", ") 74 | : "-"} 75 | 76 | 77 | 78 | Budget 79 | 80 | 81 | {data.budget 82 | ? "$ " + data.budget.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") 83 | : "-"} 84 | 85 | 86 | 87 | Revenue 88 | 89 | 90 | {data.revenue 91 | ? "$ " + data.revenue.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") 92 | : "-"} 93 | 94 | 95 | 96 | Runtime 97 | 98 | {data.runtime ? `${data.runtime} min` : "-"} 99 | 100 | 101 | Genre 102 | 103 | 104 | {data.genres.length > 0 ? data.genres.map((g) => g.name).join(", ") : "-"} 105 | 106 | 107 | 108 | IMDB 109 | 110 | 111 | {data.imdb_id ? ( 112 | 113 | {`https://imdb.com/title/${data.imdb_id}`} 114 | 115 | ) : ( 116 | "-" 117 | )} 118 | 119 | 120 | 121 | Rating 122 | 123 | 124 | {data.vote_count === 0 125 | ? "-" 126 | : `${data.vote_average} / 10 (${data.vote_count} votes, TMDB)`} 127 | 128 | 129 | 130 | Released 131 | 132 | 133 | {data.release_date || "-"} () 134 | 135 | 136 | 137 | Overview 138 | 139 | {data.overview || "-"} 140 | 141 | 142 | Missing data? Fill it{" "} 143 | 144 | here! 145 | 146 | 147 | 148 | 149 | poster 164 | 165 | 166 | ); 167 | }; 168 | 169 | export default RetailInfo_Movie_TMDB; 170 | -------------------------------------------------------------------------------- /components/detailspage/RetailInfo_TV.jsx: -------------------------------------------------------------------------------- 1 | import { Flex, Link, Grid, Heading, Image, Text } from "@chakra-ui/react"; 2 | import TimeAgo from "timeago-react"; 3 | 4 | const RetailTable = ({ data, borderColor }) => { 5 | console.log; 6 | if (!data) 7 | return ( 8 | 9 | Retail info not available 10 | 11 | ); 12 | 13 | return ( 14 | 22 | 23 | 24 | Series name 25 | 26 | {data.seriesName} 27 | 28 | 29 | Network 30 | 31 | {data.network || "-"} 32 | 33 | 34 | First aired 35 | 36 | 37 | {data.firstAired} ( 38 | ) 39 | 40 | 41 | 42 | Air date 43 | 44 | {`${data.airsDayOfWeek}, ${data.airsTime}`} 45 | 46 | 47 | Runtime 48 | 49 | {`${data.runtime} min` || "-"} 50 | 51 | 52 | Genre 53 | 54 | {data.genre?.join(", ")} 55 | 56 | 57 | Imdb 58 | 59 | 60 | 61 | {`https://www.imdb.com/title/${data.imdbId}`} 62 | 63 | 64 | 65 | 66 | Rating 67 | 68 | 69 | {data.siteRating === 0 70 | ? "-" 71 | : `${data.siteRating} / 10 (${data.siteRatingCount} votes, TVDB)`} 72 | 73 | 74 | 75 | Status 76 | 77 | {data.status} 78 | 79 | 80 | Overview 81 | 82 | {data.overview || "-"} 83 | 84 | 85 | poster 94 | 95 | ); 96 | }; 97 | 98 | export default RetailTable; 99 | -------------------------------------------------------------------------------- /components/detailspage/RetailInfo_TV_TMDB.jsx: -------------------------------------------------------------------------------- 1 | import { 2 | Box, 3 | Grid, 4 | Heading, 5 | Image, 6 | Link, 7 | Stack, 8 | Text, 9 | useColorMode, 10 | useColorModeValue, 11 | } from "@chakra-ui/react"; 12 | import TimeAgo from "timeago-react"; 13 | 14 | const RetailInfo_TV_TMDB = ({ data, borderColor }) => { 15 | const { colorMode } = useColorMode(); 16 | const fallbackSrc = { 17 | dark: "/movie-placeholder-dark.png", 18 | light: "/movie-placeholder-light.png", 19 | }; 20 | 21 | console.log(data); 22 | if (!data) return null; 23 | 24 | return ( 25 | 32 | 33 | 34 | Retail info 35 | 36 | 37 | 38 | 44 | 45 | 46 | Series name 47 | 48 | 49 | {data.original_name} 50 | {data.name !== data.original_name && ` (${data.name})`} 51 | 52 | 53 | 54 | Type 55 | 56 | {data.type || "-"} 57 | 58 | 59 | Country 60 | 61 | 62 | {data.origin_country.length > 0 ? data.origin_country.join(", ") : "-"} 63 | 64 | 65 | 66 | Network 67 | 68 | 69 | {data.networks.length > 0 ? data.networks.map((n) => n.name).join(", ") : "-"} 70 | 71 | 72 | 73 | First aired 74 | 75 | 76 | {data.first_air_date ? ( 77 | <> 78 | {data.first_air_date} () 79 | 80 | ) : ( 81 | "-" 82 | )} 83 | 84 | 85 | 86 | Runtime 87 | 88 | 89 | {data.episode_run_time.length > 0 90 | ? data.episode_run_time.map((t) => t + " min").join(", ") 91 | : "-"} 92 | 93 | 94 | 95 | Genre 96 | 97 | 98 | {data.genres.length > 0 ? data.genres.map((g) => g.name).join(", ") : "-"} 99 | 100 | 101 | 102 | Homepage 103 | 104 | 105 | 106 | {data.homepage || "-"} 107 | 108 | 109 | 110 | 111 | Rating 112 | 113 | 114 | {data.vote_count === 0 115 | ? "-" 116 | : `${data.vote_average} / 10 (${data.vote_count} votes, TMDB)`} 117 | 118 | 119 | 120 | Status 121 | 122 | {data.status || "-"} 123 | 124 | 125 | Overview 126 | 127 | {data.overview || "-"} 128 | 129 | 130 | Missing data? Fill it{" "} 131 | 132 | here! 133 | 134 | 135 | 136 | 137 | poster image 153 | 154 | 155 | ); 156 | }; 157 | 158 | export default RetailInfo_TV_TMDB; 159 | -------------------------------------------------------------------------------- /components/layout/Footer.jsx: -------------------------------------------------------------------------------- 1 | import { Box, Container, Flex, Link, Text } from "@chakra-ui/react"; 2 | import NextLink from "next/link"; 3 | 4 | export const Footer = () => { 5 | return ( 6 | 7 | 15 | 21 | Made in Budapest, Hungary 22 | 23 | • 24 | 25 | Build: {process.env.NEXT_PUBLIC_GIT_SHA} 26 | 27 | 28 | 29 | 30 | About 31 | 32 | 33 | RSS 34 | 35 | 36 | 37 | API 38 | 39 | 40 | 41 | 42 | 43 | ); 44 | }; 45 | -------------------------------------------------------------------------------- /components/layout/GithubLink.jsx: -------------------------------------------------------------------------------- 1 | import { IconButton } from "@chakra-ui/react"; 2 | import { DiGithubBadge } from "react-icons/di"; 3 | 4 | export const GithubLink = () => ( 5 | } 7 | as="a" 8 | href="https://github.com/la55u/predb-frontend" 9 | target="_blank" 10 | rel="noopener noreferrer" 11 | title="Source code on Github" 12 | color="current" 13 | variant="ghost" 14 | fontSize="24px" 15 | /> 16 | ); 17 | -------------------------------------------------------------------------------- /components/layout/Layout.jsx: -------------------------------------------------------------------------------- 1 | import { Box, Container, Flex } from "@chakra-ui/react"; 2 | import { useEffect } from "react"; 3 | import { useDispatch, useSelector } from "react-redux"; 4 | import { getMe } from "../../redux/slices/authSlice"; 5 | import { Footer } from "./Footer"; 6 | import Navbar from "./Navbar"; 7 | import { setAuthenticated } from "../../redux/slices/authSlice"; 8 | import { useSocket } from "../../hooks/useSocket"; 9 | import Head from "next/head"; 10 | 11 | const Layout = ({ title, children }) => { 12 | const dispatch = useDispatch(); 13 | useSocket(); 14 | 15 | const { isAuthenticated, user } = useSelector((state) => state.auth); 16 | 17 | useEffect(() => { 18 | const t = localStorage.getItem("auth"); 19 | if (t) { 20 | dispatch(setAuthenticated()); 21 | } 22 | if (isAuthenticated && !user) dispatch(getMe()); 23 | }, [isAuthenticated]); 24 | 25 | return ( 26 | <> 27 | 28 | {title ? `${title} | ` : ""} PREdb | Warez Scene Database 29 | 30 | 31 | 32 | 33 | 34 | 35 | {children} 36 | 37 | 38 |