Cleanup server address selector

This commit is contained in:
Zoe Roux 2024-03-06 21:27:53 +01:00
parent d52cc045e0
commit 3821950e49
9 changed files with 59 additions and 47 deletions

View File

@ -19,5 +19,6 @@
*/ */
import { LoginPage } from "@kyoo/ui"; import { LoginPage } from "@kyoo/ui";
import { withRoute } from "../../utils";
export default LoginPage; export default withRoute(LoginPage);

View File

@ -19,5 +19,6 @@
*/ */
import { RegisterPage } from "@kyoo/ui"; import { RegisterPage } from "@kyoo/ui";
import { withRoute } from "../../utils";
export default RegisterPage; export default withRoute(RegisterPage);

View File

@ -49,16 +49,17 @@ export const queryFn = async <Parser extends z.ZodTypeAny>(
type?: Parser, type?: Parser,
token?: string | null, token?: string | null,
): Promise<z.infer<Parser>> => { ): Promise<z.infer<Parser>> => {
const url = context.apiUrl ?? getCurrentApiUrl(); const url = context.apiUrl ? null : getCurrentApiUrl();
if (token === undefined && context.authenticated !== false) token = await getToken(); if (token === undefined && context.authenticated !== false) token = await getToken();
const path = [url] const path = [url]
.concat( .concat(
"path" in context "path" in context
? (context.path.filter((x) => x) as string[]) ? (context.path as string[])
: "pageParam" in context && context.pageParam : "pageParam" in context && context.pageParam
? [context.pageParam as string] ? [context.pageParam as string]
: (context.queryKey.filter((x) => x) as string[]), : (context.queryKey as string[]),
) )
.filter((x) => x)
.join("/") .join("/")
.replace("/?", "?"); .replace("/?", "?");
let resp; let resp;
@ -182,19 +183,19 @@ export type QueryPage<Props = {}, Items = unknown> = ComponentType<
export const toQueryKey = (query: { export const toQueryKey = (query: {
path: (string | undefined)[]; path: (string | undefined)[];
params?: { [query: string]: boolean | number | string | string[] | undefined }; params?: { [query: string]: boolean | number | string | string[] | undefined };
options?: { apiUrl?: string };
}) => { }) => {
if (query.params) { return [
return [ query.options?.apiUrl,
...query.path, ...query.path,
"?" + query.params
? "?" +
Object.entries(query.params) Object.entries(query.params)
.filter(([_, v]) => v !== undefined) .filter(([_, v]) => v !== undefined)
.map(([k, v]) => `${k}=${Array.isArray(v) ? v.join(",") : v}`) .map(([k, v]) => `${k}=${Array.isArray(v) ? v.join(",") : v}`)
.join("&"), .join("&")
]; : null,
} else { ];
return query.path;
}
}; };
export const useFetch = <Data,>(query: QueryIdentifier<Data>) => { export const useFetch = <Data,>(query: QueryIdentifier<Data>) => {

View File

@ -18,28 +18,37 @@
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>. * along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { ComponentProps, ReactElement, forwardRef } from "react"; import { ComponentType, ForwardedRef, ReactElement, forwardRef } from "react";
import { Theme, useYoshiki } from "yoshiki/native"; 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 { Falsy, View } from "react-native"; import { Falsy, PressableProps, View } from "react-native";
export const Button = forwardRef< export const Button = forwardRef(function Button<AsProps = PressableProps>(
View,
{ {
children,
text,
icon,
licon,
as,
...props
}: {
children?: ReactElement | Falsy; children?: ReactElement | Falsy;
text?: string; text?: string;
licon?: ReactElement | Falsy; licon?: ReactElement | Falsy;
icon?: ReactElement | Falsy; icon?: ReactElement | Falsy;
} & ComponentProps<typeof PressableFeedback> as?: ComponentType<AsProps>;
>(function Button({ children, text, icon, licon, ...props }, ref) { } & AsProps,
ref: ForwardedRef<unknown>,
) {
const { css } = useYoshiki("button"); const { css } = useYoshiki("button");
const Container = as ?? PressableFeedback;
return ( return (
<PressableFeedback <Container
ref={ref} ref={ref as any}
{...css( {...(css(
{ {
flexGrow: 0, flexGrow: 0,
flexDirection: "row", flexDirection: "row",
@ -48,7 +57,7 @@ export const Button = forwardRef<
overflow: "hidden", overflow: "hidden",
p: ts(0.5), p: ts(0.5),
borderRadius: ts(5), borderRadius: ts(5),
borderColor: (theme) => theme.accent, borderColor: (theme: Theme) => theme.accent,
borderWidth: ts(0.5), borderWidth: ts(0.5),
fover: { fover: {
self: { bg: (theme: Theme) => theme.accent }, self: { bg: (theme: Theme) => theme.accent },
@ -56,7 +65,7 @@ export const Button = forwardRef<
}, },
}, },
props as any, props as any,
)} ) as AsProps)}
> >
{(licon || text || icon) != null && ( {(licon || text || icon) != null && (
<View <View
@ -72,6 +81,6 @@ export const Button = forwardRef<
</View> </View>
)} )}
{children} {children}
</PressableFeedback> </Container>
); );
}); });

View File

@ -40,10 +40,14 @@ const SvgBlob = (props: SvgProps) => {
); );
}; };
export const FormPage = ({ children, ...props }: { children: ReactNode } & Stylable) => { export const FormPage = ({
children,
apiUrl,
...props
}: { children: ReactNode; apiUrl?: string } & Stylable) => {
const { css } = useYoshiki(); const { css } = useYoshiki();
const src = imageFn("/items/random/thumbnail"); const src = apiUrl ? `${apiUrl}/items/random/thumbnail` : imageFn("/items/random/thumbnail");
const nativeProps = Platform.select<Partial<ImageProps>>({ const nativeProps = Platform.select<Partial<ImageProps>>({
web: { web: {
defaultSource: { uri: src }, defaultSource: { uri: src },

View File

@ -44,6 +44,7 @@ export const LoginPage: QueryPage<{ apiUrl?: string; error?: string }> = ({
const { css } = useYoshiki(); const { css } = useYoshiki();
useEffect(() => { useEffect(() => {
console.log("login", apiUrl);
if (!apiUrl && Platform.OS !== "web") if (!apiUrl && Platform.OS !== "web")
router.replace("/server-url", undefined, { router.replace("/server-url", undefined, {
experimental: { nativeBehavior: "stack-replace", isNestedNavigator: false }, experimental: { nativeBehavior: "stack-replace", isNestedNavigator: false },
@ -51,7 +52,7 @@ export const LoginPage: QueryPage<{ apiUrl?: string; error?: string }> = ({
}, [apiUrl, router]); }, [apiUrl, router]);
return ( return (
<FormPage> <FormPage apiUrl={apiUrl}>
<H1>{t("login.login")}</H1> <H1>{t("login.login")}</H1>
<OidcLogin apiUrl={apiUrl} /> <OidcLogin apiUrl={apiUrl} />
<P {...css({ paddingLeft: ts(1) })}>{t("login.username")}</P> <P {...css({ paddingLeft: ts(1) })}>{t("login.username")}</P>

View File

@ -104,7 +104,7 @@ export const OidcCallbackPage: QueryPage<{
hasRun.current = true; hasRun.current = true;
function onError(error: string) { function onError(error: string) {
router.replace(`/login?error=${error}${apiUrl ? `&apiUrl=${apiUrl}` : ""}`, undefined, { router.replace({pathname: "/login", query: { error, apiUrl }}, undefined, {
experimental: { nativeBehavior: "stack-replace", isNestedNavigator: false }, experimental: { nativeBehavior: "stack-replace", isNestedNavigator: false },
}); });
} }

View File

@ -50,7 +50,7 @@ export const RegisterPage: QueryPage<{ apiUrl?: string }> = ({ apiUrl }) => {
}, [apiUrl, router]); }, [apiUrl, router]);
return ( return (
<FormPage> <FormPage apiUrl={apiUrl}>
<H1>{t("login.register")}</H1> <H1>{t("login.register")}</H1>
<OidcLogin apiUrl={apiUrl} /> <OidcLogin apiUrl={apiUrl} />
<P {...css({ paddingLeft: ts(1) })}>{t("login.username")}</P> <P {...css({ paddingLeft: ts(1) })}>{t("login.username")}</P>

View File

@ -19,7 +19,7 @@
*/ */
import { QueryIdentifier, QueryPage, ServerInfo, ServerInfoP, useFetch } from "@kyoo/models"; import { QueryIdentifier, QueryPage, ServerInfo, ServerInfoP, useFetch } from "@kyoo/models";
import { Button, P, Input, ts, H1, HR } from "@kyoo/primitives"; import { Button, P, Input, ts, H1, HR, Link } from "@kyoo/primitives";
import { useState } from "react"; import { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Platform, View } from "react-native"; import { Platform, View } from "react-native";
@ -40,8 +40,9 @@ const query: QueryIdentifier<ServerInfo> = {
}; };
export const ServerUrlPage: QueryPage = () => { export const ServerUrlPage: QueryPage = () => {
const [apiUrl, setApiUrl] = useState(""); const [_apiUrl, setApiUrl] = useState("");
const { data, error } = useFetch(query); const apiUrl = cleanApiUrl(_apiUrl);
const { data, error } = useFetch({ ...query, options: { apiUrl: apiUrl } });
const router = useRouter(); const router = useRouter();
const { t } = useTranslation(); const { t } = useTranslation();
const { css } = useYoshiki(); const { css } = useYoshiki();
@ -57,11 +58,9 @@ export const ServerUrlPage: QueryPage = () => {
<H1>{t("login.server")}</H1> <H1>{t("login.server")}</H1>
<View {...css({ justifyContent: "center" })}> <View {...css({ justifyContent: "center" })}>
<Input variant="big" onChangeText={setApiUrl} /> <Input variant="big" onChangeText={setApiUrl} />
{error && ( <P {...css({ color: (theme: Theme) => theme.colors.red, alignSelf: "center" })}>
<P {...css({ color: (theme: Theme) => theme.colors.red, alignSelf: "center" })}> {error?.errors[0] ?? " "}
{error.errors[0]} </P>
</P>
)}
</View> </View>
<View {...css({ marginTop: ts(5) })}> <View {...css({ marginTop: ts(5) })}>
<Button <Button
@ -73,20 +72,16 @@ export const ServerUrlPage: QueryPage = () => {
<View {...css({ flexDirection: "row", gap: ts(2) })}> <View {...css({ flexDirection: "row", gap: ts(2) })}>
<Button <Button
text={t("login.login")} text={t("login.login")}
onPress={async () => { onPress={() => {
router.replace(`/login?apiUrl=${apiUrl}`, undefined, { router.push(`/login?apiUrl=${apiUrl}`);
experimental: { nativeBehavior: "stack-replace", isNestedNavigator: false },
});
}} }}
disabled={error != null} disabled={error != null}
{...css({ flexGrow: 1, flexShrink: 1 })} {...css({ flexGrow: 1, flexShrink: 1 })}
/> />
<Button <Button
text={t("login.register")} text={t("login.register")}
onPress={async () => { onPress={() => {
router.replace(`/register?apiUrl=${apiUrl}`, undefined, { router.push(`/register?apiUrl=${apiUrl}`);
experimental: { nativeBehavior: "stack-replace", isNestedNavigator: false },
});
}} }}
disabled={error != null} disabled={error != null}
{...css({ flexGrow: 1, flexShrink: 1 })} {...css({ flexGrow: 1, flexShrink: 1 })}