mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-31 04:04:21 -04:00
Add logout button for logged in users
This commit is contained in:
parent
98a0466761
commit
ea53eb9b24
@ -19,7 +19,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { getSecureItem, setSecureItem } from "./secure-store";
|
import { deleteSecureItem, getSecureItem, setSecureItem } from "./secure-store";
|
||||||
import { zdate } from "./utils";
|
import { zdate } from "./utils";
|
||||||
import { queryFn } from "./query";
|
import { queryFn } from "./query";
|
||||||
import { KyooErrors } from "./kyoo-errors";
|
import { KyooErrors } from "./kyoo-errors";
|
||||||
@ -79,3 +79,7 @@ export const getTokenWJ = async (cookies?: string): Promise<[string, Token] | [n
|
|||||||
export const getToken = async (cookies?: string): Promise<string | null> =>
|
export const getToken = async (cookies?: string): Promise<string | null> =>
|
||||||
(await getTokenWJ(cookies))[0]
|
(await getTokenWJ(cookies))[0]
|
||||||
|
|
||||||
|
|
||||||
|
export const logout = async () =>{
|
||||||
|
deleteSecureItem("auth")
|
||||||
|
}
|
||||||
|
@ -18,4 +18,8 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export { setItemAsync as setSecureItem, getItemAsync as getSecureItem } from "expo-secure-store";
|
export {
|
||||||
|
setItemAsync as setSecureItem,
|
||||||
|
deleteItemAsync as deleteSecureItem,
|
||||||
|
getItemAsync as getSecureItem,
|
||||||
|
} from "expo-secure-store";
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const setSecureItemSync = (key: string, value?: string) => {
|
export const setSecureItemSync = (key: string, value: string | null) => {
|
||||||
const d = new Date();
|
const d = new Date();
|
||||||
// A year
|
// A year
|
||||||
d.setTime(d.getTime() + 365 * 24 * 60 * 60 * 1000);
|
d.setTime(d.getTime() + 365 * 24 * 60 * 60 * 1000);
|
||||||
@ -27,9 +27,11 @@ export const setSecureItemSync = (key: string, value?: string) => {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setSecureItem = async (key: string, value: string): Promise<null> =>
|
export const setSecureItem = async (key: string, value: string | null): Promise<null> =>
|
||||||
setSecureItemSync(key, value);
|
setSecureItemSync(key, value);
|
||||||
|
|
||||||
|
export const deleteSecureItem = async (key: string) => setSecureItem(key, null);
|
||||||
|
|
||||||
export const getSecureItem = async (key: string, cookies?: string): Promise<string | null> => {
|
export const getSecureItem = async (key: string, cookies?: string): Promise<string | null> => {
|
||||||
// Don't try to use document's cookies on SSR.
|
// Don't try to use document's cookies on SSR.
|
||||||
if (!cookies && typeof window === "undefined") return null;
|
if (!cookies && typeof window === "undefined") return null;
|
||||||
|
@ -25,17 +25,11 @@ import { Icon } from "./icons";
|
|||||||
import AccountCircle from "@material-symbols/svg-400/rounded/account_circle-fill.svg";
|
import AccountCircle from "@material-symbols/svg-400/rounded/account_circle-fill.svg";
|
||||||
import { YoshikiStyle } from "yoshiki/dist/type";
|
import { YoshikiStyle } from "yoshiki/dist/type";
|
||||||
import { P } from "@expo/html-elements";
|
import { P } from "@expo/html-elements";
|
||||||
|
import { forwardRef } from "react";
|
||||||
|
|
||||||
export const Avatar = ({
|
export const Avatar = forwardRef<
|
||||||
src,
|
View,
|
||||||
alt,
|
{
|
||||||
size = px(24),
|
|
||||||
color,
|
|
||||||
placeholder,
|
|
||||||
isLoading = false,
|
|
||||||
fill = false,
|
|
||||||
...props
|
|
||||||
}: {
|
|
||||||
src?: string | null;
|
src?: string | null;
|
||||||
alt?: string;
|
alt?: string;
|
||||||
size?: YoshikiStyle<number | string>;
|
size?: YoshikiStyle<number | string>;
|
||||||
@ -43,13 +37,18 @@ export const Avatar = ({
|
|||||||
color?: string;
|
color?: string;
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
fill?: boolean;
|
fill?: boolean;
|
||||||
} & Stylable) => {
|
} & Stylable
|
||||||
|
>(function _Avatar(
|
||||||
|
{ src, alt, size = px(24), color, placeholder, isLoading = false, fill = false, ...props },
|
||||||
|
ref,
|
||||||
|
) {
|
||||||
const { css, theme } = useYoshiki();
|
const { css, theme } = useYoshiki();
|
||||||
const col = color ?? theme.overlay0;
|
const col = color ?? theme.overlay0;
|
||||||
|
|
||||||
// TODO: Support dark themes when fill === true
|
// TODO: Support dark themes when fill === true
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
|
ref={ref}
|
||||||
{...css(
|
{...css(
|
||||||
[
|
[
|
||||||
{ borderRadius: 999999, width: size, height: size, overflow: "hidden" },
|
{ borderRadius: 999999, width: size, height: size, overflow: "hidden" },
|
||||||
@ -76,4 +75,4 @@ export const Avatar = ({
|
|||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
@ -30,20 +30,21 @@ import { P } from "./text";
|
|||||||
import { ContrastArea } from "./themes";
|
import { ContrastArea } from "./themes";
|
||||||
import { ts } from "./utils";
|
import { ts } from "./utils";
|
||||||
import Check from "@material-symbols/svg-400/rounded/check-fill.svg";
|
import Check from "@material-symbols/svg-400/rounded/check-fill.svg";
|
||||||
|
import { useRouter } from "solito/router";
|
||||||
|
|
||||||
const MenuContext = createContext<((open: boolean) => void) | undefined>(undefined);
|
const MenuContext = createContext<((open: boolean) => void) | undefined>(undefined);
|
||||||
|
|
||||||
const Menu = <AsProps,>({
|
const Menu = <AsProps,>({
|
||||||
Triger,
|
Trigger,
|
||||||
onMenuOpen,
|
onMenuOpen,
|
||||||
onMenuClose,
|
onMenuClose,
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: {
|
}: {
|
||||||
Triger: ComponentType<AsProps>;
|
Trigger: ComponentType<AsProps>;
|
||||||
children: ReactNode | ReactNode[] | null;
|
children: ReactNode | ReactNode[] | null;
|
||||||
onMenuOpen: () => void;
|
onMenuOpen?: () => void;
|
||||||
onMenuClose: () => void;
|
onMenuClose?: () => void;
|
||||||
} & Omit<AsProps, "onPress">) => {
|
} & Omit<AsProps, "onPress">) => {
|
||||||
const [isOpen, setOpen] = useState(false);
|
const [isOpen, setOpen] = useState(false);
|
||||||
|
|
||||||
@ -55,7 +56,7 @@ const Menu = <AsProps,>({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* @ts-ignore */}
|
{/* @ts-ignore */}
|
||||||
<Triger onPress={() => setOpen(true)} {...props} />
|
<Trigger onPress={() => setOpen(true)} {...props} />
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<Portal>
|
<Portal>
|
||||||
<ContrastArea mode="user">
|
<ContrastArea mode="user">
|
||||||
@ -112,20 +113,22 @@ const MenuItem = ({
|
|||||||
label,
|
label,
|
||||||
selected,
|
selected,
|
||||||
onSelect,
|
onSelect,
|
||||||
|
href,
|
||||||
...props
|
...props
|
||||||
}: {
|
}: {
|
||||||
label: string;
|
label: string;
|
||||||
selected?: boolean;
|
selected?: boolean;
|
||||||
onSelect: () => void;
|
} & ({ onSelect: () => void; href?: undefined } | { href: string; onSelect?: undefined })) => {
|
||||||
}) => {
|
|
||||||
const { css, theme } = useYoshiki();
|
const { css, theme } = useYoshiki();
|
||||||
const setOpen = useContext(MenuContext);
|
const setOpen = useContext(MenuContext);
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PressableFeedback
|
<PressableFeedback
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
setOpen?.call(null, false);
|
setOpen?.call(null, false);
|
||||||
onSelect?.call(null);
|
onSelect?.call(null);
|
||||||
|
if (href) router.push(href);
|
||||||
}}
|
}}
|
||||||
{...css(
|
{...css(
|
||||||
{
|
{
|
||||||
|
@ -19,7 +19,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
||||||
import { ComponentType, forwardRef, ReactNode } from "react";
|
import { ComponentProps, ComponentType, forwardRef, ReactNode } from "react";
|
||||||
|
import Link from "next/link";
|
||||||
import { PressableProps } from "react-native";
|
import { PressableProps } from "react-native";
|
||||||
import { useYoshiki } from "yoshiki/web";
|
import { useYoshiki } from "yoshiki/web";
|
||||||
import { px, useYoshiki as useNativeYoshiki } from "yoshiki/native";
|
import { px, useYoshiki as useNativeYoshiki } from "yoshiki/native";
|
||||||
@ -43,16 +44,16 @@ const InternalTriger = forwardRef<unknown, any>(function _Triger(
|
|||||||
});
|
});
|
||||||
|
|
||||||
const Menu = <AsProps extends { onPress: PressableProps["onPress"] }>({
|
const Menu = <AsProps extends { onPress: PressableProps["onPress"] }>({
|
||||||
Triger,
|
Trigger,
|
||||||
onMenuOpen,
|
onMenuOpen,
|
||||||
onMenuClose,
|
onMenuClose,
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: {
|
}: {
|
||||||
Triger: ComponentType<AsProps>;
|
Trigger: ComponentType<AsProps>;
|
||||||
children: ReactNode | ReactNode[] | null;
|
children: ReactNode | ReactNode[] | null;
|
||||||
onMenuOpen: () => void;
|
onMenuOpen?: () => void;
|
||||||
onMenuClose: () => void;
|
onMenuClose?: () => void;
|
||||||
} & Omit<AsProps, "onPress">) => {
|
} & Omit<AsProps, "onPress">) => {
|
||||||
return (
|
return (
|
||||||
<DropdownMenu.Root
|
<DropdownMenu.Root
|
||||||
@ -63,7 +64,7 @@ const Menu = <AsProps extends { onPress: PressableProps["onPress"] }>({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DropdownMenu.Trigger asChild>
|
<DropdownMenu.Trigger asChild>
|
||||||
<InternalTriger Component={Triger} ComponentProps={props} />
|
<InternalTriger Component={Trigger} ComponentProps={props} />
|
||||||
</DropdownMenu.Trigger>
|
</DropdownMenu.Trigger>
|
||||||
<ContrastArea mode="user">
|
<ContrastArea mode="user">
|
||||||
<YoshikiProvider>
|
<YoshikiProvider>
|
||||||
@ -90,18 +91,38 @@ const Menu = <AsProps extends { onPress: PressableProps["onPress"] }>({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const Item = forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
ComponentProps<typeof DropdownMenu.Item> & { href?: string }
|
||||||
|
>(function _Item({ children, href, ...props }, ref) {
|
||||||
|
if (href) {
|
||||||
|
return (
|
||||||
|
<DropdownMenu.Item ref={ref} {...props} asChild>
|
||||||
|
<Link href={href} style={{ textDecoration: "none" }}>
|
||||||
|
{children}
|
||||||
|
</Link>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<DropdownMenu.Item ref={ref} {...props}>
|
||||||
|
{children}
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const MenuItem = ({
|
const MenuItem = ({
|
||||||
label,
|
label,
|
||||||
icon,
|
icon,
|
||||||
selected,
|
selected,
|
||||||
onSelect,
|
onSelect,
|
||||||
|
href,
|
||||||
...props
|
...props
|
||||||
}: {
|
}: {
|
||||||
label: string;
|
label: string;
|
||||||
icon?: JSX.Element;
|
icon?: JSX.Element;
|
||||||
selected?: boolean;
|
selected?: boolean;
|
||||||
onSelect: () => void;
|
} & ({ onSelect: () => void; href?: undefined } | { href: string; onSelect?: undefined })) => {
|
||||||
}) => {
|
|
||||||
const { css: nCss } = useNativeYoshiki();
|
const { css: nCss } = useNativeYoshiki();
|
||||||
const { css, theme } = useYoshiki();
|
const { css, theme } = useYoshiki();
|
||||||
|
|
||||||
@ -112,8 +133,9 @@ const MenuItem = ({
|
|||||||
background: ${theme.alternate.accent};
|
background: ${theme.alternate.accent};
|
||||||
}
|
}
|
||||||
`}</style>
|
`}</style>
|
||||||
<DropdownMenu.Item
|
<Item
|
||||||
onSelect={onSelect}
|
onSelect={onSelect}
|
||||||
|
href={href}
|
||||||
{...css(
|
{...css(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
@ -121,7 +143,6 @@ const MenuItem = ({
|
|||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
padding: "8px",
|
padding: "8px",
|
||||||
height: "32px",
|
height: "32px",
|
||||||
color: (theme) => theme.paragraph,
|
|
||||||
focus: {
|
focus: {
|
||||||
boxShadow: "none",
|
boxShadow: "none",
|
||||||
},
|
},
|
||||||
@ -138,8 +159,8 @@ const MenuItem = ({
|
|||||||
{...nCss({ paddingRight: px(8) })}
|
{...nCss({ paddingRight: px(8) })}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{<P {...nCss([{ color: "inherit" }, !selected && { paddingLeft: px(8 * 2) }])}>{label}</P>}
|
{<P {...nCss(!selected && { paddingLeft: px(8 * 2) })}>{label}</P>}
|
||||||
</DropdownMenu.Item>
|
</Item>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -29,6 +29,7 @@ import { percent, px, useYoshiki } from "yoshiki/native";
|
|||||||
import { DefaultLayout } from "../layout";
|
import { DefaultLayout } from "../layout";
|
||||||
import { FormPage } from "./form";
|
import { FormPage } from "./form";
|
||||||
import { PasswordInput } from "./password-input";
|
import { PasswordInput } from "./password-input";
|
||||||
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
|
|
||||||
export const LoginPage: QueryPage = () => {
|
export const LoginPage: QueryPage = () => {
|
||||||
const [username, setUsername] = useState("");
|
const [username, setUsername] = useState("");
|
||||||
@ -36,6 +37,7 @@ export const LoginPage: QueryPage = () => {
|
|||||||
const [error, setError] = useState<string | undefined>(undefined);
|
const [error, setError] = useState<string | undefined>(undefined);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { css } = useYoshiki();
|
const { css } = useYoshiki();
|
||||||
|
|
||||||
@ -66,7 +68,9 @@ export const LoginPage: QueryPage = () => {
|
|||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
const { error } = await loginFunc("login", { username, password });
|
const { error } = await loginFunc("login", { username, password });
|
||||||
setError(error);
|
setError(error);
|
||||||
if (!error) router.push("/");
|
if (error) return;
|
||||||
|
queryClient.invalidateQueries(["auth", "me"]);
|
||||||
|
router.push("/");
|
||||||
}}
|
}}
|
||||||
{...css({
|
{...css({
|
||||||
m: ts(1),
|
m: ts(1),
|
||||||
|
@ -29,6 +29,7 @@ import { percent, px, useYoshiki } from "yoshiki/native";
|
|||||||
import { DefaultLayout } from "../layout";
|
import { DefaultLayout } from "../layout";
|
||||||
import { FormPage } from "./form";
|
import { FormPage } from "./form";
|
||||||
import { PasswordInput } from "./password-input";
|
import { PasswordInput } from "./password-input";
|
||||||
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
|
|
||||||
export const RegisterPage: QueryPage = () => {
|
export const RegisterPage: QueryPage = () => {
|
||||||
const [email, setEmail] = useState("");
|
const [email, setEmail] = useState("");
|
||||||
@ -38,6 +39,7 @@ export const RegisterPage: QueryPage = () => {
|
|||||||
const [error, setError] = useState<string | undefined>(undefined);
|
const [error, setError] = useState<string | undefined>(undefined);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { css } = useYoshiki();
|
const { css } = useYoshiki();
|
||||||
|
|
||||||
@ -81,7 +83,9 @@ export const RegisterPage: QueryPage = () => {
|
|||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
const { error } = await loginFunc("register", { email, username, password });
|
const { error } = await loginFunc("register", { email, username, password });
|
||||||
setError(error);
|
setError(error);
|
||||||
if (!error) router.push("/");
|
if (error) return;
|
||||||
|
queryClient.invalidateQueries(["auth", "me"]);
|
||||||
|
router.push("/");
|
||||||
}}
|
}}
|
||||||
{...css({
|
{...css({
|
||||||
m: ts(1),
|
m: ts(1),
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Library, LibraryP, Page, Paged, QueryIdentifier, User, UserP } from "@kyoo/models";
|
import { Library, LibraryP, logout, Page, Paged, QueryIdentifier, User, UserP } from "@kyoo/models";
|
||||||
import {
|
import {
|
||||||
Input,
|
Input,
|
||||||
IconButton,
|
IconButton,
|
||||||
@ -29,17 +29,19 @@ import {
|
|||||||
tooltip,
|
tooltip,
|
||||||
ts,
|
ts,
|
||||||
Link,
|
Link,
|
||||||
|
Menu,
|
||||||
} from "@kyoo/primitives";
|
} from "@kyoo/primitives";
|
||||||
import { Platform, TextInput, View, ViewProps } from "react-native";
|
import { Platform, TextInput, View, ViewProps } from "react-native";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { createParam } from "solito";
|
import { createParam } from "solito";
|
||||||
import { useRouter } from "solito/router";
|
import { useRouter } from "solito/router";
|
||||||
import { rem, Stylable, useYoshiki } from "yoshiki/native";
|
import { rem, Stylable, useYoshiki } from "yoshiki/native";
|
||||||
import Menu from "@material-symbols/svg-400/rounded/menu-fill.svg";
|
import MenuIcon from "@material-symbols/svg-400/rounded/menu-fill.svg";
|
||||||
import Search from "@material-symbols/svg-400/rounded/search-fill.svg";
|
import Search from "@material-symbols/svg-400/rounded/search-fill.svg";
|
||||||
import { Fetch, FetchNE } from "../fetch";
|
import { Fetch, FetchNE } from "../fetch";
|
||||||
import { KyooLongLogo } from "./icon";
|
import { KyooLongLogo } from "./icon";
|
||||||
import { forwardRef, useRef, useState } from "react";
|
import { forwardRef, useRef, useState } from "react";
|
||||||
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
|
|
||||||
export const NavbarTitle = (props: Stylable & { onLayout?: ViewProps["onLayout"] }) => {
|
export const NavbarTitle = (props: Stylable & { onLayout?: ViewProps["onLayout"] }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -90,23 +92,37 @@ export const MeQuery: QueryIdentifier<User> = {
|
|||||||
export const NavbarProfile = () => {
|
export const NavbarProfile = () => {
|
||||||
const { css, theme } = useYoshiki();
|
const { css, theme } = useYoshiki();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
// TODO: show logged in user.
|
|
||||||
return (
|
return (
|
||||||
<FetchNE query={MeQuery}>
|
<FetchNE query={MeQuery}>
|
||||||
{({ username }) => (
|
{({ isError: isGuest, username }) => (
|
||||||
<Link
|
<Menu
|
||||||
href="/login"
|
Trigger={Avatar}
|
||||||
{...tooltip(username ?? t("navbar.login"))}
|
|
||||||
{...css({ marginLeft: ts(1), justifyContent: "center" })}
|
|
||||||
>
|
|
||||||
<Avatar
|
|
||||||
placeholder={username}
|
placeholder={username}
|
||||||
alt={t("navbar.login")}
|
alt={t("navbar.login")}
|
||||||
size={30}
|
size={30}
|
||||||
color={theme.colors.white}
|
color={theme.colors.white}
|
||||||
|
{...css({ marginLeft: ts(1), marginVertical: "auto", justifyContent: "center" })}
|
||||||
|
{...tooltip(username ?? t("navbar.login"))}
|
||||||
|
>
|
||||||
|
{isGuest ? (
|
||||||
|
<>
|
||||||
|
<Menu.Item label={t("login.login")} href="/login" />
|
||||||
|
<Menu.Item label={t("login.register")} href="/register" />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Menu.Item
|
||||||
|
label={t("login.logout")}
|
||||||
|
onSelect={() => {
|
||||||
|
logout();
|
||||||
|
queryClient.invalidateQueries(["auth", "me"]);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Link>
|
</>
|
||||||
|
)}
|
||||||
|
</Menu>
|
||||||
)}
|
)}
|
||||||
</FetchNE>
|
</FetchNE>
|
||||||
);
|
);
|
||||||
@ -175,13 +191,12 @@ export const Navbar = (props: Stylable) => {
|
|||||||
shadowOpacity: 0.3,
|
shadowOpacity: 0.3,
|
||||||
shadowRadius: 4.65,
|
shadowRadius: 4.65,
|
||||||
elevation: 8,
|
elevation: 8,
|
||||||
zIndex: 1,
|
|
||||||
},
|
},
|
||||||
props,
|
props,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={Menu}
|
icon={MenuIcon}
|
||||||
aria-label="more"
|
aria-label="more"
|
||||||
aria-controls="menu-appbar"
|
aria-controls="menu-appbar"
|
||||||
aria-haspopup="true"
|
aria-haspopup="true"
|
||||||
|
@ -65,7 +65,7 @@ export const RightButtons = ({
|
|||||||
<View {...css({ flexDirection: "row" }, props)}>
|
<View {...css({ flexDirection: "row" }, props)}>
|
||||||
{subtitles && (
|
{subtitles && (
|
||||||
<Menu
|
<Menu
|
||||||
Triger={IconButton}
|
Trigger={IconButton}
|
||||||
icon={ClosedCaption}
|
icon={ClosedCaption}
|
||||||
onMenuOpen={onMenuOpen}
|
onMenuOpen={onMenuOpen}
|
||||||
onMenuClose={onMenuClose}
|
onMenuClose={onMenuClose}
|
||||||
|
@ -53,6 +53,7 @@
|
|||||||
"login": {
|
"login": {
|
||||||
"login": "Login",
|
"login": "Login",
|
||||||
"register": "Register",
|
"register": "Register",
|
||||||
|
"logout": "Logout",
|
||||||
"server": "Server Address",
|
"server": "Server Address",
|
||||||
"email": "Email",
|
"email": "Email",
|
||||||
"username": "Username",
|
"username": "Username",
|
||||||
|
@ -53,6 +53,7 @@
|
|||||||
"login": {
|
"login": {
|
||||||
"login": "Connexion",
|
"login": "Connexion",
|
||||||
"register": "Créer un compte",
|
"register": "Créer un compte",
|
||||||
|
"logout": "Déconnexion",
|
||||||
"server": "Addresse du serveur",
|
"server": "Addresse du serveur",
|
||||||
"email": "Email",
|
"email": "Email",
|
||||||
"username": "Username",
|
"username": "Username",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user