mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-06-03 13:44:33 -04:00
Add a register page
This commit is contained in:
parent
b62b272492
commit
2b87aacc58
23
front/apps/mobile/app/register/index.tsx
Normal file
23
front/apps/mobile/app/register/index.tsx
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { RegisterPage } from "@kyoo/ui";
|
||||||
|
|
||||||
|
export default RegisterPage;
|
24
front/apps/web/src/pages/register/index.tsx
Normal file
24
front/apps/web/src/pages/register/index.tsx
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { RegisterPage } from "@kyoo/ui";
|
||||||
|
import { withRoute } from "~/router";
|
||||||
|
|
||||||
|
export default withRoute(RegisterPage);
|
@ -18,7 +18,8 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { px, Stylable, Theme, useYoshiki } from "yoshiki/native";
|
import { ComponentProps } from "react";
|
||||||
|
import { Theme, useYoshiki } from "yoshiki/native";
|
||||||
import { PressableFeedback } from "./links";
|
import { PressableFeedback } from "./links";
|
||||||
import { P } from "./text";
|
import { P } from "./text";
|
||||||
import { ts } from "./utils";
|
import { ts } from "./utils";
|
||||||
@ -27,16 +28,15 @@ export const Button = ({
|
|||||||
text,
|
text,
|
||||||
onPress,
|
onPress,
|
||||||
...props
|
...props
|
||||||
}: { text: string; onPress?: () => void } & Stylable) => {
|
}: { text: string } & ComponentProps<typeof PressableFeedback>) => {
|
||||||
const { css } = useYoshiki();
|
const { css } = useYoshiki();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PressableFeedback
|
<PressableFeedback
|
||||||
onPress={onPress}
|
|
||||||
{...css(
|
{...css(
|
||||||
{
|
{
|
||||||
flexGrow: 0,
|
flexGrow: 0,
|
||||||
p: ts(.5),
|
p: ts(0.5),
|
||||||
borderRadius: ts(5),
|
borderRadius: ts(5),
|
||||||
borderColor: (theme) => theme.accent,
|
borderColor: (theme) => theme.accent,
|
||||||
borderWidth: ts(0.5),
|
borderWidth: ts(0.5),
|
||||||
@ -45,6 +45,7 @@ export const Button = ({
|
|||||||
text: { color: (theme: Theme) => theme.colors.white },
|
text: { color: (theme: Theme) => theme.colors.white },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// @ts-ignore ??
|
||||||
props,
|
props,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
@ -23,4 +23,4 @@ export { BrowsePage } from "./browse";
|
|||||||
export { MovieDetails, ShowDetails } from "./details";
|
export { MovieDetails, ShowDetails } from "./details";
|
||||||
export { Player } from "./player";
|
export { Player } from "./player";
|
||||||
export { SearchPage } from "./search";
|
export { SearchPage } from "./search";
|
||||||
export { LoginPage } from "./login";
|
export { LoginPage, RegisterPage } from "./login";
|
||||||
|
86
front/packages/ui/src/login/form.tsx
Normal file
86
front/packages/ui/src/login/form.tsx
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ts } from "@kyoo/primitives";
|
||||||
|
import { ReactNode } from "react";
|
||||||
|
import { ImageBackground, ImageProps, Platform, View } from "react-native";
|
||||||
|
import Svg, { SvgProps, Path } from "react-native-svg";
|
||||||
|
import { min, percent, px, Stylable, useYoshiki, vh, vw } from "yoshiki/native";
|
||||||
|
|
||||||
|
const SvgBlob = (props: SvgProps) => {
|
||||||
|
const { css, theme } = useYoshiki();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View {...css({ width: percent(100), aspectRatio: 5 / 6 }, props)}>
|
||||||
|
<Svg width="100%" height="100%" viewBox="0 0 500 600">
|
||||||
|
<Path
|
||||||
|
d="M459.7 0c-20.2 43.3-40.3 86.6-51.7 132.6-11.3 45.9-13.9 94.6-36.1 137.6-22.2 43-64.1 80.3-111.5 88.2s-100.2-13.7-144.5-1.8C71.6 368.6 35.8 414.2 0 459.7V0h459.7z"
|
||||||
|
fill={theme.background}
|
||||||
|
/>
|
||||||
|
</Svg>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FormPage = ({ children, ...props }: { children: ReactNode } & Stylable) => {
|
||||||
|
const { css } = useYoshiki();
|
||||||
|
|
||||||
|
// TODO: Replace the hardcoded 1 to a random show/movie thumbnail.
|
||||||
|
const src = `${Platform.OS === "web" ? "/api/" : process.env.PUBLIC_BACK_URL}/shows/1/thumbnail`;
|
||||||
|
const nativeProps = Platform.select<ImageProps>({
|
||||||
|
web: {
|
||||||
|
defaultSource: typeof src === "string" ? { uri: src! } : Array.isArray(src) ? src[0] : src!,
|
||||||
|
},
|
||||||
|
default: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ImageBackground
|
||||||
|
source={{ uri: src }}
|
||||||
|
{...nativeProps}
|
||||||
|
{...css({
|
||||||
|
flexDirection: "row",
|
||||||
|
flexGrow: 1,
|
||||||
|
backgroundColor: (theme) => theme.dark.background,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<View
|
||||||
|
{...css({
|
||||||
|
width: min(vh(90), px(1200)),
|
||||||
|
height: min(vh(90), px(1200)),
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<SvgBlob {...css({ position: "absolute", top: 0, left: 0 })} />
|
||||||
|
<View
|
||||||
|
{...css(
|
||||||
|
{
|
||||||
|
width: percent(75),
|
||||||
|
paddingHorizontal: ts(3),
|
||||||
|
maxWidth: vw(100),
|
||||||
|
},
|
||||||
|
props,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</ImageBackground>
|
||||||
|
);
|
||||||
|
};
|
22
front/packages/ui/src/login/index.ts
Normal file
22
front/packages/ui/src/login/index.ts
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export { LoginPage } from "./login";
|
||||||
|
export { RegisterPage } from "./register";
|
@ -1,181 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { KyooErrors, kyooUrl, QueryPage } from "@kyoo/models";
|
|
||||||
import { Button, P, Input, ts, H1, A, IconButton } from "@kyoo/primitives";
|
|
||||||
import { ComponentProps, useState } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { ImageBackground, ImageProps, Platform, View } from "react-native";
|
|
||||||
import { Trans } from "react-i18next";
|
|
||||||
import { min, percent, px, useYoshiki, vh, vw } from "yoshiki/native";
|
|
||||||
import Visibility from "@material-symbols/svg-400/rounded/visibility-fill.svg";
|
|
||||||
import VisibilityOff from "@material-symbols/svg-400/rounded/visibility_off-fill.svg";
|
|
||||||
import { DefaultLayout } from "../layout";
|
|
||||||
import Svg, { SvgProps, Path } from "react-native-svg";
|
|
||||||
|
|
||||||
const SvgBlob = (props: SvgProps) => {
|
|
||||||
const { css, theme } = useYoshiki();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View {...css({ width: percent(100), aspectRatio: 5 / 6 }, props)}>
|
|
||||||
<Svg width="100%" height="100%" viewBox="0 0 500 600">
|
|
||||||
<Path
|
|
||||||
d="M459.7 0c-20.2 43.3-40.3 86.6-51.7 132.6-11.3 45.9-13.9 94.6-36.1 137.6-22.2 43-64.1 80.3-111.5 88.2s-100.2-13.7-144.5-1.8C71.6 368.6 35.8 414.2 0 459.7V0h459.7z"
|
|
||||||
fill={theme.background}
|
|
||||||
/>
|
|
||||||
</Svg>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const PasswordInput = (props: ComponentProps<typeof Input>) => {
|
|
||||||
const { css } = useYoshiki();
|
|
||||||
const [show, setVisibility] = useState(false);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Input
|
|
||||||
secureTextEntry={!show}
|
|
||||||
right={
|
|
||||||
<IconButton
|
|
||||||
icon={show ? VisibilityOff : Visibility}
|
|
||||||
size={19}
|
|
||||||
onPress={() => setVisibility(!show)}
|
|
||||||
{...css({ width: px(19), height: px(19), m: 0, p: 0 })}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const login = async (username: string, password: string) => {
|
|
||||||
let resp;
|
|
||||||
try {
|
|
||||||
resp = await fetch(`${kyooUrl}/auth/login`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
username,
|
|
||||||
password,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Login error", e);
|
|
||||||
throw { errors: ["Could not reach Kyoo's server."] } as KyooErrors;
|
|
||||||
}
|
|
||||||
if (!resp.ok) {
|
|
||||||
const err = await resp.json() as KyooErrors;
|
|
||||||
return { type: "error", value: null, error: err.errors[0] };
|
|
||||||
}
|
|
||||||
const token = await resp.json();
|
|
||||||
// TODO: Save the token in the secure storage.
|
|
||||||
return { type: "value", value: token, error: null };
|
|
||||||
};
|
|
||||||
|
|
||||||
export const LoginPage: QueryPage = () => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const { css } = useYoshiki();
|
|
||||||
|
|
||||||
// TODO: Replace the hardcoded 1 to a random show/movie thumbnail.
|
|
||||||
const src = `${Platform.OS === "web" ? "/api/" : process.env.PUBLIC_BACK_URL}/shows/1/thumbnail`;
|
|
||||||
const nativeProps = Platform.select<ImageProps>({
|
|
||||||
web: {
|
|
||||||
defaultSource: typeof src === "string" ? { uri: src! } : Array.isArray(src) ? src[0] : src!,
|
|
||||||
},
|
|
||||||
default: {},
|
|
||||||
});
|
|
||||||
|
|
||||||
const [username, setUsername] = useState("");
|
|
||||||
const [password, setPassword] = useState("");
|
|
||||||
const [error, setError] = useState<string | null>(null);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ImageBackground
|
|
||||||
source={{ uri: src }}
|
|
||||||
{...nativeProps}
|
|
||||||
{...css({
|
|
||||||
flexDirection: "row",
|
|
||||||
flexGrow: 1,
|
|
||||||
backgroundColor: (theme) => theme.dark.background,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<View
|
|
||||||
{...css({
|
|
||||||
width: min(vh(90), px(1200)),
|
|
||||||
height: min(vh(90), px(1200)),
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<SvgBlob {...css({ position: "absolute", top: 0, left: 0 })} />
|
|
||||||
<View
|
|
||||||
{...css({
|
|
||||||
width: percent(75),
|
|
||||||
maxWidth: vw(100),
|
|
||||||
paddingHorizontal: ts(3),
|
|
||||||
marginTop: Platform.OS === "web" ? ts(6) : 0,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<H1>{t("login.login")}</H1>
|
|
||||||
{Platform.OS !== "web" && (
|
|
||||||
<>
|
|
||||||
<P {...css({ paddingLeft: ts(1) })}>{t("login.server")}</P>
|
|
||||||
<Input variant="big" />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<P {...css({ paddingLeft: ts(1) })}>{t("login.username")}</P>
|
|
||||||
<Input
|
|
||||||
autoComplete="username"
|
|
||||||
variant="big"
|
|
||||||
onChangeText={(value) => setUsername(value)}
|
|
||||||
/>
|
|
||||||
<P {...css({ paddingLeft: ts(1) })}>{t("login.password")}</P>
|
|
||||||
<PasswordInput
|
|
||||||
autoComplete="password"
|
|
||||||
variant="big"
|
|
||||||
onChangeText={(value) => setPassword(value)}
|
|
||||||
/>
|
|
||||||
{error && <P {...css({ color: (theme) => theme.colors.red })}>{error}</P>}
|
|
||||||
<Button
|
|
||||||
text={t("login.login")}
|
|
||||||
onPress={async () => {
|
|
||||||
const { error } = await login(username, password);
|
|
||||||
setError(error);
|
|
||||||
}}
|
|
||||||
{...css({
|
|
||||||
m: ts(1),
|
|
||||||
width: px(250),
|
|
||||||
maxWidth: percent(100),
|
|
||||||
alignSelf: "center",
|
|
||||||
mY: ts(3),
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
<P>
|
|
||||||
<Trans i18nKey="login.or-register">
|
|
||||||
Don’t have an account? <A href="/register">Register</A>.
|
|
||||||
</Trans>
|
|
||||||
</P>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</ImageBackground>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
LoginPage.getLayout = DefaultLayout;
|
|
111
front/packages/ui/src/login/login.tsx
Normal file
111
front/packages/ui/src/login/login.tsx
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
/*
|
||||||
|
* 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { KyooErrors, kyooUrl, QueryPage } from "@kyoo/models";
|
||||||
|
import { Button, P, Input, ts, H1, A } from "@kyoo/primitives";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Platform } from "react-native";
|
||||||
|
import { Trans } from "react-i18next";
|
||||||
|
import { percent, px, useYoshiki } from "yoshiki/native";
|
||||||
|
import { DefaultLayout } from "../layout";
|
||||||
|
import { FormPage } from "./form";
|
||||||
|
import { PasswordInput } from "./password-input";
|
||||||
|
|
||||||
|
const login = async (username: string, password: string) => {
|
||||||
|
let resp;
|
||||||
|
try {
|
||||||
|
resp = await fetch(`${kyooUrl}/auth/login`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Login error", e);
|
||||||
|
throw { errors: ["Could not reach Kyoo's server."] } as KyooErrors;
|
||||||
|
}
|
||||||
|
if (!resp.ok) {
|
||||||
|
const err = (await resp.json()) as KyooErrors;
|
||||||
|
return { type: "error", value: null, error: err.errors[0] };
|
||||||
|
}
|
||||||
|
const token = await resp.json();
|
||||||
|
// TODO: Save the token in the secure storage.
|
||||||
|
return { type: "value", value: token, error: null };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LoginPage: QueryPage = () => {
|
||||||
|
const [username, setUsername] = useState("");
|
||||||
|
const [password, setPassword] = useState("");
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { css } = useYoshiki();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormPage
|
||||||
|
{...css({
|
||||||
|
marginTop: Platform.OS === "web" ? ts(6) : 0,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<H1>{t("login.login")}</H1>
|
||||||
|
{Platform.OS !== "web" && (
|
||||||
|
<>
|
||||||
|
<P {...css({ paddingLeft: ts(1) })}>{t("login.server")}</P>
|
||||||
|
<Input variant="big" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<P {...css({ paddingLeft: ts(1) })}>{t("login.username")}</P>
|
||||||
|
<Input autoComplete="username" variant="big" onChangeText={(value) => setUsername(value)} />
|
||||||
|
<P {...css({ paddingLeft: ts(1) })}>{t("login.password")}</P>
|
||||||
|
<PasswordInput
|
||||||
|
autoComplete="password"
|
||||||
|
variant="big"
|
||||||
|
onChangeText={(value) => setPassword(value)}
|
||||||
|
/>
|
||||||
|
{error && <P {...css({ color: (theme) => theme.colors.red })}>{error}</P>}
|
||||||
|
<Button
|
||||||
|
text={t("login.login")}
|
||||||
|
onPress={async () => {
|
||||||
|
const { error } = await login(username, password);
|
||||||
|
setError(error);
|
||||||
|
}}
|
||||||
|
{...css({
|
||||||
|
m: ts(1),
|
||||||
|
width: px(250),
|
||||||
|
maxWidth: percent(100),
|
||||||
|
alignSelf: "center",
|
||||||
|
mY: ts(3),
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<P>
|
||||||
|
<Trans i18nKey="login.or-register">
|
||||||
|
Don’t have an account? <A href="/register">Register</A>.
|
||||||
|
</Trans>
|
||||||
|
</P>
|
||||||
|
</FormPage>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
LoginPage.getLayout = DefaultLayout;
|
45
front/packages/ui/src/login/password-input.tsx
Normal file
45
front/packages/ui/src/login/password-input.tsx
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { IconButton, Input } from "@kyoo/primitives";
|
||||||
|
import { ComponentProps, useState } from "react";
|
||||||
|
import { px, useYoshiki } from "yoshiki/native";
|
||||||
|
import Visibility from "@material-symbols/svg-400/rounded/visibility-fill.svg";
|
||||||
|
import VisibilityOff from "@material-symbols/svg-400/rounded/visibility_off-fill.svg";
|
||||||
|
|
||||||
|
export const PasswordInput = (props: ComponentProps<typeof Input>) => {
|
||||||
|
const { css } = useYoshiki();
|
||||||
|
const [show, setVisibility] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Input
|
||||||
|
secureTextEntry={!show}
|
||||||
|
right={
|
||||||
|
<IconButton
|
||||||
|
icon={show ? VisibilityOff : Visibility}
|
||||||
|
size={19}
|
||||||
|
onPress={() => setVisibility(!show)}
|
||||||
|
{...css({ width: px(19), height: px(19), m: 0, p: 0 })}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
100
front/packages/ui/src/login/register.tsx
Normal file
100
front/packages/ui/src/login/register.tsx
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
/*
|
||||||
|
* 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { KyooErrors, kyooUrl, QueryPage } from "@kyoo/models";
|
||||||
|
import { Button, P, Input, ts, H1, A } from "@kyoo/primitives";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Platform } from "react-native";
|
||||||
|
import { Trans } from "react-i18next";
|
||||||
|
import { percent, px, useYoshiki } from "yoshiki/native";
|
||||||
|
import { DefaultLayout } from "../layout";
|
||||||
|
import { FormPage } from "./form";
|
||||||
|
import { PasswordInput } from "./password-input";
|
||||||
|
|
||||||
|
export const RegisterPage: QueryPage = () => {
|
||||||
|
const [email, setEmail] = useState("");
|
||||||
|
const [username, setUsername] = useState("");
|
||||||
|
const [password, setPassword] = useState("");
|
||||||
|
const [confirm, setConfirm] = useState("");
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { css } = useYoshiki();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormPage>
|
||||||
|
<H1>{t("login.register")}</H1>
|
||||||
|
{Platform.OS !== "web" && (
|
||||||
|
<>
|
||||||
|
<P {...css({ paddingLeft: ts(1) })}>{t("login.server")}</P>
|
||||||
|
<Input variant="big" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<P {...css({ paddingLeft: ts(1) })}>{t("login.username")}</P>
|
||||||
|
<Input autoComplete="username" variant="big" onChangeText={(value) => setUsername(value)} />
|
||||||
|
|
||||||
|
<P {...css({ paddingLeft: ts(1) })}>{t("login.email")}</P>
|
||||||
|
<Input autoComplete="email" variant="big" onChangeText={(value) => setEmail(value)} />
|
||||||
|
|
||||||
|
<P {...css({ paddingLeft: ts(1) })}>{t("login.password")}</P>
|
||||||
|
<PasswordInput
|
||||||
|
autoComplete="password-new"
|
||||||
|
variant="big"
|
||||||
|
onChangeText={(value) => setPassword(value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<P {...css({ paddingLeft: ts(1) })}>{t("login.confirm")}</P>
|
||||||
|
<PasswordInput
|
||||||
|
autoComplete="password-new"
|
||||||
|
variant="big"
|
||||||
|
onChangeText={(value) => setConfirm(value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{password !== confirm && (
|
||||||
|
<P {...css({ color: (theme) => theme.colors.red })}>{t("login.password-no-match")}</P>
|
||||||
|
)}
|
||||||
|
{error && <P {...css({ color: (theme) => theme.colors.red })}>{error}</P>}
|
||||||
|
<Button
|
||||||
|
text={t("login.login")}
|
||||||
|
disabled={error != null || password !== confirm}
|
||||||
|
onPress={async () => {
|
||||||
|
const { error } = await register(email, username, password);
|
||||||
|
setError(error);
|
||||||
|
}}
|
||||||
|
{...css({
|
||||||
|
m: ts(1),
|
||||||
|
width: px(250),
|
||||||
|
maxWidth: percent(100),
|
||||||
|
alignSelf: "center",
|
||||||
|
mY: ts(3),
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
<P>
|
||||||
|
<Trans i18nKey="login.or-login">
|
||||||
|
Have an account already? <A href="/login">Log in</A>.
|
||||||
|
</Trans>
|
||||||
|
</P>
|
||||||
|
</FormPage>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
RegisterPage.getLayout = DefaultLayout;
|
@ -57,6 +57,9 @@
|
|||||||
"email": "Email",
|
"email": "Email",
|
||||||
"username": "Username",
|
"username": "Username",
|
||||||
"password": "Password",
|
"password": "Password",
|
||||||
"or-register": "Don’t have an account? <1>Register</1>."
|
"confirm": "Confirm Password",
|
||||||
|
"or-register": "Don’t have an account? <1>Register</1>.",
|
||||||
|
"or-login": "Have an account already? <1>Log in</1>.",
|
||||||
|
"password-no-match": "Passwords do not match."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,6 +57,9 @@
|
|||||||
"email": "Email",
|
"email": "Email",
|
||||||
"username": "Username",
|
"username": "Username",
|
||||||
"password": "Mot de passe",
|
"password": "Mot de passe",
|
||||||
"or-register": "Vous n'avez pas de compte ? <1>Inscrivez-vous</1>."
|
"confirm": "Confirm Password",
|
||||||
|
"or-register": "Vous n'avez pas de compte ? <1>Inscrivez-vous</1>.",
|
||||||
|
"or-login": "Vous avez déjà un compte ? <1>Connectez-vous.<1/>",
|
||||||
|
"password-no-match": "Mots de passe differents"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user