mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-31 12:14:46 -04:00
Add continue with oidc button on login and register pages
This commit is contained in:
parent
5f8d0d1b99
commit
239ad9a4dc
@ -22,11 +22,21 @@ namespace Kyoo.Authentication.Models;
|
|||||||
|
|
||||||
public class ServerInfo
|
public class ServerInfo
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The list of oidc providers configured for this instance of kyoo.
|
||||||
|
/// </summary>
|
||||||
public Dictionary<string, OidcInfo> Oidc { get; set; }
|
public Dictionary<string, OidcInfo> Oidc { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class OidcInfo
|
public class OidcInfo
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The name of this oidc service. Human readable.
|
||||||
|
/// </summary>
|
||||||
public string DisplayName { get; set; }
|
public string DisplayName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A url returing a square logo for this provider.
|
||||||
|
/// </summary>
|
||||||
public string? LogoUrl { get; set; }
|
public string? LogoUrl { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -32,3 +32,4 @@ export * from "./watch-info";
|
|||||||
export * from "./watch-status";
|
export * from "./watch-status";
|
||||||
export * from "./watchlist";
|
export * from "./watchlist";
|
||||||
export * from "./user";
|
export * from "./user";
|
||||||
|
export * from "./server-info";
|
||||||
|
54
front/packages/models/src/resources/server-info.ts
Normal file
54
front/packages/models/src/resources/server-info.ts
Normal 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>;
|
@ -23,14 +23,17 @@ 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";
|
||||||
import { View } from "react-native";
|
import { Falsy, View } from "react-native";
|
||||||
|
|
||||||
export const Button = forwardRef<
|
export const Button = forwardRef<
|
||||||
View,
|
View,
|
||||||
{ text: string; licon?: ReactElement; icon?: ReactElement } & ComponentProps<
|
{
|
||||||
typeof PressableFeedback
|
children?: ReactElement | Falsy;
|
||||||
>
|
text?: string;
|
||||||
>(function Button({ text, icon, licon, ...props }, ref) {
|
licon?: ReactElement | Falsy;
|
||||||
|
icon?: ReactElement | Falsy;
|
||||||
|
} & ComponentProps<typeof PressableFeedback>
|
||||||
|
>(function Button({ children, text, icon, licon, ...props }, ref) {
|
||||||
const { css } = useYoshiki("button");
|
const { css } = useYoshiki("button");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -55,17 +58,20 @@ export const Button = forwardRef<
|
|||||||
props as any,
|
props as any,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<View
|
{(licon || text || icon) != null && (
|
||||||
{...css({
|
<View
|
||||||
paddingX: ts(3),
|
{...css({
|
||||||
flexDirection: "row",
|
paddingX: ts(3),
|
||||||
alignItems: "center",
|
flexDirection: "row",
|
||||||
})}
|
alignItems: "center",
|
||||||
>
|
})}
|
||||||
{licon}
|
>
|
||||||
<P {...css({ textAlign: "center" }, "text")}>{text}</P>
|
{licon}
|
||||||
{icon}
|
{text && <P {...css({ textAlign: "center" }, "text")}>{text}</P>}
|
||||||
</View>
|
{icon}
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
{children}
|
||||||
</PressableFeedback>
|
</PressableFeedback>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { HR as EHR } from "@expo/html-elements";
|
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";
|
import { ts } from "./utils";
|
||||||
|
|
||||||
export const HR = ({
|
export const HR = ({
|
||||||
@ -39,13 +39,13 @@ export const HR = ({
|
|||||||
},
|
},
|
||||||
orientation === "vertical" && {
|
orientation === "vertical" && {
|
||||||
width: px(1),
|
width: px(1),
|
||||||
height: "auto",
|
height: percent(100),
|
||||||
marginY: ts(1),
|
marginY: ts(1),
|
||||||
marginX: ts(2),
|
marginX: ts(2),
|
||||||
},
|
},
|
||||||
orientation === "horizontal" && {
|
orientation === "horizontal" && {
|
||||||
height: px(1),
|
height: px(1),
|
||||||
width: "auto",
|
width: percent(100),
|
||||||
marginX: ts(1),
|
marginX: ts(1),
|
||||||
marginY: ts(2),
|
marginY: ts(2),
|
||||||
},
|
},
|
||||||
|
@ -29,7 +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";
|
import { OidcLogin } from "./oidc";
|
||||||
|
|
||||||
export const cleanApiUrl = (apiUrl: string) => {
|
export const cleanApiUrl = (apiUrl: string) => {
|
||||||
if (Platform.OS === "web") return undefined;
|
if (Platform.OS === "web") return undefined;
|
||||||
@ -45,7 +45,6 @@ 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();
|
||||||
|
|
||||||
@ -56,6 +55,7 @@ export const LoginPage: QueryPage = () => {
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<H1>{t("login.login")}</H1>
|
<H1>{t("login.login")}</H1>
|
||||||
|
<OidcLogin />
|
||||||
{Platform.OS !== "web" && (
|
{Platform.OS !== "web" && (
|
||||||
<>
|
<>
|
||||||
<P {...css({ paddingLeft: ts(1) })}>{t("login.server")}</P>
|
<P {...css({ paddingLeft: ts(1) })}>{t("login.server")}</P>
|
||||||
@ -102,4 +102,6 @@ export const LoginPage: QueryPage = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
LoginPage.getFetchUrls = () => [OidcLogin.query()];
|
||||||
|
|
||||||
LoginPage.getLayout = DefaultLayout;
|
LoginPage.getLayout = DefaultLayout;
|
||||||
|
83
front/packages/ui/src/login/oidc.tsx
Normal file
83
front/packages/ui/src/login/oidc.tsx
Normal 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,
|
||||||
|
});
|
@ -30,6 +30,7 @@ import { DefaultLayout } from "../layout";
|
|||||||
import { FormPage } from "./form";
|
import { FormPage } from "./form";
|
||||||
import { PasswordInput } from "./password-input";
|
import { PasswordInput } from "./password-input";
|
||||||
import { cleanApiUrl } from "./login";
|
import { cleanApiUrl } from "./login";
|
||||||
|
import { OidcLogin } from "./oidc";
|
||||||
|
|
||||||
export const RegisterPage: QueryPage = () => {
|
export const RegisterPage: QueryPage = () => {
|
||||||
const [apiUrl, setApiUrl] = useState("");
|
const [apiUrl, setApiUrl] = useState("");
|
||||||
@ -46,6 +47,7 @@ export const RegisterPage: QueryPage = () => {
|
|||||||
return (
|
return (
|
||||||
<FormPage>
|
<FormPage>
|
||||||
<H1>{t("login.register")}</H1>
|
<H1>{t("login.register")}</H1>
|
||||||
|
<OidcLogin />
|
||||||
{Platform.OS !== "web" && (
|
{Platform.OS !== "web" && (
|
||||||
<>
|
<>
|
||||||
<P {...css({ paddingLeft: ts(1) })}>{t("login.server")}</P>
|
<P {...css({ paddingLeft: ts(1) })}>{t("login.server")}</P>
|
||||||
@ -109,4 +111,6 @@ export const RegisterPage: QueryPage = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
RegisterPage.getFetchUrls = () => [OidcLogin.query()];
|
||||||
|
|
||||||
RegisterPage.getLayout = DefaultLayout;
|
RegisterPage.getLayout = DefaultLayout;
|
||||||
|
@ -68,7 +68,8 @@
|
|||||||
"more": "More",
|
"more": "More",
|
||||||
"expand": "Expand",
|
"expand": "Expand",
|
||||||
"collapse": "Collapse",
|
"collapse": "Collapse",
|
||||||
"edit": "Edit"
|
"edit": "Edit",
|
||||||
|
"or": "OR"
|
||||||
},
|
},
|
||||||
"navbar": {
|
"navbar": {
|
||||||
"home": "Home",
|
"home": "Home",
|
||||||
@ -165,6 +166,7 @@
|
|||||||
"login": {
|
"login": {
|
||||||
"login": "Login",
|
"login": "Login",
|
||||||
"register": "Register",
|
"register": "Register",
|
||||||
|
"via": "Continue via {{provider}}",
|
||||||
"add-account": "Add account",
|
"add-account": "Add account",
|
||||||
"logout": "Logout",
|
"logout": "Logout",
|
||||||
"server": "Server Address",
|
"server": "Server Address",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user