Add continue with oidc button on login and register pages

This commit is contained in:
Zoe Roux 2024-03-03 16:10:46 +01:00
parent 5f8d0d1b99
commit 239ad9a4dc
9 changed files with 184 additions and 22 deletions

View File

@ -22,11 +22,21 @@ namespace Kyoo.Authentication.Models;
public class ServerInfo
{
/// <summary>
/// The list of oidc providers configured for this instance of kyoo.
/// </summary>
public Dictionary<string, OidcInfo> Oidc { get; set; }
}
public class OidcInfo
{
/// <summary>
/// The name of this oidc service. Human readable.
/// </summary>
public string DisplayName { get; set; }
/// <summary>
/// A url returing a square logo for this provider.
/// </summary>
public string? LogoUrl { get; set; }
}

View File

@ -32,3 +32,4 @@ export * from "./watch-info";
export * from "./watch-status";
export * from "./watchlist";
export * from "./user";
export * from "./server-info";

View File

@ -0,0 +1,54 @@
/*
* 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 { z } from "zod";
import { imageFn } from "..";
export const OidcInfoP = z.object({
/*
* The name of this oidc service. Human readable.
*/
displayName: z.string(),
/*
* A url returing a square logo for this provider.
*/
logoUrl: z.string().nullable(),
});
export const ServerInfoP = z.object({
/*
* The list of oidc providers configured for this instance of kyoo.
*/
oidc: z
.record(z.string(), OidcInfoP)
.transform((x) =>
Object.fromEntries(
Object.entries(x).map(([provider, info]) => [
provider,
{ ...info, link: imageFn(`/auth/login/${provider}`) },
]),
),
),
});
/**
* A season of a Show.
*/
export type ServerInfo = z.infer<typeof ServerInfoP>;

View File

@ -23,14 +23,17 @@ import { Theme, useYoshiki } from "yoshiki/native";
import { PressableFeedback } from "./links";
import { P } from "./text";
import { ts } from "./utils";
import { View } from "react-native";
import { Falsy, View } from "react-native";
export const Button = forwardRef<
View,
{ text: string; licon?: ReactElement; icon?: ReactElement } & ComponentProps<
typeof PressableFeedback
>
>(function Button({ text, icon, licon, ...props }, ref) {
{
children?: ReactElement | Falsy;
text?: string;
licon?: ReactElement | Falsy;
icon?: ReactElement | Falsy;
} & ComponentProps<typeof PressableFeedback>
>(function Button({ children, text, icon, licon, ...props }, ref) {
const { css } = useYoshiki("button");
return (
@ -55,17 +58,20 @@ export const Button = forwardRef<
props as any,
)}
>
<View
{...css({
paddingX: ts(3),
flexDirection: "row",
alignItems: "center",
})}
>
{licon}
<P {...css({ textAlign: "center" }, "text")}>{text}</P>
{icon}
</View>
{(licon || text || icon) != null && (
<View
{...css({
paddingX: ts(3),
flexDirection: "row",
alignItems: "center",
})}
>
{licon}
{text && <P {...css({ textAlign: "center" }, "text")}>{text}</P>}
{icon}
</View>
)}
{children}
</PressableFeedback>
);
});

View File

@ -19,7 +19,7 @@
*/
import { HR as EHR } from "@expo/html-elements";
import { px, Stylable, useYoshiki } from "yoshiki/native";
import { percent, px, Stylable, useYoshiki } from "yoshiki/native";
import { ts } from "./utils";
export const HR = ({
@ -39,13 +39,13 @@ export const HR = ({
},
orientation === "vertical" && {
width: px(1),
height: "auto",
height: percent(100),
marginY: ts(1),
marginX: ts(2),
},
orientation === "horizontal" && {
height: px(1),
width: "auto",
width: percent(100),
marginX: ts(1),
marginY: ts(2),
},

View File

@ -29,7 +29,7 @@ import { percent, px, useYoshiki } from "yoshiki/native";
import { DefaultLayout } from "../layout";
import { FormPage } from "./form";
import { PasswordInput } from "./password-input";
import { useQueryClient } from "@tanstack/react-query";
import { OidcLogin } from "./oidc";
export const cleanApiUrl = (apiUrl: string) => {
if (Platform.OS === "web") return undefined;
@ -45,7 +45,6 @@ export const LoginPage: QueryPage = () => {
const [error, setError] = useState<string | undefined>(undefined);
const router = useRouter();
const queryClient = useQueryClient();
const { t } = useTranslation();
const { css } = useYoshiki();
@ -56,6 +55,7 @@ export const LoginPage: QueryPage = () => {
})}
>
<H1>{t("login.login")}</H1>
<OidcLogin />
{Platform.OS !== "web" && (
<>
<P {...css({ paddingLeft: ts(1) })}>{t("login.server")}</P>
@ -102,4 +102,6 @@ export const LoginPage: QueryPage = () => {
);
};
LoginPage.getFetchUrls = () => [OidcLogin.query()];
LoginPage.getLayout = DefaultLayout;

View File

@ -0,0 +1,83 @@
/*
* 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 { QueryIdentifier, ServerInfo, ServerInfoP, useFetch } from "@kyoo/models";
import { Button, HR, P, Skeleton, tooltip, ts } from "@kyoo/primitives";
import { View, ImageBackground } from "react-native";
import { percent, rem, useYoshiki } from "yoshiki/native";
import { useTranslation } from "react-i18next";
import { ErrorView } from "../errors";
export const OidcLogin = () => {
const { css } = useYoshiki();
const { t } = useTranslation();
const { data, error } = useFetch(OidcLogin.query());
const btn = css({ width: { xs: percent(100), sm: percent(75) }, marginY: ts(1) });
return (
<View {...css({ alignItems: "center", marginY: ts(1) })}>
{error ? (
<ErrorView error={error} />
) : data ? (
Object.values(data.oidc).map((x) => (
<Button
href={x.link}
key={x.displayName}
licon={
x.logoUrl != null && (
<ImageBackground
source={{ uri: x.logoUrl }}
{...css({ width: ts(3), height: ts(3), marginRight: ts(2) })}
/>
)
}
text={t("login.via", { provider: x.displayName })}
{...tooltip(t("login.via", { provider: x.displayName }))}
{...btn}
/>
))
) : (
[...Array(3)].map((_, i) => (
<Button key={i} {...btn}>
<Skeleton {...css({ width: percent(66), marginY: rem(0.5) })} />
</Button>
))
)}
<View
{...css({
marginY: ts(1),
flexDirection: "row",
width: percent(100),
alignItems: "center",
})}
>
<HR {...css({ flexGrow: 1 })} />
<P>{t("misc.or")}</P>
<HR {...css({ flexGrow: 1 })} />
</View>
</View>
);
};
OidcLogin.query = (): QueryIdentifier<ServerInfo> => ({
path: ["info"],
parser: ServerInfoP,
});

View File

@ -30,6 +30,7 @@ import { DefaultLayout } from "../layout";
import { FormPage } from "./form";
import { PasswordInput } from "./password-input";
import { cleanApiUrl } from "./login";
import { OidcLogin } from "./oidc";
export const RegisterPage: QueryPage = () => {
const [apiUrl, setApiUrl] = useState("");
@ -46,6 +47,7 @@ export const RegisterPage: QueryPage = () => {
return (
<FormPage>
<H1>{t("login.register")}</H1>
<OidcLogin />
{Platform.OS !== "web" && (
<>
<P {...css({ paddingLeft: ts(1) })}>{t("login.server")}</P>
@ -109,4 +111,6 @@ export const RegisterPage: QueryPage = () => {
);
};
RegisterPage.getFetchUrls = () => [OidcLogin.query()];
RegisterPage.getLayout = DefaultLayout;

View File

@ -68,7 +68,8 @@
"more": "More",
"expand": "Expand",
"collapse": "Collapse",
"edit": "Edit"
"edit": "Edit",
"or": "OR"
},
"navbar": {
"home": "Home",
@ -165,6 +166,7 @@
"login": {
"login": "Login",
"register": "Register",
"via": "Continue via {{provider}}",
"add-account": "Add account",
"logout": "Logout",
"server": "Server Address",