diff --git a/front/apps/mobile/app/(app)/admin/index.tsx b/front/apps/mobile/app/(app)/admin/index.tsx new file mode 100644 index 00000000..b057197b --- /dev/null +++ b/front/apps/mobile/app/(app)/admin/index.tsx @@ -0,0 +1,23 @@ +/* + * Kyoo - A portable and vast media library solution. + * Copyright (c) Kyoo. + * + * See AUTHORS.md and LICENSE file in the project root for full license information. + * + * Kyoo is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * Kyoo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Kyoo. If not, see . + */ + +import { AdminPage } from "@kyoo/ui"; + +export default AdminPage; diff --git a/front/apps/mobile/app/utils.tsx b/front/apps/mobile/app/utils.tsx index a8043324..d63e4332 100644 --- a/front/apps/mobile/app/utils.tsx +++ b/front/apps/mobile/app/utils.tsx @@ -24,7 +24,8 @@ import { StatusBar, StatusBarProps } from "react-native"; import * as ScreenOrientation from "expo-screen-orientation"; import * as NavigationBar from "expo-navigation-bar"; import arrayShuffle from "array-shuffle"; -import { QueryPage } from "@kyoo/models"; +import { QueryPage, useHasPermission } from "@kyoo/models"; +import { Unauthorized } from "@kyoo/ui"; const FullscreenProvider = () => { useEffect(() => { @@ -49,6 +50,11 @@ export const withRoute = ( const { statusBar, fullscreen, ...routeOptions } = options ?? {}; const WithUseRoute = (props: any) => { const routeParams = useLocalSearchParams(); + const hasPermissions = useHasPermission((Component as QueryPage).requiredPermissions ?? []); + + if (!hasPermissions) + return ; + return ( <> {routeOptions && } diff --git a/front/apps/web/src/pages/admin/index.tsx b/front/apps/web/src/pages/admin/index.tsx new file mode 100644 index 00000000..829344ec --- /dev/null +++ b/front/apps/web/src/pages/admin/index.tsx @@ -0,0 +1,24 @@ +/* + * Kyoo - A portable and vast media library solution. + * Copyright (c) Kyoo. + * + * See AUTHORS.md and LICENSE file in the project root for full license information. + * + * Kyoo is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * Kyoo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Kyoo. If not, see . + */ + +import { AdminPage } from "@kyoo/ui"; +import { withRoute } from "~/router"; + +export default withRoute(AdminPage); diff --git a/front/apps/web/src/router.tsx b/front/apps/web/src/router.tsx index 2ffe6dad..5579fdfe 100644 --- a/front/apps/web/src/router.tsx +++ b/front/apps/web/src/router.tsx @@ -18,6 +18,8 @@ * along with Kyoo. If not, see . */ +import { QueryPage, useHasPermission } from "@kyoo/models"; +import { Unauthorized } from "@kyoo/ui"; import { useRouter } from "next/router"; import { ComponentType } from "react"; @@ -27,7 +29,10 @@ export const withRoute = ( ) => { const WithUseRoute = (props: Props) => { const router = useRouter(); + const hasPermissions = useHasPermission((Component as QueryPage).requiredPermissions ?? []); + if (!hasPermissions) + return ; // @ts-ignore return ; }; diff --git a/front/packages/models/src/accounts.tsx b/front/packages/models/src/accounts.tsx index 6f3faa26..cfc982a3 100644 --- a/front/packages/models/src/accounts.tsx +++ b/front/packages/models/src/accounts.tsx @@ -158,9 +158,11 @@ export const useAccounts = () => { return useContext(AccountContext); }; -export const useHasPermission = (...perms: string[]) => { +export const useHasPermission = (perms?: string[]) => { const account = useAccount(); + if (!perms) return true; + // TODO: Read permission of guest account here. if (!account) return false; return perms.every((perm) => account.permissions.includes(perm)); diff --git a/front/packages/models/src/query.tsx b/front/packages/models/src/query.tsx index a57eef59..e23933aa 100644 --- a/front/packages/models/src/query.tsx +++ b/front/packages/models/src/query.tsx @@ -183,6 +183,7 @@ export type QueryPage = ComponentType< getLayout?: | QueryPage<{ page: ReactElement }> | { Layout: QueryPage<{ page: ReactElement }>; props: object }; + requiredPermissions?: string[] randomItems?: Items[]; }; diff --git a/front/packages/ui/src/admin/index.tsx b/front/packages/ui/src/admin/index.tsx new file mode 100644 index 00000000..32df5d89 --- /dev/null +++ b/front/packages/ui/src/admin/index.tsx @@ -0,0 +1,35 @@ +/* + * Kyoo - A portable and vast media library solution. + * Copyright (c) Kyoo. + * + * See AUTHORS.md and LICENSE file in the project root for full license information. + * + * Kyoo is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * Kyoo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Kyoo. If not, see . + */ + +import { QueryPage } from "@kyoo/models"; +import { P, ts } from "@kyoo/primitives"; +import { ScrollView } from "react-native"; +import { DefaultLayout } from "../layout"; + +export const AdminPage: QueryPage = () => { + return ( + +

hbgen

+
+ ); +}; + +AdminPage.getLayout = DefaultLayout; +AdminPage.requiredPermissions = ["admin.read"]; diff --git a/front/packages/ui/src/details/collection.tsx b/front/packages/ui/src/details/collection.tsx index e31b7d9d..994cf38a 100644 --- a/front/packages/ui/src/details/collection.tsx +++ b/front/packages/ui/src/details/collection.tsx @@ -28,7 +28,7 @@ import { import { Container, H2, ImageBackground, Link, P, focusReset, ts } from "@kyoo/primitives"; import { useTranslation } from "react-i18next"; import { Theme, useYoshiki } from "yoshiki/native"; -import { ErrorView } from "../fetch"; +import { ErrorView } from "../errors"; export const PartOf = ({ name, diff --git a/front/packages/ui/src/errors/error.tsx b/front/packages/ui/src/errors/error.tsx new file mode 100644 index 00000000..ccf3cefa --- /dev/null +++ b/front/packages/ui/src/errors/error.tsx @@ -0,0 +1,46 @@ +/* + * Kyoo - A portable and vast media library solution. + * Copyright (c) Kyoo. + * + * See AUTHORS.md and LICENSE file in the project root for full license information. + * + * Kyoo is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * Kyoo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Kyoo. If not, see . + */ + +import { KyooErrors } from "@kyoo/models"; +import { P } from "@kyoo/primitives"; +import { View } from "react-native"; +import { useYoshiki } from "yoshiki/native"; + +export const ErrorView = ({ error }: { error: KyooErrors }) => { + const { css } = useYoshiki(); + + return ( + theme.colors.red, + flexGrow: 1, + flexShrink: 1, + justifyContent: "center", + alignItems: "center", + })} + > + {error.errors.map((x, i) => ( +

theme.colors.white })}> + {x} +

+ ))} +
+ ); +}; diff --git a/front/packages/ui/src/errors/index.tsx b/front/packages/ui/src/errors/index.tsx new file mode 100644 index 00000000..25c26ba6 --- /dev/null +++ b/front/packages/ui/src/errors/index.tsx @@ -0,0 +1,22 @@ +/* + * Kyoo - A portable and vast media library solution. + * Copyright (c) Kyoo. + * + * See AUTHORS.md and LICENSE file in the project root for full license information. + * + * Kyoo is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * Kyoo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Kyoo. If not, see . + */ + +export * from "./error"; +export * from "./unauthorized"; diff --git a/front/packages/ui/src/errors/unauthorized.tsx b/front/packages/ui/src/errors/unauthorized.tsx new file mode 100644 index 00000000..bf149aaf --- /dev/null +++ b/front/packages/ui/src/errors/unauthorized.tsx @@ -0,0 +1,42 @@ +/* + * Kyoo - A portable and vast media library solution. + * Copyright (c) Kyoo. + * + * See AUTHORS.md and LICENSE file in the project root for full license information. + * + * Kyoo is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * Kyoo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Kyoo. If not, see . + */ + +import { P } from "@kyoo/primitives"; +import { useTranslation } from "react-i18next"; +import { View } from "react-native"; +import { useYoshiki } from "yoshiki/native"; + +export const Unauthorized = ({ missing }: { missing: string[] }) => { + const { t } = useTranslation(); + const { css } = useYoshiki(); + + return ( + +

{t("errors.unauthorized", { permission: missing.join(", ") })}

+
+ ); +}; diff --git a/front/packages/ui/src/fetch-infinite.tsx b/front/packages/ui/src/fetch-infinite.tsx index 7e007e64..54165661 100644 --- a/front/packages/ui/src/fetch-infinite.tsx +++ b/front/packages/ui/src/fetch-infinite.tsx @@ -22,7 +22,8 @@ import { Page, QueryIdentifier, useInfiniteFetch } from "@kyoo/models"; import { useBreakpointMap, HR } from "@kyoo/primitives"; import { ContentStyle, FlashList } from "@shopify/flash-list"; import { ComponentProps, ComponentType, isValidElement, ReactElement, useRef } from "react"; -import { EmptyView, ErrorView, Layout, OfflineView, WithLoading, addHeader } from "./fetch"; +import { EmptyView, Layout, OfflineView, WithLoading, addHeader } from "./fetch"; +import { ErrorView } from "./errors"; import { FlatList, View, ViewStyle } from "react-native"; const emulateGap = ( diff --git a/front/packages/ui/src/fetch-infinite.web.tsx b/front/packages/ui/src/fetch-infinite.web.tsx index a0cd929b..ed3a5baa 100644 --- a/front/packages/ui/src/fetch-infinite.web.tsx +++ b/front/packages/ui/src/fetch-infinite.web.tsx @@ -31,7 +31,8 @@ import { useRef, } from "react"; import { Stylable, nativeStyleToCss, useYoshiki, ysMap } from "yoshiki"; -import { EmptyView, ErrorView, Layout, WithLoading, addHeader } from "./fetch"; +import { EmptyView, Layout, WithLoading, addHeader } from "./fetch"; +import { ErrorView } from "./errors"; import type { ContentStyle } from "@shopify/flash-list"; const InfiniteScroll = ({ diff --git a/front/packages/ui/src/fetch.tsx b/front/packages/ui/src/fetch.tsx index f2b56af6..626b1d87 100644 --- a/front/packages/ui/src/fetch.tsx +++ b/front/packages/ui/src/fetch.tsx @@ -18,12 +18,13 @@ * along with Kyoo. If not, see . */ -import { Page, QueryIdentifier, useFetch, KyooErrors } from "@kyoo/models"; +import { Page, QueryIdentifier, useFetch } from "@kyoo/models"; import { Breakpoint, P } from "@kyoo/primitives"; import { ComponentType, ReactElement, isValidElement } from "react"; import { useTranslation } from "react-i18next"; import { View } from "react-native"; import { useYoshiki } from "yoshiki/native"; +import { ErrorView } from "./errors"; export type Layout = { numColumns: Breakpoint; @@ -84,28 +85,6 @@ export const OfflineView = () => { ); }; -export const ErrorView = ({ error }: { error: KyooErrors }) => { - const { css } = useYoshiki(); - - return ( - theme.colors.red, - flexGrow: 1, - flexShrink: 1, - justifyContent: "center", - alignItems: "center", - })} - > - {error.errors.map((x, i) => ( -

theme.colors.white })}> - {x} -

- ))} -
- ); -}; - export const EmptyView = ({ message }: { message: string }) => { const { css } = useYoshiki(); diff --git a/front/packages/ui/src/index.ts b/front/packages/ui/src/index.ts index de07c18a..3fae9f53 100644 --- a/front/packages/ui/src/index.ts +++ b/front/packages/ui/src/index.ts @@ -28,3 +28,5 @@ export { SearchPage } from "./search"; export { LoginPage, RegisterPage } from "./login"; export { DownloadPage, DownloadProvider } from "./downloads"; export { SettingsPage } from "./settings"; +export { AdminPage } from "./admin"; +export * from "./errors"; diff --git a/front/packages/ui/src/navbar/index.tsx b/front/packages/ui/src/navbar/index.tsx index d5ffc96c..cfac2981 100644 --- a/front/packages/ui/src/navbar/index.tsx +++ b/front/packages/ui/src/navbar/index.tsx @@ -44,6 +44,7 @@ import Admin from "@material-symbols/svg-400/rounded/admin_panel_settings.svg"; import Settings from "@material-symbols/svg-400/rounded/settings.svg"; import { KyooLongLogo } from "./icon"; import { forwardRef, useEffect, useRef, useState } from "react"; +import { AdminPage } from "../admin"; export const NavbarTitle = (props: Stylable & { onLayout?: ViewProps["onLayout"] }) => { const { t } = useTranslation(); @@ -139,7 +140,7 @@ export const NavbarRight = () => { const { css, theme } = useYoshiki(); const { t } = useTranslation(); const { push } = useRouter(); - const isAdmin = useHasPermission("admin.read"); + const isAdmin = useHasPermission(AdminPage.requiredPermissions); return ( diff --git a/front/packages/ui/src/player/components/scrubber.tsx b/front/packages/ui/src/player/components/scrubber.tsx index d89d8854..f8a2ea33 100644 --- a/front/packages/ui/src/player/components/scrubber.tsx +++ b/front/packages/ui/src/player/components/scrubber.tsx @@ -22,12 +22,12 @@ import { useFetch, QueryIdentifier, imageFn, Chapter } from "@kyoo/models"; import { Sprite, P, imageBorderRadius, ts } from "@kyoo/primitives"; import { View, Platform } from "react-native"; import { percent, useYoshiki, px, Theme, useForceRerender } from "yoshiki/native"; -import { ErrorView } from "../../fetch"; import { useMemo } from "react"; import { useAtomValue } from "jotai"; import { durationAtom } from "../state"; import { toTimerString } from "./left-buttons"; import { seekProgressAtom } from "./hover"; +import { ErrorView } from "../../errors"; type Thumb = { from: number; diff --git a/front/packages/ui/src/player/index.tsx b/front/packages/ui/src/player/index.tsx index bf4eadb9..be4fd13a 100644 --- a/front/packages/ui/src/player/index.tsx +++ b/front/packages/ui/src/player/index.tsx @@ -40,8 +40,8 @@ import { fullscreenAtom, Video } from "./state"; import { episodeDisplayNumber } from "../details/episode"; import { useVideoKeyboard } from "./keyboard"; import { MediaSessionManager } from "./media-session"; -import { ErrorView } from "../fetch"; import { WatchStatusObserver } from "./watch-status-observer"; +import { ErrorView } from "../errors"; type Item = (Movie & { type: "movie" }) | (Episode & { type: "episode" }); @@ -66,8 +66,6 @@ const mapData = ( }; }; -let firstSlug: string | null = null; - export const Player = ({ slug, type, diff --git a/front/translations/en.json b/front/translations/en.json index 11fddcd7..5192ad20 100644 --- a/front/translations/en.json +++ b/front/translations/en.json @@ -189,11 +189,12 @@ }, "errors": { "connection": "Could not connect to the kyoo's server", - "connection-tips": "Troublshotting tips:\n - Are you connected to internet?\n - Is your kyoo's server online?\n - Have your account been banned?", + "connection-tips": "Troubleshooting tips:\n - Are you connected to internet?\n - Is your kyoo's server online?\n - Have your account been banned?", "unknown": "Unknown error", "try-again": "Try again", "re-login": "Re login", - "offline": "You are not connected to internet. Try again later." + "offline": "You are not connected to internet. Try again later.", + "unauthorized": "You are missing the permissions {{permission}} to access this page." }, "mediainfo": { "file": "File", diff --git a/front/translations/fr.json b/front/translations/fr.json index b8d1a5be..4a5a6555 100644 --- a/front/translations/fr.json +++ b/front/translations/fr.json @@ -193,7 +193,8 @@ "unknown": "Erreur inconnue", "try-again": "Réessayer", "re-login": "Se reconnecter", - "offline": "Vous n'êtes pas connecté à Internet. Réessayez plus tard." + "offline": "Vous n'êtes pas connecté à Internet. Réessayez plus tard.", + "unauthorized": "Il vous manque les autorisations {{permission}} pour accéder à cette page." }, "mediainfo": { "file": "Fichier",