├── .env.local.example ├── .prettierignore ├── public ├── favicon.ico ├── movie-placeholder-dark.png ├── movie-placeholder-light.png ├── dolittle.2020.multi.complete.uhd.bluray-orca-proof.jpg └── settings-knobs.svg ├── styles ├── foundations │ ├── radii.js │ ├── colors.js │ ├── index.js │ └── typography.js ├── components │ ├── index.js │ ├── container.js │ ├── button.js │ └── input.js ├── global.css ├── index.js └── styles.js ├── .eslintrc.json ├── components ├── detailspage │ ├── RetailInfo_Discogs.jsx │ ├── Files.jsx │ ├── Proof.jsx │ ├── NFO.jsx │ ├── RetailInfo_TV.jsx │ ├── DetailsTable.jsx │ ├── RetailInfo_TV_TMDB.jsx │ └── RetailInfo_Movie_TMDB.jsx ├── releases │ ├── NoResults.jsx │ ├── CopyButon.jsx │ ├── Pagination.jsx │ ├── ReleaseList.jsx │ ├── CategoryBadge.jsx │ └── ReleaseRow.jsx ├── layout │ ├── GithubLink.jsx │ ├── ThemeSwitcher.jsx │ ├── Layout.jsx │ ├── Footer.jsx │ ├── Logo.jsx │ ├── MobileMenu.jsx │ └── Navbar.jsx ├── profile │ ├── ActionGroup.jsx │ └── DeleteAccountModal.jsx ├── search │ ├── SearchTabs.jsx │ ├── Toolbar.jsx │ ├── SearchAdvanced.jsx │ └── SearchSimple.jsx ├── notifications │ └── NotificationCard.jsx └── modals │ └── ModalSubscribe.jsx ├── pages ├── api │ └── hello.js ├── about.jsx ├── logout.jsx ├── index.jsx ├── rss.jsx ├── _app.jsx ├── stats.jsx ├── notifications.jsx ├── release │ └── [_id].jsx ├── restore.jsx ├── login.jsx ├── register.jsx └── profile.jsx ├── .prettierrc ├── next.config.js ├── hooks ├── useDebounce.js ├── useLocalStorage.js ├── useSocket.js └── useFetch.js ├── utils ├── sites.js ├── routes.js ├── helpers.js └── classify.js ├── .gitignore ├── context └── SearchContext.jsx ├── redux ├── store.js └── slices │ ├── statsSlice.js │ ├── toastSlice.js │ ├── searchSlice.js │ ├── releasesSlice.js │ ├── notificationSlice.js │ └── authSlice.js ├── package.json └── README.md /.env.local.example: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_API_BASE=https://api.predb.live -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | coverage 4 | .next 5 | build 6 | public -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/la55u/predb-frontend/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /styles/foundations/radii.js: -------------------------------------------------------------------------------- 1 | export default { 2 | sm: "3px", 3 | md: "5px", 4 | lg: "7px", 5 | }; 6 | -------------------------------------------------------------------------------- /public/movie-placeholder-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/la55u/predb-frontend/HEAD/public/movie-placeholder-dark.png -------------------------------------------------------------------------------- /public/movie-placeholder-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/la55u/predb-frontend/HEAD/public/movie-placeholder-light.png -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals", 3 | "rules": { 4 | "react/no-unescaped-entities": 0 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /components/detailspage/RetailInfo_Discogs.jsx: -------------------------------------------------------------------------------- 1 | export const RetailInfo_Discogs = ({ data }) => { 2 | return
{JSON.stringify(data, null, 4)}
; 3 | }; 4 | -------------------------------------------------------------------------------- /public/dolittle.2020.multi.complete.uhd.bluray-orca-proof.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/la55u/predb-frontend/HEAD/public/dolittle.2020.multi.complete.uhd.bluray-orca-proof.jpg -------------------------------------------------------------------------------- /pages/api/hello.js: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | 3 | export default (req, res) => { 4 | res.statusCode = 200; 5 | res.json({ name: "John Doe" }); 6 | }; 7 | -------------------------------------------------------------------------------- /styles/components/index.js: -------------------------------------------------------------------------------- 1 | import Button from "./button"; 2 | import Container from "./container"; 3 | import Input from "./input"; 4 | 5 | export default { 6 | Container, 7 | Input, 8 | Button, 9 | }; 10 | -------------------------------------------------------------------------------- /styles/foundations/colors.js: -------------------------------------------------------------------------------- 1 | export default { 2 | light: { 3 | bg: "rgb(237, 242, 247)", 4 | col: "gray.700", 5 | }, 6 | 7 | dark: { 8 | bg: "#0b0b13", 9 | col: "gainsboro", 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /styles/components/container.js: -------------------------------------------------------------------------------- 1 | export default { 2 | baseStyle: { 3 | width: "full", 4 | maxW: "1100px", 5 | }, 6 | variants: { 7 | fullwidth: { 8 | maxW: "1920px", 9 | }, 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /styles/foundations/index.js: -------------------------------------------------------------------------------- 1 | import colors from "./colors"; 2 | import radii from "./radii"; 3 | import typography from "./typography"; 4 | 5 | export default { 6 | colors, 7 | radii, 8 | ...typography, 9 | }; 10 | -------------------------------------------------------------------------------- /styles/foundations/typography.js: -------------------------------------------------------------------------------- 1 | export default { 2 | fonts: { 3 | heading: `'Heebo', sans-serif`, 4 | body: `'Heebo', sans-serif`, 5 | mono: `SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace`, 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | const withBundleAnalyzer = require("@next/bundle-analyzer")({ 2 | enabled: process.env.ANALYZE === "true", 3 | }); 4 | 5 | module.exports = withBundleAnalyzer({ 6 | i18n: { 7 | locales: ["en-US"], 8 | defaultLocale: "en-US", 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /pages/about.jsx: -------------------------------------------------------------------------------- 1 | import { Heading } from "@chakra-ui/react"; 2 | import Layout from "../components/layout/Layout"; 3 | 4 | const About = () => { 5 | return ( 6 | 7 | About 8 | 9 | ); 10 | }; 11 | 12 | export default About; 13 | -------------------------------------------------------------------------------- /styles/global.css: -------------------------------------------------------------------------------- 1 | @keyframes newitemfade { 2 | from { 3 | background: #4fd1c5; 4 | } 5 | to { 6 | background: transparent; 7 | } 8 | } 9 | .new-item { 10 | animation-name: newitemfade; 11 | animation-duration: 1.5s; 12 | } 13 | 14 | body { 15 | overflow-y: scroll; 16 | } 17 | -------------------------------------------------------------------------------- /styles/components/button.js: -------------------------------------------------------------------------------- 1 | const baseStyle = { 2 | borderRadius: "sm", 3 | }; 4 | 5 | const variants = { 6 | solid: (props) => { 7 | const { colorScheme: c } = props; 8 | return { 9 | shadow: "md", 10 | // bg: `${c}.200`, 11 | }; 12 | }, 13 | }; 14 | 15 | export default { 16 | baseStyle, 17 | variants, 18 | }; 19 | -------------------------------------------------------------------------------- /hooks/useDebounce.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | export const useDebounce = (value, timeout) => { 4 | const [state, setState] = useState(value); 5 | 6 | useEffect(() => { 7 | const handler = setTimeout(() => setState(value), timeout); 8 | 9 | return () => clearTimeout(handler); 10 | }, [value, timeout]); 11 | 12 | return state; 13 | }; 14 | -------------------------------------------------------------------------------- /pages/logout.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { useDispatch } from "react-redux"; 3 | import Layout from "../components/layout/Layout"; 4 | import { logout } from "../redux/slices/authSlice"; 5 | 6 | const Logout = () => { 7 | const dispatch = useDispatch(); 8 | 9 | useEffect(() => { 10 | dispatch(logout()); 11 | }, []); 12 | return ; 13 | }; 14 | 15 | export default Logout; 16 | -------------------------------------------------------------------------------- /components/releases/NoResults.jsx: -------------------------------------------------------------------------------- 1 | import { Box, Flex, Text } from "@chakra-ui/react"; 2 | import { MdZoomOut } from "react-icons/md"; 3 | 4 | export const NoResults = () => ( 5 | 12 | 13 | No results found 14 | 15 | ); 16 | -------------------------------------------------------------------------------- /styles/index.js: -------------------------------------------------------------------------------- 1 | import { extendTheme } from "@chakra-ui/react"; 2 | import components from "./components"; 3 | import foundations from "./foundations"; 4 | import styles from "./styles"; 5 | 6 | const config = { 7 | useSystemColorMode: false, 8 | initialColorMode: "dark", 9 | cssVarPrefix: "chakra", 10 | }; 11 | 12 | export const theme = extendTheme({ 13 | ...foundations, 14 | components, 15 | styles, 16 | config, 17 | }); 18 | -------------------------------------------------------------------------------- /utils/sites.js: -------------------------------------------------------------------------------- 1 | export const SITES = { 2 | AL: "Acid-Lounge", 3 | AR: "AlphaRatio", 4 | FF: "FunFile", 5 | IPT: "IPTorrents", 6 | ME: "Milkie", 7 | nC: "nCore", 8 | PTM: "PreToMe", 9 | PTF: "PTFiles", 10 | RTT: "RevolutionTT", 11 | SBS: "SuperBits", 12 | SPC: "Speed.cd", 13 | TD: "TorrentDay", 14 | TL: "TorrentLeech", 15 | TN: "", 16 | TS: "TorrentSeeds", 17 | Tby: "TorrentBytes", 18 | XS: "XSpeeds", 19 | }; 20 | -------------------------------------------------------------------------------- /hooks/useLocalStorage.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | export const useLocalStorage = (key, defaultValue) => { 4 | const stored = localStorage.getItem(key); 5 | const initial = stored ? JSON.parse(stored) : defaultValue; 6 | const [value, setValue] = useState(initial); 7 | 8 | useEffect(() => { 9 | localStorage.setItem(key, JSON.stringify(value)); 10 | }, [key, value]); 11 | 12 | return [value, setValue]; 13 | }; 14 | -------------------------------------------------------------------------------- /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/profile/ActionGroup.jsx: -------------------------------------------------------------------------------- 1 | import { Box, Heading, useColorModeValue } from "@chakra-ui/react"; 2 | 3 | export const ActionGroup = ({ title, children, ...rest }) => { 4 | const bg = useColorModeValue("blackAlpha.100", "whiteAlpha.50"); 5 | 6 | return ( 7 | <> 8 | 9 | {title} 10 | 11 | 12 | {children} 13 | 14 | 15 | ); 16 | }; 17 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /public/settings-knobs.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /pages/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Layout from "../components/layout/Layout"; 3 | import { Pagination } from "../components/releases/Pagination"; 4 | import ReleaseList from "../components/releases/ReleaseList"; 5 | import SearchTabs from "../components/search/SearchTabs"; 6 | import Toolbar from "../components/search/Toolbar"; 7 | 8 | const Home = () => { 9 | return ( 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | ); 19 | }; 20 | 21 | export default Home; 22 | -------------------------------------------------------------------------------- /components/layout/ThemeSwitcher.jsx: -------------------------------------------------------------------------------- 1 | import { MoonIcon, SunIcon } from "@chakra-ui/icons"; 2 | import { IconButton, useColorMode, useColorModeValue } from "@chakra-ui/react"; 3 | 4 | export const ThemeSwitcher = () => { 5 | const { toggleColorMode } = useColorMode(); 6 | const icon = useColorModeValue(, ); 7 | 8 | return ( 9 | 19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /context/SearchContext.jsx: -------------------------------------------------------------------------------- 1 | import { createContext } from "react"; 2 | 3 | export function searchReducer(state, action) { 4 | switch (action.type) { 5 | case "SEARCH_START": 6 | return { loading: true, error: null, data: null }; 7 | case "SEARCH_SUCCESS": 8 | return { loading: false, error: null, data: action.payload }; 9 | case "SEARCH_FAIL": 10 | return { loading: false, error: action.payload, data: null }; 11 | } 12 | } 13 | 14 | export const initialState = { 15 | data: null, 16 | error: null, 17 | loading: false, 18 | }; 19 | 20 | export const SearchContext = createContext(null); 21 | export const StateContext = createContext(null); 22 | -------------------------------------------------------------------------------- /redux/store.js: -------------------------------------------------------------------------------- 1 | import { configureStore } from "@reduxjs/toolkit"; 2 | import authReducer from "./slices/authSlice"; 3 | import notificationSlice from "./slices/notificationSlice"; 4 | import releasesReducer from "./slices/releasesSlice"; 5 | import searchReducer from "./slices/searchSlice"; 6 | import statsSlice from "./slices/statsSlice"; 7 | import toastReducer from "./slices/toastSlice"; 8 | 9 | export default configureStore({ 10 | reducer: { 11 | releases: releasesReducer, 12 | search: searchReducer, 13 | auth: authReducer, 14 | toast: toastReducer, 15 | notifications: notificationSlice, 16 | stats: statsSlice, 17 | }, 18 | devTools: true, 19 | }); 20 | -------------------------------------------------------------------------------- /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/search/SearchTabs.jsx: -------------------------------------------------------------------------------- 1 | import { Tab, TabList, TabPanel, TabPanels, Tabs } from "@chakra-ui/react"; 2 | import React from "react"; 3 | import SearchAdvanced from "./SearchAdvanced"; 4 | import SearchSimple from "./SearchSimple"; 5 | 6 | const SearchTabs = () => { 7 | return ( 8 | 9 | 10 | Simple 11 | Advanced 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | ); 24 | }; 25 | 26 | export default SearchTabs; 27 | -------------------------------------------------------------------------------- /utils/routes.js: -------------------------------------------------------------------------------- 1 | export const API_BASE = process.env.NEXT_PUBLIC_API_BASE; 2 | 3 | export const ROUTES = { 4 | home: "/", 5 | release: "/release/[rid]", 6 | login: "/login", 7 | register: "/register", 8 | restore: "/restore", 9 | }; 10 | 11 | export const API_ENDPOINT = { 12 | RELEASES: "/api/data", 13 | COUNT: "/api/data/count", 14 | DOWNLOAD: "/api/data/download", 15 | STATS: "/api/stats", 16 | 17 | SEARCH_SIMPLE: "/api/search", 18 | SEARCH_ADVANCED: "/api/search/advanced", 19 | 20 | LOGIN: "/api/auth/login", 21 | LOGOUT: "/api/auth/logout", 22 | REGISTER: "/api/auth/register", 23 | RESTORE: "/api/auth/restore", 24 | RESTORE_PASSWORD: "/api/auth/restore/password", 25 | DELETE: "/api/auth/delete", 26 | ME: "/api/auth/me", 27 | 28 | NOTIFICATIONS: "/api/notifications", 29 | }; 30 | -------------------------------------------------------------------------------- /styles/components/input.js: -------------------------------------------------------------------------------- 1 | import { mode } from "@chakra-ui/theme-tools"; 2 | 3 | export default { 4 | variants: { 5 | filled: (props) => { 6 | return { 7 | field: { 8 | color: mode("light.col", "dark.col")(props), 9 | bg: mode("white", "whiteAlpha.50")(props), 10 | shadow: "sm", 11 | _placeholder: { 12 | color: mode("light.col", "dark.col")(props), 13 | opacity: 0.6, 14 | }, 15 | _focus: { 16 | borderColor: mode("black", "teal.300")(props), 17 | bg: mode("white", "transparent")(props), 18 | }, 19 | _hover: { 20 | bg: mode("gray.50", "whiteAlpha.100")(props), 21 | }, 22 | }, 23 | }; 24 | }, 25 | }, 26 | defaultProps: { 27 | variant: "filled", 28 | }, 29 | }; 30 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /redux/slices/statsSlice.js: -------------------------------------------------------------------------------- 1 | import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; 2 | import { API_BASE, API_ENDPOINT } from "../../utils/routes"; 3 | 4 | export const getStats = createAsyncThunk("stats/getStats", async (_, thunkApi) => { 5 | const res = await fetch(API_BASE + API_ENDPOINT.STATS); 6 | const data = await res.json(); 7 | return data; 8 | }); 9 | 10 | const statsSlice = createSlice({ 11 | name: "stats", 12 | initialState: { 13 | loading: false, 14 | stats: null, 15 | error: null, 16 | }, 17 | reducers: {}, 18 | extraReducers: { 19 | [getStats.pending]: (state) => { 20 | state.loading = true; 21 | }, 22 | [getStats.fulfilled]: (state, action) => { 23 | state.loading = false; 24 | state.stats = action.payload; 25 | }, 26 | [getStats.rejected]: (state, action) => { 27 | state.loading = false; 28 | }, 29 | }, 30 | }); 31 | 32 | export default statsSlice.reducer; 33 | -------------------------------------------------------------------------------- /components/releases/CopyButon.jsx: -------------------------------------------------------------------------------- 1 | import { IconButton, useClipboard, useToast } from "@chakra-ui/react"; 2 | import { HiCheck } from "react-icons/hi"; 3 | import { RiFileCopyLine } from "react-icons/ri"; 4 | 5 | const CopyButton = ({ value, ...rest }) => { 6 | const { onCopy, hasCopied } = useClipboard(value); 7 | const toast = useToast(); 8 | 9 | const handleClick = () => { 10 | onCopy(); 11 | toast({ 12 | description: "Name copied to clipboard", 13 | status: "success", 14 | duration: 3000, 15 | isClosable: false, 16 | }); 17 | }; 18 | 19 | return ( 20 | : } 28 | onClick={handleClick} 29 | {...rest} 30 | /> 31 | ); 32 | }; 33 | 34 | export default CopyButton; 35 | -------------------------------------------------------------------------------- /styles/styles.js: -------------------------------------------------------------------------------- 1 | import { mode } from "@chakra-ui/theme-tools"; 2 | 3 | const styles = { 4 | global: (props) => ({ 5 | body: { 6 | color: mode("light.col", "dark.col")(props), 7 | bg: mode("light.bg", "dark.bg")(props), 8 | transition: "background-color 0.2s", 9 | lineHeight: "base", 10 | }, 11 | "::selection": { 12 | background: mode("black", "teal.300")(props), 13 | color: mode("teal.300", "black")(props), 14 | }, 15 | "*::placeholder": { 16 | color: mode("gray.400", "whiteAlpha.400")(props), 17 | }, 18 | "*, *::before, &::after": { 19 | borderColor: mode("gray.200", "whiteAlpha.300")(props), 20 | wordWrap: "break-word", 21 | }, 22 | /* clears the ‘X’ from Chrome */ 23 | "input[type='search']::-webkit-search-decoration, input[type='search']::-webkit-search-cancel-button, input[type='search']::-webkit-search-results-button, input[type='search']::-webkit-search-results-decoration": 24 | { appearance: "none" }, 25 | }), 26 | }; 27 | 28 | export default styles; 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "predb-live", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "NEXT_PUBLIC_GIT_SHA=`git rev-parse --short HEAD` next dev", 7 | "build": "NEXT_PUBLIC_GIT_SHA=`git rev-parse --short HEAD` next build", 8 | "start": "next start", 9 | "anal": "ANALYZE=true next build", 10 | "lint": "next lint" 11 | }, 12 | "dependencies": { 13 | "@chakra-ui/icons": "^1.0.15", 14 | "@chakra-ui/react": "^1.6.6", 15 | "@emotion/react": "^11.4.1", 16 | "@emotion/styled": "^11.3.0", 17 | "@reduxjs/toolkit": "^1.6.1", 18 | "focus-visible": "^5.2.0", 19 | "framer-motion": "^4.1.17", 20 | "lodash": "^4.17.21", 21 | "lodash.debounce": "^4.0.8", 22 | "next": "^11.1.0", 23 | "react": "17.0.2", 24 | "react-dom": "17.0.2", 25 | "react-icons": "^4.2.0", 26 | "react-redux": "^7.2.4", 27 | "socket.io-client": "^4.1.3", 28 | "timeago-react": "^3.0.3" 29 | }, 30 | "devDependencies": { 31 | "@next/bundle-analyzer": "^11.1.0", 32 | "eslint": "7.32.0", 33 | "eslint-config-next": "11.1.0", 34 | "prettier": "^2.3.2" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /hooks/useSocket.js: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { useDispatch } from "react-redux"; 3 | import io from "socket.io-client"; 4 | import { addRelease, updateRelease } from "../redux/slices/releasesSlice"; 5 | import { API_BASE } from "../utils/routes"; 6 | 7 | export function useSocket() { 8 | const dispatch = useDispatch(); 9 | 10 | useEffect(() => { 11 | const socket = io(API_BASE); 12 | 13 | // custom events from API 14 | socket.on("data_added", handleNew); 15 | socket.on("data_updated", handleUpdate); 16 | 17 | // Socket.IO default events 18 | socket.on("connect", () => console.log("Socket connected")); 19 | socket.on("disconnect", () => console.log("Socket disconnected")); 20 | socket.on("connect_error", (err) => console.log("Connection error:", err.message)); 21 | 22 | return () => socket.disconnect(); 23 | }, []); 24 | 25 | const handleNew = (payload) => { 26 | console.log("new:", payload); 27 | dispatch(addRelease(payload)); 28 | }; 29 | 30 | const handleUpdate = (payload) => { 31 | console.log("update:", payload); 32 | dispatch(updateRelease(payload)); 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /redux/slices/toastSlice.js: -------------------------------------------------------------------------------- 1 | import { createStandaloneToast } from "@chakra-ui/toast"; 2 | import { createSlice } from "@reduxjs/toolkit"; 3 | import { theme } from "../../styles"; 4 | 5 | const toast = createStandaloneToast({ theme }); 6 | 7 | const toastSlice = createSlice({ 8 | name: "toast", 9 | initialState: {}, 10 | reducers: { 11 | addToast: (state, action) => { 12 | const { title, description, status } = action.payload; 13 | toast({ 14 | title, 15 | description, 16 | status, 17 | }); 18 | }, 19 | addSuccessToast: (state, action) => { 20 | const { title, description } = action.payload; 21 | toast({ 22 | title, 23 | description, 24 | status: "success", 25 | duration: 5000, 26 | }); 27 | }, 28 | addErrorToast: (state, action) => { 29 | const { title, description } = action.payload; 30 | toast({ 31 | title, 32 | description, 33 | status: "error", 34 | duration: 8000, 35 | }); 36 | }, 37 | }, 38 | extraReducers: {}, 39 | }); 40 | 41 | export const { addToast, addSuccessToast, addErrorToast } = toastSlice.actions; 42 | export default toastSlice.reducer; 43 | -------------------------------------------------------------------------------- /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/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/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 |