diff --git a/front/packages/models/src/login.ts b/front/packages/models/src/login.ts index da9919ef..23aefec8 100644 --- a/front/packages/models/src/login.ts +++ b/front/packages/models/src/login.ts @@ -56,8 +56,9 @@ export const loginFunc = async ( } }; -export const getToken = async (): Promise => { - const tokenStr = await getSecureItem("auth"); +export const getToken = async (cookies?: string): Promise => { + // @ts-ignore Web only. + const tokenStr = await getSecureItem("auth", cookies); if (!tokenStr) return null; const token = JSON.parse(tokenStr) as Token; diff --git a/front/packages/models/src/query.tsx b/front/packages/models/src/query.tsx index de1dc375..9fb770d9 100644 --- a/front/packages/models/src/query.tsx +++ b/front/packages/models/src/query.tsx @@ -32,7 +32,6 @@ import { KyooErrors } from "./kyoo-errors"; import { Page, Paged } from "./page"; import { Platform } from "react-native"; import { getToken } from "./login"; -import { getSecureItem } from "./secure-store.web"; export const kyooUrl = Platform.OS !== "web" @@ -195,7 +194,7 @@ export const fetchQuery = async (queries: QueryIdentifier[], cookies?: string) = // we can't put this check in a function because we want build time optimizations // see https://github.com/vercel/next.js/issues/5354 for details if (typeof window !== "undefined") return {}; - const authToken = getSecureItem("auth", cookies); + const authToken = await getToken(cookies); const client = createQueryClient(); await Promise.all( diff --git a/front/packages/models/src/resources/index.ts b/front/packages/models/src/resources/index.ts index abbc0ad6..39332330 100644 --- a/front/packages/models/src/resources/index.ts +++ b/front/packages/models/src/resources/index.ts @@ -29,3 +29,4 @@ export * from "./studio"; export * from "./episode"; export * from "./season"; export * from "./watch-item"; +export * from "./user"; diff --git a/front/packages/models/src/resources/user.ts b/front/packages/models/src/resources/user.ts new file mode 100644 index 00000000..ff37697a --- /dev/null +++ b/front/packages/models/src/resources/user.ts @@ -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 { z } from "zod"; +import { ResourceP } from "../traits/resource"; + +/** + * The library that will contain Shows, Collections... + */ +export const UserP = ResourceP.extend({ + /** + * The name of this user. + */ + username: z.string(), + /** + * The user email address. + */ + email: z.string(), + /** + * The list of permissions of the user. The format of this is implementation dependent. + */ + permissions: z.array(z.string()), +}); + +export type User = z.infer; diff --git a/front/packages/models/src/secure-store.web.ts b/front/packages/models/src/secure-store.web.ts index d3f8ac63..44eee273 100644 --- a/front/packages/models/src/secure-store.web.ts +++ b/front/packages/models/src/secure-store.web.ts @@ -21,14 +21,13 @@ export const setSecureItem = async (key: string, value: string): Promise => { const d = new Date(); // A year - d.setTime(d.getTime() + 356 * 24 * 60 * 60 * 1000); + d.setTime(d.getTime() + 365 * 24 * 60 * 60 * 1000); const expires = "expires=" + d.toUTCString(); document.cookie = key + "=" + value + ";" + expires + ";path=/"; return null; }; export const getSecureItem = async (key: string, cookies?: string): Promise => { - if (typeof window === "undefined") return null; const name = key + "="; const decodedCookie = decodeURIComponent(cookies ?? document.cookie); const ca = decodedCookie.split(";"); diff --git a/front/packages/primitives/src/avatar.tsx b/front/packages/primitives/src/avatar.tsx index 91d38318..d746e2b9 100644 --- a/front/packages/primitives/src/avatar.tsx +++ b/front/packages/primitives/src/avatar.tsx @@ -24,12 +24,14 @@ import { useYoshiki, px, Stylable } from "yoshiki/native"; import { Icon } from "./icons"; import AccountCircle from "@material-symbols/svg-400/rounded/account_circle-fill.svg"; import { YoshikiStyle } from "yoshiki/dist/type"; +import { P } from "@expo/html-elements"; export const Avatar = ({ src, alt, size = px(24), color, + placeholder, isLoading = false, fill = false, ...props @@ -37,6 +39,7 @@ export const Avatar = ({ src?: string | null; alt?: string; size?: YoshikiStyle; + placeholder?: string; color?: string; isLoading?: boolean; fill?: boolean; @@ -53,12 +56,21 @@ export const Avatar = ({ fill && { bg: col, }, + placeholder && + !src && + !isLoading && { + bg: theme.accent, + justifyContent: "center", + alignItems: "center", + }, ], props, )} > {src || isLoading ? ( {alt} + ) : placeholder ? ( +

{placeholder[0]}

) : ( )} diff --git a/front/packages/ui/src/layout.tsx b/front/packages/ui/src/layout.tsx index 986b5e8b..e1fd923d 100644 --- a/front/packages/ui/src/layout.tsx +++ b/front/packages/ui/src/layout.tsx @@ -19,7 +19,7 @@ */ import { ReactElement } from "react"; -import { Navbar } from "./navbar"; +import { MeQuery, Navbar } from "./navbar"; import { useYoshiki } from "yoshiki/native"; import { Main } from "@kyoo/primitives"; @@ -51,4 +51,4 @@ export const DefaultLayout = ({ page, transparent }: { page: ReactElement, trans ); }; -DefaultLayout.getFetchUrls = () => [Navbar.query()]; +DefaultLayout.getFetchUrls = () => [Navbar.query(), MeQuery]; diff --git a/front/packages/ui/src/navbar/index.tsx b/front/packages/ui/src/navbar/index.tsx index 29873478..dada4964 100644 --- a/front/packages/ui/src/navbar/index.tsx +++ b/front/packages/ui/src/navbar/index.tsx @@ -18,7 +18,7 @@ * along with Kyoo. If not, see . */ -import { Library, LibraryP, Page, Paged, QueryIdentifier } from "@kyoo/models"; +import { Library, LibraryP, Page, Paged, QueryIdentifier, User, UserP } from "@kyoo/models"; import { Input, IconButton, @@ -82,18 +82,33 @@ const SearchBar = forwardRef< ); }); +export const MeQuery: QueryIdentifier = { + path: ["auth", "me"], + parser: UserP, +}; + export const NavbarProfile = () => { const { css, theme } = useYoshiki(); const { t } = useTranslation(); + // TODO: show logged in user. return ( - - - + + {({ username }) => ( + + + + )} + ); }; export const NavbarRight = () => { @@ -125,9 +140,9 @@ export const NavbarRight = () => { onPress={ Platform.OS === "web" ? () => { - setSearch(true); - setTimeout(() => ref.current?.focus(), 0); - } + setSearch(true); + setTimeout(() => ref.current?.focus(), 0); + } : () => push("/search") } {...tooltip(t("navbar.search"))}