diff --git a/.env.example b/.env.example index 3edacf3..183c768 100644 --- a/.env.example +++ b/.env.example @@ -1,11 +1,16 @@ +PORT= REACT_APP_SITENAME= REACT_APP_SITEURL= REACT_APP_INDEX_TITLE_TEXT= -REACT_APP_DISQUS_SHORTNAME= REACT_APP_HEADER_LOGO_TYPE= +REACT_APP_DISQUS_SHORTNAME= REACT_APP_DEV_API_URL= +REACT_APP_DEV_ADMIN_URL= +REACT_APP_JIKAN_INSTANCE_URL= REACT_APP_GA_USER_ID= REACT_APP_SSS_PAGE= +REACT_APP_USE_SERVICE_WORKER= +REACT_APP_DEFAULT_LANG= REACT_APP_META_DESCRIPTION= REACT_APP_META_AUTHOR= diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..2d2e9a0 --- /dev/null +++ b/.env.production @@ -0,0 +1 @@ +INLINE_RUNTIME_CHUNK=false \ No newline at end of file diff --git a/.gitignore b/.gitignore index 3de2007..5090264 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,5 @@ src/static/fullLogo.gif /src/static/logo.png /src/static/fullLogo.png /src/static/fullLogo-dark.png + +.eslintcache diff --git a/README.md b/README.md index e2b501c..85aab14 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@

-[React](https://github.com/facebook/react), [ReactN](https://github.com/CharlesStover/reactn) ve [Material-UI](https://github.com/mui-org/material-ui) kullanarak hazırlandı. +[React](https://github.com/facebook/react) ve [Material-UI](https://github.com/mui-org/material-ui) kullanarak hazırlandı. ## Yükleme Talimatları diff --git a/package.json b/package.json index 1d6c21e..c8115f6 100644 --- a/package.json +++ b/package.json @@ -1,73 +1,76 @@ { "name": "forfansubsfront", - "version": "3.2.1", - "private": false, - "dependencies": { - "@fortawesome/fontawesome-svg-core": "^1.2.28", - "@fortawesome/free-brands-svg-icons": "^5.13.0", - "@fortawesome/free-regular-svg-icons": "^5.13.0", - "@fortawesome/free-solid-svg-icons": "^5.13.0", - "@fortawesome/react-fontawesome": "^0.1.10", - "@material-ui/core": "^4.10.1", - "@material-ui/icons": "^4.9.1", - "@material-ui/lab": "^4.0.0-alpha.55", - "awesome-debounce-promise": "^2.1.0", - "axios": "^0.21.1", - "core-js": "^3.6.5", - "cron": "^1.8.2", - "date-fns": "^2.14.0", - "disqus-react": "^1.0.8", - "lodash": "^4.17.19", - "lodash-es": "^4.17.15", - "markdown-to-jsx": "^6.11.4", - "node-sass": "^4.14.1", - "notistack": "^0.9.16", - "react": "^16.13.1", - "react-app-polyfill": "^1.0.6", - "react-async-hook": "^3.6.1", - "react-dom": "^16.13.1", - "react-dotdotdot": "^1.3.1", - "react-ga": "^2.7.0", - "react-helmet-async": "^1.0.6", - "react-infinite-scroll-component": "^5.0.5", - "react-interval": "^2.1.1", - "react-lazyload": "^2.6.7", - "react-parallax": "^3.0.3", - "react-router-dom": "^5.2.0", - "react-scripts": "3.4.1", - "react-swipeable": "^5.5.1", - "react-toastify": "^6.0.5", - "reactn": "^2.2.7", - "reactn-devtools": "^1.1.0", - "redux": "^4.0.5", - "rellax": "^1.12.1", - "remarkable": "^2.0.1", - "source-map-explorer": "^2.4.2" - }, - "resolutions": { - "minimist": "^1.2.3" - }, - "scripts": { - "start": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test", - "eject": "react-scripts eject", - "analyze": "source-map-explorer build/static/js/*" - }, - "eslintConfig": { - "extends": "react-app" - }, - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] - }, - "proxy": "http://localhost:5000" + "version": "4.0.0", + "private": false, + "dependencies": { + "@fortawesome/fontawesome-svg-core": "^1.2.32", + "@fortawesome/free-brands-svg-icons": "^5.15.1", + "@fortawesome/free-regular-svg-icons": "^5.15.1", + "@fortawesome/free-solid-svg-icons": "^5.15.1", + "@fortawesome/react-fontawesome": "^0.1.13", + "@material-ui/core": "^4.11.1", + "@material-ui/icons": "^4.9.1", + "@material-ui/lab": "^4.0.0-alpha.56", + "@nivo/bar": "^0.65.1", + "@nivo/core": "^0.66.0", + "awesome-debounce-promise": "^2.1.0", + "axios": "^0.21.0", + "core-js": "^3.7.0", + "date-fns": "^2.16.1", + "disqus-react": "^1.0.10", + "flag-icon-css": "^3.5.0", + "i18next": "^19.8.4", + "i18next-browser-languagedetector": "^6.0.1", + "i18next-http-backend": "^1.0.21", + "lodash-es": "^4.17.15", + "markdown-to-jsx": "^7.1.0", + "notistack": "^1.0.2", + "react": "^17.0.1", + "react-app-polyfill": "^2.0.0", + "react-device-detect": "^1.17.0", + "react-dom": "^17.0.1", + "react-dotdotdot": "^1.3.1", + "react-ga": "^3.3.0", + "react-helmet-async": "^1.0.7", + "react-i18next": "^11.7.3", + "react-icons": "^4.1.0", + "react-infinite-scroll-component": "^5.1.0", + "react-interval": "^2.1.1", + "react-lazyload": "^3.1.0", + "react-router-dom": "^5.2.0", + "react-scripts": "4.0.1", + "react-swipeable": "^6.0.0", + "sass": "^1.32.8", + "web-vitals": "^1.0.1" + }, + "resolutions": { + "minimist": "^1.2.3" + }, + "devDependencies": { + "source-map-explorer": "^2.5.0" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject", + "analyze": "source-map-explorer build/static/js/*" + }, + "eslintConfig": { + "extends": "react-app" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version", + "not dead" + ] + }, + "proxy": "http://localhost:5000" } \ No newline at end of file diff --git a/src/App.js b/src/App.js index c857592..9c146a4 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,4 @@ -import React, { useEffect, lazy, Suspense } from 'react'; -import { useDispatch, useGlobal } from 'reactn' +import React, { useEffect, lazy, Suspense, useContext } from 'react'; import { BrowserRouter as Router, Route, Switch } from "react-router-dom"; import { HelmetProvider } from 'react-helmet-async' import Wrapper from './components/hoc/wrapper' @@ -12,6 +11,9 @@ import SSSPage from './pages/sss/index' import FourOhFourPage from './components/404/404' import EkipAlimlariPage from './pages/ekip-alimlari/index' import ExtraPagesList from './pages/extra-pages/index' +import SiteStatusContext from './contexts/site_status.context'; +import LoginPage from './pages/user/logIn' +import RegisterPage from './pages/user/register' const IndexPage = lazy(() => import('./pages/index/index')) const SearchPage = lazy(() => import('./pages/ara/index')) @@ -20,48 +22,50 @@ const MangaPage = lazy(() => import('./pages/ceviriler/manga/index')) const EpisodePage = lazy(() => import('./pages/episode/index')) const MangaEpisodePage = lazy(() => import('./pages/manga-episode/index')) const CompleteRegistrationPage = lazy(() => import('./pages/kayit-tamamla/index')) +const TakvimPage = lazy(() => import('./pages/calendar/index')) export default function App() { - const getOnline = useDispatch('getOnline') - const checkMobile = useDispatch('checkMobile') - const [online] = useGlobal('online') + const [siteStatus] = useContext(SiteStatusContext) + ReactGA.initialize(process.env.REACT_APP_GA_USER_ID); useEffect(() => { - getOnline() - checkMobile(navigator.userAgent || navigator.vendor || window.opera) - - }, [getOnline, checkMobile]) + }, []) - if (online === true) + if (siteStatus.status === true) return ( <> - - - }> - - - - - - - - - - - {ExtraPagesList.length ? ExtraPagesList.map(({ PageUrl, PageComponent }) => ( - - )) : ""} - - - - - + + }> + + + + + + + + + + + + + + + + {ExtraPagesList.length ? ExtraPagesList.map(({ PageUrl, PageComponent }) => ( + + )) : ""} + + + + + + ); - else if (online === false) return ( + else if (!siteStatus.loading && !siteStatus.status) return ( ) diff --git a/src/components/404/404.js b/src/components/404/404.js index dfd5e6b..8c61e36 100644 --- a/src/components/404/404.js +++ b/src/components/404/404.js @@ -1,9 +1,10 @@ -import React, { useState } from 'react' +import { useState } from 'react' import { Redirect } from 'react-router-dom' import { Typography, makeStyles } from '@material-ui/core' import { fourOhFourGif } from '../../config/theming/images' +import { useTranslation } from 'react-i18next' const useStyles = makeStyles(theme => ({ Container: { @@ -22,6 +23,7 @@ const useStyles = makeStyles(theme => ({ })) export default function FourOhFourPage() { + const { t } = useTranslation('components') const classes = useStyles() const [redirect, setRedirect] = useState(false) @@ -36,11 +38,11 @@ export default function FourOhFourPage() {
404gif - Aradığınız sayfayı bulamadık, kaldırılmış olabilir. - + {t('404.header_text')} + - 10 saniye içerisinde ana sayfaya yönlendirileceksiniz. - + {t('404.subtitle_text')} +
) diff --git a/src/components/app/pre-screens.js b/src/components/app/pre-screens.js index 55d7006..aafc0bb 100644 --- a/src/components/app/pre-screens.js +++ b/src/components/app/pre-screens.js @@ -1,6 +1,3 @@ -import React from 'react' -import { useGlobal } from 'reactn' - import Box from '@material-ui/core/Box' import Typography from '@material-ui/core/Typography' import CircularProgress from '@material-ui/core/CircularProgress' @@ -8,6 +5,9 @@ import CircularProgress from '@material-ui/core/CircularProgress' import { fullLogo, fullLogoDark } from '../../config/theming/images' import { makeStyles } from '@material-ui/core' +import { useTranslation } from 'react-i18next' +import { useContext } from 'react' +import SettingsContext from '../../contexts/settings.context' const useStyles = makeStyles(theme => ({ LogoContainer: { @@ -21,20 +21,21 @@ const useStyles = makeStyles(theme => ({ })) export default function InitialLoading(props) { + const { t } = useTranslation('components') + const [settings] = useContext(SettingsContext) const { error } = props - const [usertheme] = useGlobal('theme') const theme = useStyles() return ( - Site loading logo + Site loading logo {error ? <> - Sunucuyla bağlantı kuramadık :( + {t('pre_screens.connection_error.header_text')} - Lütfen daha sonra tekrar deneyin... + {t('pre_screens.connection_error.subtitle_text')} diff --git a/src/components/ara/components.js b/src/components/ara/components.js index f9361e1..6a09009 100644 --- a/src/components/ara/components.js +++ b/src/components/ara/components.js @@ -1,4 +1,4 @@ -import React, { useState } from 'react' +import { useState } from 'react' import { Grid, Typography, Box, makeStyles } from '@material-ui/core' import { red, blue } from '@material-ui/core/colors' @@ -8,6 +8,7 @@ import { Link } from 'react-router-dom' import { animePage, mangaPage } from '../../config/front-routes' import { CoverPlaceholder } from '../../config/theming/images' import { contentCover } from '../../config/api-routes' +import { useTranslation } from 'react-i18next' const useStyles = makeStyles(theme => ({ // ./pages/ara/index.js içerisinde kullanılan classlar @@ -95,6 +96,7 @@ const useStyles = makeStyles(theme => ({ })) function AnimeContainer(props) { + const { t } = props const { slug, name, synopsis, genres, premiered, cover_art } = props.data const [imageError, setImageError] = useState(false) const classes = useStyles(props.data) @@ -105,7 +107,7 @@ function AnimeContainer(props) { - {premiered || "Bilgi bulunamadı"} + {premiered || t('components:search.warnings.not_found_premiered')}
@@ -136,7 +138,7 @@ function AnimeContainer(props) { className={classes.ContentSynopsis} bgcolor={props.scrollbg} > - {synopsis || "Konu bulunamadı."} + {synopsis || t('components:search.warnings.not_found_synopsis')}
@@ -145,7 +147,7 @@ function AnimeContainer(props) { {genres.map(g =>
  • - {g} + {t(`genres:${g}`)}
  • ) } @@ -158,6 +160,7 @@ function AnimeContainer(props) { } function MangaContainer(props) { + const { t } = useTranslation('common') const { slug, name, synopsis, genres, cover_art } = props.data const [imageError, setImageError] = useState(false) const classes = useStyles(props.data) @@ -193,7 +196,7 @@ function MangaContainer(props) { variant="subtitle1" className={classes.ContentSynopsis} > - {synopsis || "Konu bulunamadı."} + {synopsis || t('warnings.not_found_synopsis')}
    diff --git a/src/components/calendar/index.js b/src/components/calendar/index.js new file mode 100644 index 0000000..5d4bd00 --- /dev/null +++ b/src/components/calendar/index.js @@ -0,0 +1,215 @@ +import { useEffect, Fragment } from 'react' +import { Button, Divider, makeStyles, Typography } from '@material-ui/core' +import { CoverPlaceholder } from '../../config/theming/images' +import Dotdotdot from 'react-dotdotdot' +import clsx from 'clsx' + +import addDays from 'date-fns/addDays' +import format from 'date-fns/format' +import isSameDay from 'date-fns/isSameDay' +import isFuture from 'date-fns/isFuture' +import getDay from 'date-fns/getDay' +import parseISO from 'date-fns/parseISO' + +import { useTranslation } from 'react-i18next' +import { Link } from 'react-router-dom' +import { animePage } from '../../config/front-routes' + +const useStyles = makeStyles(theme => ({ + CalendarDays: { + display: "flex", + marginBottom: theme.spacing(2), + overflowX: "auto", + overflowY: "hidden", + "& .MuiButton-root": { + borderRadius: 0 + } + }, + CalendarDaysItem: { + display: "flex", + cursor: "pointer", + padding: theme.spacing(0, 2), + "&:first-child": { + padding: theme.spacing(0, 2, 0, 0) + }, + "&:last-child": { + padding: theme.spacing(0, 0, 0, 2) + }, + [theme.breakpoints.down('sm')]: { + marginBottom: theme.spacing(1) + } + }, + CalendarDaysButton: { + "& span": { + fontWeight: "400" + } + }, + CalendarDaysSameDay: { + "& span": { + fontWeight: "600" + }, + "& p": { + fontWeight: "600" + } + }, + CalendarDayPanel: { + display: "grid", + gridTemplateColumns: "repeat(4, 1fr)", + gridGap: theme.spacing(4), + [theme.breakpoints.down("lg")]: { + gridTemplateColumns: "repeat(3, 1fr)" + }, + [theme.breakpoints.down("sm")]: { + gridGap: theme.spacing(2), + gridTemplateColumns: "repeat(1, 1fr)" + } + }, + CalendarItem: { + display: "flex", + flexDirection: "column" + }, + CalendarItemContainer: { + display: "grid", + gridTemplateColumns: `${theme.spacing(16)}px auto`, + backgroundColor: theme.palette.background.paper, + boxShadow: theme.shadows[6], + [theme.breakpoints.down("md")]: { + gridTemplateColumns: `${theme.spacing(12)}px auto` + }, + [theme.breakpoints.down("sm")]: { + gridTemplateColumns: `${theme.spacing(16)}px auto` + } + }, + CalendarItemCoverArt: { + height: 180, + backgroundSize: "cover", + backgroundRepeat: "no-repeat", + [theme.breakpoints.down("md")]: { + height: 140, + }, + [theme.breakpoints.down("sm")]: { + height: 180, + } + }, + CalendarItemText: { + display: "flex", + flexDirection: "column", + justifyContent: "center", + padding: theme.spacing(2) + }, + CalendarItemTime: { + [theme.breakpoints.down("md")]: { + }, + [theme.breakpoints.down("md")]: { + } + } +})) + +function CalendarDays(props) { + const classes = useStyles() + const { t } = useTranslation(['days', 'common']) + const { firstDayOfWeek, todayDate, selectedDay, setSelectedDay, dayList } = props + + useEffect(() => { + const sameDayCheck = isSameDay(new Date(), todayDate) + + if (sameDayCheck) setSelectedDay(getDay(todayDate)) + }, []) + + return ( + <> +
    + { + dayList.map((item, index) => { + const date = addDays(firstDayOfWeek, index) + return ( + +
    +
    setSelectedDay(item)}> +
    + + {format(date, t('common:calendar_date_format'))} + + + {t(`${item}`)} + +
    +
    +
    + {index !== dayList.length - 1 + ? + : ""} +
    + ) + }) + } +
    + + ) +} + +function CalendarDayPanel(props) { + const classes = useStyles() + const { items } = props + + return ( + <> +
    + {items.map(item => )} +
    + + ) +} + +function CalendarItem(props) { + const { t } = useTranslation('common') + const { name, episodes, cover_art, release_date, time, slug, classes } = props + + return ( + <> +
    +
    + {time ? + + {format(parseISO(time), "HH:mm")} + + : "" + } + +
    +
    + +
    + +
    + + + + {name || ""} + + + + + + {episodes.length ? t('episode.episode_title', { episode_number: Number(episodes[0].episode_number) + 1 }) : t('episode.episode_title', { episode_number: 1 })} + + + {isFuture(new Date(release_date)) ? + + {t('ns.release_date')}: {format(new Date(release_date), t('date_format'))} + + : ""} +
    +
    +
    + + ) +} + +export { CalendarDays, CalendarDayPanel } \ No newline at end of file diff --git a/src/components/ceviriler/anime/download-links.js b/src/components/ceviriler/anime/download-links.js index 25d7179..2b45694 100644 --- a/src/components/ceviriler/anime/download-links.js +++ b/src/components/ceviriler/anime/download-links.js @@ -1,12 +1,13 @@ -import React, { useState } from 'react' -import axios from '../../../config/axios/axios' +import { useState } from 'react' import { Button, Popper, Box, Fade, makeStyles, ClickAwayListener } from '@material-ui/core' import { getEpisodeDownloadLinks } from '../../../config/api-routes' +import postDataToAPI from '../../../helpers/postDataToAPI' import { Typography } from '@material-ui/core'; import WarningBox from '../../warningerrorbox/warning'; import { Skeleton } from '@material-ui/lab'; +import { useTranslation } from 'react-i18next'; const useStyles = makeStyles(theme => ({ Container: { @@ -16,7 +17,7 @@ const useStyles = makeStyles(theme => ({ Button: { marginRight: theme.spacing(1), marginBottom: theme.spacing(1), - boxShadow: theme.shadows[6] + boxShadow: theme.shadows[2] }, LinkContainer: { backgroundColor: theme.palette.background.level1, @@ -35,7 +36,7 @@ export default function DownloadLink(props) { const [loading, setLoading] = useState(true) const [data, setData] = useState([]) - const [anchorEl, setAnchorEl] = React.useState(null); + const [anchorEl, setAnchorEl] = useState(null) const open = Boolean(anchorEl); const id = open ? 'simple-popper' : undefined; @@ -58,7 +59,7 @@ export default function DownloadLink(props) { episode_id: props.episodeid } - const res = await axios.post(getEpisodeDownloadLinks(props.animeslug), postData) + const res = await postDataToAPI({ route: getEpisodeDownloadLinks(props.animeslug), data: postData }) setData(res.data) setLoading(false) @@ -67,6 +68,8 @@ export default function DownloadLink(props) { } function DownloadLinkContainer() { + const { t } = useTranslation('components') + if (!loading && data.length !== 0) return ( data.map((d, i) => ( @@ -86,7 +89,7 @@ export default function DownloadLink(props) { ) else if (!loading && data.length === 0) - return Link bulunamadı. + return {t('translations.warnings.anime.not_found_download_links')} else return (
    diff --git a/src/components/ceviriler/components.js b/src/components/ceviriler/components.js index 23ae153..0e3766c 100644 --- a/src/components/ceviriler/components.js +++ b/src/components/ceviriler/components.js @@ -1,230 +1,48 @@ -import React, { useState, useEffect } from "react" -import { Link } from "react-router-dom" -import { Grid, Typography, Box, Button, makeStyles, Divider, fade, Modal } from "@material-ui/core" -import clsx from "clsx" -import DisqusBox from "../../components/disqus/disqus" - -import { bluray, CoverPlaceholder } from "../../config/theming/images" -import { getAnimeWatchIndex, mangaEpisodePage } from "../../config/front-routes" -import { contentHeader, contentLogo, contentCover } from "../../config/api-routes" - -import { format } from "date-fns" -import WarningBox from "../warningerrorbox/warning" -import DownloadLink from "./anime/download-links" -import MotdContainer from "../motd" - -const useStyles = makeStyles((theme) => ({ - Container: { - position: "relative", - }, - BackgroundContainer: { - overflow: "hidden", - position: "relative", - margin: theme.overrides.defaultMarginOverride, - marginBottom: 0, - - [theme.breakpoints.down("sm")]: { - margin: theme.overrides.defaultMarginMobileOverride, - marginBottom: 0 - }, - }, - BottomContainer: { - position: "relative", - margin: theme.overrides.defaultMarginOverride, - marginTop: 0, - marginBottom: 0, - padding: `${theme.spacing(2)}px ${theme.spacing(5)}px`, - backgroundColor: theme.palette.background.default, - textAlign: "center", - - [theme.breakpoints.down("sm")]: { - margin: theme.overrides.defaultMarginMobileOverride, - marginTop: 0, - marginBottom: 0 - } - }, - BackgroundImage: { - position: "absolute", - top: 0, - left: 0, - right: 0, - height: "100%", - overflow: "hidden", - "& img": { - position: "absolute", - objectFit: "cover", - width: "70%", - height: "100%", - top: "50%", - left: "65%", - transform: "translate(-50%, -50%)", - }, - [theme.breakpoints.down("xs")]: { - paddingBottom: "80%", - "& img": { - marginTop: 0, - }, - }, - }, - BackgroundImageOverlay: { - //eslint-disable-next-line - background: `linear-gradient(90deg, ${theme.palette.background.default} 0%, ${theme.palette.background.default} 35%, ${fade(theme.palette.background.default, 0)} 50%)`, - position: "absolute", - top: 0, - bottom: 0, - left: 0, - right: 0, - [theme.breakpoints.down("xs")]: { - background: `linear-gradient(90deg, ${theme.palette.background.default} 0%, ${theme.palette.background.default} 35%, ${fade(theme.palette.background.default, 0)} 100%)`, - }, - }, - FallbackBackgroundImage: { - filter: "blur(5px)", - opacity: 0.8, - }, - CoverArtContainer: { - maxWidth: 225 - theme.spacing(2), - width: 225 - theme.spacing(2), - display: "none", - [theme.breakpoints.down("sm")]: { - maxWidth: "70%", - width: "70%", - }, - - "& img": { - width: "inherit", - }, - }, - FallbackCoverArt: { - display: "block", - zIndex: 2, - boxShadow: theme.shadows[6], - height: "auto!important", - [theme.breakpoints.down('sm')]: { - display: "none" - } - }, - LogoImage: { - "& img": { - width: 400, - [theme.breakpoints.down('sm')]: { - width: "100%" - } - } - }, - AnimeContainer: { - position: "relative", - zIndex: 2, - padding: theme.spacing(7), - [theme.breakpoints.down('sm')]: { - padding: theme.spacing(3), - } - }, - PremieredContainer: { - color: theme.palette.type === "dark" ? theme.palette.grey["400"] : theme.palette.text.primary, - }, - TextContainer: { - width: "45%", - [theme.breakpoints.down('sm')]: { - width: "100%" - } - }, - SynopsisContainer: { - whiteSpace: "pre-wrap", - }, - ContentButton: { - marginRight: theme.spacing(2), - marginTop: theme.spacing(2), - boxShadow: theme.shadows[6] - }, - BottomStuff: { - marginTop: theme.spacing(4), - }, - DownloadLinkDivider: { - marginBottom: theme.spacing(1), - }, - ModalContainer: { - top: "50%", - left: "50%", - transform: "translate(-50%, -50%)", - position: 'absolute', - width: 600, - backgroundColor: theme.palette.background.paper, - border: '2px solid #000', - boxShadow: theme.shadows[5], - padding: theme.spacing(2, 4, 3), - [theme.breakpoints.down('sm')]: { - width: "100%" - } - } -})) +import { useState, useEffect } from "react"; +import { Link } from "react-router-dom"; -function episodeParser(episodenumber, specialtype) { - if (specialtype === "toplu") - return `TOPLU LİNK ${episodenumber === "0" ? "" : episodenumber}` +import { useTranslation } from "react-i18next"; +import Find from "lodash-es/find"; +import Slice from "lodash-es/slice"; +import clsx from "clsx"; +import { + Grid, + Typography, + Box, + Button, + Divider, + Modal, +} from "@material-ui/core"; +import { blue, grey, lime, purple, red } from "@material-ui/core/colors"; +import { WarningSharp } from "@material-ui/icons"; +import { ResponsiveBar } from "@nivo/bar"; - if (specialtype && specialtype !== "toplu") { - return `${specialtype.toUpperCase()} ${episodenumber}` - } else return `${episodenumber}. Bölüm` -} +import { bluray, CoverPlaceholder } from "../../config/theming/images"; +import { + getAnimeWatchIndex, + mangaEpisodePage, +} from "../../config/front-routes"; +import { + contentHeader, + contentLogo, + contentCover, + jikanAPI, + youtubeEmbedLink, +} from "../../config/api-routes"; +import useStyles from "./styles"; -function AdultModal(props) { - const classes = useStyles() - const { open, handleClose } = props - - useEffect(() => { - if (open) - document.getElementById("scroll-node").style.filter = "blur(50px)" - else document.getElementById("scroll-node").style.removeProperty('filter') - return function cleanup() { - document.getElementById("scroll-node").style.removeProperty('filter') - } - }) - - return ( - -
    - - +18 ögelere sahip bir içeriğe ulaşmak istediniz, devam etmek istiyor musunuz? - - - - - -
    -
    - ) -} - -function MetadataContainer(props) { - const { title, titleM, list } = props - - return ( - - - {list.length > 1 ? `${titleM}: ` : `${title}: `} - - {list.length !== 0 ? ( - - {list.join(", ")} - - ) : ( - ${title} bulunamadı. - )} - - ) -} +import DisqusBox from "../../components/disqus/disqus"; +import WarningBox from "../warningerrorbox/warning"; +import DownloadLink from "./anime/download-links"; +import MotdContainer from "../motd"; +import EpisodeTitleParser from "../../config/episode-title-parser"; +import Format from "../date-fns/format"; +import axios from "axios"; +import Loading from "../progress"; +import Dotdotdot from "react-dotdotdot"; function AnimePage(props) { + const { t } = useTranslation(["components", "common"]); const { id, name, @@ -233,6 +51,7 @@ function AnimePage(props) { premiered, version, translators, + pv, encoders, studios, release_date, @@ -242,42 +61,133 @@ function AnimePage(props) { episodes, series_status, trans_status, - episode_count } = props - const classes = useStyles(props) - const [headerError, setHeaderError] = useState(false) - const [coverArtError, setCoverArtError] = useState(false) - const [logoError, setLogoError] = useState(false) - const [adultModal, setAdultModal] = useState(props.adult_modal) + episode_count, + } = props; + const classes = useStyles(props); + const [headerError, setHeaderError] = useState(false); + const [coverArtError, setCoverArtError] = useState(false); + const [logoError, setLogoError] = useState(false); + const [adultModal, setAdultModal] = useState(props.adult_modal); + + // Jikan States + const [ + jikanScoreStatusDataLoading, + setJikanScoreStatusDataLoading, + ] = useState(true); + const [ + jikanCharacterStaffDataLoading, + setJikanCharacterStaffDataLoading, + ] = useState(true); + const [jikanScoreData, setJikanScoreData] = useState([]); + const [jikanStatusData, setJikanStatusData] = useState({}); + const [jikanCharactersData, setJikanCharactersData] = useState([]); + const [jikanStaffData, setJikanStaffData] = useState([]); + + useEffect(() => { + async function getJikanStatusData() { + const tempData = []; + try { + const res = await axios.get( + jikanAPI({ + contentType: "anime", + contentId: mal_link.split("/")[4], + extraPath: "stats", + }) + ); + + if (res.status === 200) { + for (const score in res.data.scores) { + tempData.push({ + score: score, + votesColor: getVoteColor(score), + ...res.data.scores[score], + }); + } + + setJikanScoreData(tempData); + setJikanStatusData({ + completed: res.data.completed || 0, + watching: res.data.watching || 0, + dropped: res.data.dropped || 0, + on_hold: res.data.on_hold || 0, + plan_to_watch: res.data.plan_to_watch || 0, + }); + setJikanScoreStatusDataLoading(false); + } else { + setJikanScoreStatusDataLoading(false); + } + } catch (err) { + setJikanScoreStatusDataLoading(false); + } + } + + async function getJikanCharactersData() { + try { + const res = await axios.get( + jikanAPI({ + contentType: "anime", + contentId: mal_link.split("/")[4], + extraPath: "characters_staff", + }) + ); + + if (res.status === 200) { + setJikanCharactersData(res.data.characters); + setJikanStaffData(res.data.staff); + setJikanCharacterStaffDataLoading(false); + } else { + setJikanCharacterStaffDataLoading(false); + } + } catch (err) { + setJikanCharacterStaffDataLoading(false); + } + } + + getJikanStatusData(); + getJikanCharactersData(); + }, []); let batchLinks = episodes.map((data) => data.can_user_download && data.special_type === "toplu" ? ( ) : null - ) + ); let downloadLinks = episodes.map((data) => data.can_user_download && data.special_type !== "toplu" ? ( ) : null - ) + ); function handleClose() { - return setAdultModal(state => (!state)) + return setAdultModal((state) => !state); } // Delete null objects from downloadLinks - batchLinks = batchLinks.filter((b) => b) - downloadLinks = downloadLinks.filter((d) => d) + batchLinks = batchLinks.filter((b) => b); + downloadLinks = downloadLinks.filter((d) => d); return ( <> @@ -285,100 +195,121 @@ function AnimePage(props) {
    - +
    - + {premiered ? premiered : null} {version === "bd" ? ( bd-logo ) : null} {logoError ? ( - + {name} ) : ( - - {name { - setLogoError(true) - img.target.style.display = - "none" - }} - > - - )} + + { + setLogoError(true); + img.target.style.display = + "none"; + }} + > + + )} +
    + { + if (coverArtError) { + img.target.src = CoverPlaceholder; + return null; + } + img.target.src = cover_art; + setCoverArtError(true); + }} + alt='' + /> +
    - {synopsis ? synopsis : "Konu bulunamadı."} + {synopsis + ? synopsis + : t( + "common:warnings.not_found_synopsis" + )}
    - + - + {episodes.length !== 0 ? ( ) : null} {mal_link ? ( ) : null} @@ -388,96 +319,153 @@ function AnimePage(props) {
    {headerError ? ( {name { if (coverArtError) { - img.target.src = CoverPlaceholder - return null + img.target.src = CoverPlaceholder; + return null; } - img.target.src = cover_art - setCoverArtError(true) + img.target.src = cover_art; + setCoverArtError(true); }} /> ) : null} {name { - img.target.src = coverArtError ? cover_art : contentCover("anime", slug) - setHeaderError(true) + img.target.src = coverArtError + ? cover_art + : contentCover("anime", slug); + setHeaderError(true); }} >
    - + -
    + {/* Download Links */} + - + - - İndirme Linkleri + + {t("translations.anime.download_links")}
      {batchLinks.length !== 0 ? ( <> {batchLinks} {downloadLinks.length === - 0 ? null : ( - - )} + 0 ? null : ( + + )} ) : null} {downloadLinks.length !== 0 ? ( downloadLinks ) : batchLinks.length !== 0 ? null : ( - İndirme linki bulunamadı. + {t( + "translations.warnings.anime.not_found_download_links" + )} )}
    +
    + {/* Jikan Stats */} + + + {t("translations.anime.stats")} + + + + + + + + + + + + + + + + + + + + + {/* Disqus Box */} + {process.env.REACT_APP_DISQUS_SHORTNAME ? ( <> @@ -490,24 +478,119 @@ function AnimePage(props) { ) : ( - "" - )} -
    + "" + )} +
    - ) + ); } function MangaPage(props) { - const { id, name, slug, cover_art, translators, editors, authors, release_date, genres, mal_link, synopsis, reader_link, download_link, series_status, trans_status, episode_count } = props - const classes = useStyles(props) - const [headerError, setHeaderError] = useState(false) - const [coverArtError, setCoverArtError] = useState(false) - const [logoError, setLogoError] = useState(false) - const [adultModal, setAdultModal] = useState(props.adult_modal) + const { t } = useTranslation(["components", "common"]); + const { + id, + name, + slug, + cover_art, + translators, + editors, + authors, + release_date, + genres, + mal_link, + synopsis, + reader_link, + download_link, + series_status, + trans_status, + episode_count, + } = props; + const classes = useStyles(props); + const [headerError, setHeaderError] = useState(false); + const [coverArtError, setCoverArtError] = useState(false); + const [logoError, setLogoError] = useState(false); + const [adultModal, setAdultModal] = useState(props.adult_modal); + + // Jikan States + const [ + jikanScoreStatusDataLoading, + setJikanScoreStatusDataLoading, + ] = useState(true); + const [ + jikanCharacterStaffDataLoading, + setJikanCharacterStaffDataLoading, + ] = useState(true); + const [jikanScoreData, setJikanScoreData] = useState([]); + const [jikanStatusData, setJikanStatusData] = useState({}); + const [jikanCharactersData, setJikanCharactersData] = useState([]); + const [jikanStaffData, setJikanStaffData] = useState([]); + + useEffect(() => { + async function getJikanStatusData() { + const tempData = []; + try { + const res = await axios.get( + jikanAPI({ + contentType: "manga", + contentId: mal_link.split("/")[4], + extraPath: "stats", + }) + ); + + if (res.status === 200) { + for (const score in res.data.scores) { + tempData.push({ + score: score, + votesColor: getVoteColor(score), + ...res.data.scores[score], + }); + } + + setJikanScoreData(tempData); + setJikanStatusData({ + completed: res.data.completed || 0, + reading: res.data.reading || 0, + dropped: res.data.dropped || 0, + on_hold: res.data.on_hold || 0, + plan_to_read: res.data.plan_to_read || 0, + }); + setJikanScoreStatusDataLoading(false); + } else { + setJikanScoreStatusDataLoading(false); + } + } catch (err) { + setJikanScoreStatusDataLoading(false); + } + } + + async function getJikanCharactersData() { + try { + const res = await axios.get( + jikanAPI({ + contentType: "manga", + contentId: mal_link.split("/")[4], + extraPath: "characters", + }) + ); + + if (res.status === 200) { + setJikanCharactersData(res.data.characters); + setJikanCharacterStaffDataLoading(false); + } else { + setJikanCharacterStaffDataLoading(false); + } + } catch (err) { + setJikanCharacterStaffDataLoading(false); + } + } + + getJikanStatusData(); + getJikanCharactersData(); + }, []); function handleClose() { - return setAdultModal(state => (!state)) + return setAdultModal((state) => !state); } return ( @@ -516,123 +599,142 @@ function MangaPage(props) {
    - + - - + {logoError ? ( - + {name} ) : ( - - {name { - setLogoError(true) - img.target.style.display = - "none" - }} - > - - )} + + { + setLogoError(true); + img.target.style.display = + "none"; + }} + > + + )} +
    + { + if (coverArtError) { + img.target.src = CoverPlaceholder; + return null; + } + img.target.src = cover_art; + setCoverArtError(true); + }} + alt='' + /> +
    - {synopsis ? synopsis : "Konu bulunamadı."} + {synopsis + ? synopsis + : t( + "common:warnings.not_found_synopsis" + )}
    - + - + {download_link ? ( ) : null} {reader_link ? ( - ) - : - episode_count ? - - - - : - "" - } + ) : episode_count ? ( + + + + ) : ( + "" + )} {mal_link ? ( ) : null} @@ -642,64 +744,92 @@ function MangaPage(props) { {headerError ? ( {name { if (coverArtError) { - img.target.src = CoverPlaceholder - return null + img.target.src = CoverPlaceholder; + return null; } - img.target.src = cover_art - setCoverArtError(true) + img.target.src = cover_art; + setCoverArtError(true); }} /> ) : null} {name { - img.target.src = coverArtError ? cover_art : contentCover("manga", slug) - setHeaderError(true) + img.target.src = coverArtError + ? cover_art + : contentCover("manga", slug); + setHeaderError(true); }} >
    - + + + + + {/* Jikan Stats */} + + + {t("translations.anime.stats")} + + + + + + + + + + +
    {process.env.REACT_APP_DISQUS_SHORTNAME ? ( <> @@ -713,12 +843,586 @@ function MangaPage(props) { ) : ( - "" - )} + "" + )}
    - ) + ); +} + +function MetadataContainer(props) { + const { t } = useTranslation(["components", "genres"]); + const { title, list, type } = props; + + function getRenderBox() { + switch (type) { + case "genres": { + return list.map( + (l, i) => + `${t(`genres:${l}`)}${ + list.length - 1 !== i ? ", " : "" + }` + ); + } + default: { + return list.join(", "); + } + } + } + + return ( + + + {title}: + + {list.length !== 0 ? ( + + {getRenderBox()} + + ) : ( + + {t("translations.warnings.content_metadata.not_found", { + title: title, + })} + + )} + + ); +} + +function JikanStatsScoresChart(props) { + const { t } = useTranslation(["common", "components"]); + const classes = useStyles(); + + const { data, loading } = props; + + return ( + <> + + {t("ns.score_distribution")} + + {data.length ? ( +
    + {data.length ? ( +
    + getVoteColor({ value: d.index })} + indexBy='score' + colorBy='index' + labelFormat={(d) => ( + + {d} + + )} + labelTextColor={{ theme: "labels.text.fill" }} + enableGridY={false} + margin={{ + top: 30, + right: 0, + bottom: 30, + left: 0, + }} + padding={0.6} + isInteractive={false} + axisTop={null} + axisRight={null} + axisLeft={null} + axisBottom={{ + tickSize: 0, + tickPadding: 10, + format: (d) => ( + + {d} + + ), + }} + /> +
    + ) : ( + "" + )} + + + jikan API + + +
    + ) : loading ? ( +
    + +
    + ) : ( +
    + + + {t( + "components:translations.jikan_errors.couldnt_reach_data" + )} + +
    + )} + + ); +} + +function JikanStatsStatusChart(props) { + const { t } = useTranslation(["common", "components"]); + const classes = useStyles(); + const { data, loading } = props; + + return ( + <> + + {t("ns.status_distribution")} + + {Object.keys(data).length ? ( +
    +
    + {Object.keys(data).map((key) => ( +
    +
    + + {t(`ns.${key}`)} + +
    +
    {data[key]}
    +
    + ))} +
    + + + jikan API + + +
    + ) : loading ? ( +
    + +
    + ) : ( +
    + + + {t( + "components:translations.jikan_errors.couldnt_reach_data" + )} + +
    + )} + + ); +} + +function PVBox(props) { + const { t } = useTranslation(["common", "components"]); + const classes = useStyles(); + let { link } = props; + + if (link) { + var regExp = /^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\??v?=?))([^#\&\?]*).*/; + let match = link.match(regExp); + if (match && match[7].length === 11) + link = youtubeEmbedLink({ videoId: match[7] }); + else link = ""; + } + + return ( + <> + + {t("ns.youtube_preview")} + + {link ? ( + <> +
    +