mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-24 02:02:36 -04:00
Rework apiurl handling to allow guests login on android
This commit is contained in:
parent
158058b720
commit
d52cc045e0
@ -42,7 +42,7 @@ import {
|
|||||||
UserP,
|
UserP,
|
||||||
useUserTheme,
|
useUserTheme,
|
||||||
} from "@kyoo/models";
|
} from "@kyoo/models";
|
||||||
import { Component, ComponentType, useContext, useState } from "react";
|
import { ComponentType, useContext, useState } from "react";
|
||||||
import NextApp, { AppContext, type AppProps } from "next/app";
|
import NextApp, { AppContext, type AppProps } from "next/app";
|
||||||
import { Poppins } from "next/font/google";
|
import { Poppins } from "next/font/google";
|
||||||
import { useTheme, useMobileHover, useStyleRegistry, StyleRegistryProvider } from "yoshiki/web";
|
import { useTheme, useMobileHover, useStyleRegistry, StyleRegistryProvider } from "yoshiki/web";
|
||||||
|
@ -19,14 +19,15 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { ReactNode, createContext, useContext, useEffect, useMemo, useRef } from "react";
|
import { ReactNode, createContext, useContext, useEffect, useMemo, useRef } from "react";
|
||||||
import { User, UserP } from "./resources";
|
import { ServerInfoP, User, UserP } from "./resources";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { zdate } from "./utils";
|
import { zdate } from "./utils";
|
||||||
import { removeAccounts, setCookie, updateAccount } from "./account-internal";
|
import { removeAccounts, setCookie, updateAccount } from "./account-internal";
|
||||||
import { useMMKVString } from "react-native-mmkv";
|
import { useMMKVString } from "react-native-mmkv";
|
||||||
import { Platform } from "react-native";
|
import { Platform } from "react-native";
|
||||||
import { useFetch } from "./query";
|
|
||||||
import { useQueryClient } from "@tanstack/react-query";
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
|
import { atom, getDefaultStore, useAtomValue, useSetAtom } from "jotai";
|
||||||
|
import { useFetch, } from "./query";
|
||||||
import { KyooErrors } from "./kyoo-errors";
|
import { KyooErrors } from "./kyoo-errors";
|
||||||
|
|
||||||
export const TokenP = z.object({
|
export const TokenP = z.object({
|
||||||
@ -49,6 +50,16 @@ export const AccountP = UserP.and(
|
|||||||
);
|
);
|
||||||
export type Account = z.infer<typeof AccountP>;
|
export type Account = z.infer<typeof AccountP>;
|
||||||
|
|
||||||
|
const defaultApiUrl = Platform.OS === "web" ? "/api" : null;
|
||||||
|
const currentApiUrl = atom<string | null>(defaultApiUrl);
|
||||||
|
export const getCurrentApiUrl = () => {
|
||||||
|
const store = getDefaultStore();
|
||||||
|
return store.get(currentApiUrl);
|
||||||
|
};
|
||||||
|
export const useCurrentApiUrl = () => {
|
||||||
|
return useAtomValue(currentApiUrl);
|
||||||
|
};
|
||||||
|
|
||||||
const AccountContext = createContext<(Account & { select: () => void; remove: () => void })[]>([]);
|
const AccountContext = createContext<(Account & { select: () => void; remove: () => void })[]>([]);
|
||||||
export const ConnectionErrorContext = createContext<{
|
export const ConnectionErrorContext = createContext<{
|
||||||
error: KyooErrors | null;
|
error: KyooErrors | null;
|
||||||
@ -66,10 +77,14 @@ export const AccountProvider = ({
|
|||||||
ssrAccount?: Account;
|
ssrAccount?: Account;
|
||||||
ssrError?: KyooErrors;
|
ssrError?: KyooErrors;
|
||||||
}) => {
|
}) => {
|
||||||
|
const setApiUrl = useSetAtom(currentApiUrl);
|
||||||
if (Platform.OS === "web" && typeof window === "undefined") {
|
if (Platform.OS === "web" && typeof window === "undefined") {
|
||||||
const accs = ssrAccount
|
const accs = ssrAccount
|
||||||
? [{ ...ssrAccount, selected: true, select: () => {}, remove: () => {} }]
|
? [{ ...ssrAccount, selected: true, select: () => {}, remove: () => {} }]
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
|
setApiUrl(process.env.KYOO_URL ?? "http://localhost:5000");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AccountContext.Provider value={accs}>
|
<AccountContext.Provider value={accs}>
|
||||||
<ConnectionErrorContext.Provider
|
<ConnectionErrorContext.Provider
|
||||||
@ -103,6 +118,10 @@ export const AccountProvider = ({
|
|||||||
|
|
||||||
// update user's data from kyoo un startup, it could have changed.
|
// update user's data from kyoo un startup, it could have changed.
|
||||||
const selected = useMemo(() => accounts.find((x) => x.selected), [accounts]);
|
const selected = useMemo(() => accounts.find((x) => x.selected), [accounts]);
|
||||||
|
useEffect(() => {
|
||||||
|
setApiUrl(selected?.apiUrl ?? defaultApiUrl);
|
||||||
|
}, [selected, setApiUrl]);
|
||||||
|
|
||||||
const user = useFetch({
|
const user = useFetch({
|
||||||
path: ["auth", "me"],
|
path: ["auth", "me"],
|
||||||
parser: UserP,
|
parser: UserP,
|
||||||
@ -130,6 +149,7 @@ export const AccountProvider = ({
|
|||||||
// update cookies for ssr (needs to contains token, theme, language...)
|
// update cookies for ssr (needs to contains token, theme, language...)
|
||||||
if (Platform.OS === "web") {
|
if (Platform.OS === "web") {
|
||||||
setCookie("account", selected);
|
setCookie("account", selected);
|
||||||
|
// cookie used for images and videos since we can't add Authorization headers in img or video tags.
|
||||||
setCookie("X-Bearer", selected?.token.access_token);
|
setCookie("X-Bearer", selected?.token.access_token);
|
||||||
}
|
}
|
||||||
}, [selected, queryClient]);
|
}, [selected, queryClient]);
|
||||||
@ -162,10 +182,14 @@ export const useAccounts = () => {
|
|||||||
|
|
||||||
export const useHasPermission = (perms?: string[]) => {
|
export const useHasPermission = (perms?: string[]) => {
|
||||||
const account = useAccount();
|
const account = useAccount();
|
||||||
|
const { data } = useFetch({
|
||||||
|
path: ["info"],
|
||||||
|
parser: ServerInfoP,
|
||||||
|
});
|
||||||
|
|
||||||
if (!perms || !perms[0]) return true;
|
if (!perms || !perms[0]) return true;
|
||||||
|
|
||||||
// TODO: Read permission of guest account here.
|
const available = account?.permissions ?? data?.guestPermissions;
|
||||||
if (!account) return false;
|
if (!available) return false;
|
||||||
return perms.every((perm) => account.permissions.includes(perm));
|
return perms.every((perm) => available.includes(perm));
|
||||||
};
|
};
|
||||||
|
@ -20,14 +20,9 @@
|
|||||||
|
|
||||||
import { queryFn } from "./query";
|
import { queryFn } from "./query";
|
||||||
import { KyooErrors } from "./kyoo-errors";
|
import { KyooErrors } from "./kyoo-errors";
|
||||||
import { Account, TokenP } from "./accounts";
|
import { Account, TokenP, getCurrentApiUrl } from "./accounts";
|
||||||
import { UserP } from "./resources";
|
import { UserP } from "./resources";
|
||||||
import {
|
import { addAccount, getCurrentAccount, removeAccounts, updateAccount } from "./account-internal";
|
||||||
addAccount,
|
|
||||||
getCurrentAccount,
|
|
||||||
removeAccounts,
|
|
||||||
updateAccount,
|
|
||||||
} from "./account-internal";
|
|
||||||
import { Platform } from "react-native";
|
import { Platform } from "react-native";
|
||||||
|
|
||||||
type Result<A, B> =
|
type Result<A, B> =
|
||||||
@ -38,6 +33,7 @@ export const login = async (
|
|||||||
action: "register" | "login",
|
action: "register" | "login",
|
||||||
{ apiUrl, ...body }: { username: string; password: string; email?: string; apiUrl?: string },
|
{ apiUrl, ...body }: { username: string; password: string; email?: string; apiUrl?: string },
|
||||||
): Promise<Result<Account, string>> => {
|
): Promise<Result<Account, string>> => {
|
||||||
|
apiUrl ??= getCurrentApiUrl()!;
|
||||||
try {
|
try {
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
setTimeout(() => controller.abort(), 5_000);
|
setTimeout(() => controller.abort(), 5_000);
|
||||||
@ -57,7 +53,7 @@ export const login = async (
|
|||||||
UserP,
|
UserP,
|
||||||
`Bearer ${token.access_token}`,
|
`Bearer ${token.access_token}`,
|
||||||
);
|
);
|
||||||
const account: Account = { ...user, apiUrl: apiUrl ?? "/api", token, selected: true };
|
const account: Account = { ...user, apiUrl: apiUrl, token, selected: true };
|
||||||
addAccount(account);
|
addAccount(account);
|
||||||
return { ok: true, value: account };
|
return { ok: true, value: account };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -67,6 +63,7 @@ export const login = async (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const oidcLogin = async (provider: string, code: string, apiUrl?: string) => {
|
export const oidcLogin = async (provider: string, code: string, apiUrl?: string) => {
|
||||||
|
apiUrl ??= getCurrentApiUrl()!;
|
||||||
try {
|
try {
|
||||||
const token = await queryFn(
|
const token = await queryFn(
|
||||||
{
|
{
|
||||||
@ -82,7 +79,7 @@ export const oidcLogin = async (provider: string, code: string, apiUrl?: string)
|
|||||||
UserP,
|
UserP,
|
||||||
`Bearer ${token.access_token}`,
|
`Bearer ${token.access_token}`,
|
||||||
);
|
);
|
||||||
const account: Account = { ...user, apiUrl: apiUrl ?? "/api", token, selected: true };
|
const account: Account = { ...user, apiUrl: apiUrl, token, selected: true };
|
||||||
addAccount(account);
|
addAccount(account);
|
||||||
return { ok: true, value: account };
|
return { ok: true, value: account };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -29,14 +29,8 @@ import {
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { KyooErrors } from "./kyoo-errors";
|
import { KyooErrors } from "./kyoo-errors";
|
||||||
import { Page, Paged } from "./page";
|
import { Page, Paged } from "./page";
|
||||||
import { Platform } from "react-native";
|
|
||||||
import { getToken } from "./login";
|
import { getToken } from "./login";
|
||||||
import { getCurrentAccount } from "./account-internal";
|
import { getCurrentApiUrl } from ".";
|
||||||
|
|
||||||
const kyooUrl =
|
|
||||||
typeof window === "undefined" ? process.env.KYOO_URL ?? "http://localhost:5000" : "/api";
|
|
||||||
// The url of kyoo, set after each query (used by the image parser).
|
|
||||||
export let kyooApiUrl = kyooUrl;
|
|
||||||
|
|
||||||
export const queryFn = async <Parser extends z.ZodTypeAny>(
|
export const queryFn = async <Parser extends z.ZodTypeAny>(
|
||||||
context: {
|
context: {
|
||||||
@ -55,9 +49,7 @@ 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 ?? (Platform.OS === "web" ? kyooUrl : getCurrentAccount()?.apiUrl);
|
const url = context.apiUrl ?? getCurrentApiUrl();
|
||||||
kyooApiUrl = url;
|
|
||||||
|
|
||||||
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(
|
||||||
|
@ -20,11 +20,12 @@
|
|||||||
|
|
||||||
import { Platform } from "react-native";
|
import { Platform } from "react-native";
|
||||||
import { ZodObject, ZodRawShape, z } from "zod";
|
import { ZodObject, ZodRawShape, z } from "zod";
|
||||||
import { kyooApiUrl } from "..";
|
import { getCurrentApiUrl } from "..";
|
||||||
|
|
||||||
export const imageFn = (url: string) => (Platform.OS === "web" ? `/api${url}` : kyooApiUrl + url);
|
export const imageFn = (url: string) =>
|
||||||
|
Platform.OS === "web" ? `/api${url}` : `${getCurrentApiUrl()!}${url}`;
|
||||||
|
|
||||||
export const baseAppUrl = () => Platform.OS === "web" ? window.location.origin : "kyoo://";
|
export const baseAppUrl = () => (Platform.OS === "web" ? window.location.origin : "kyoo://");
|
||||||
|
|
||||||
export const Img = z.object({
|
export const Img = z.object({
|
||||||
source: z.string(),
|
source: z.string(),
|
||||||
|
@ -20,9 +20,8 @@
|
|||||||
|
|
||||||
import { login, QueryPage } from "@kyoo/models";
|
import { login, QueryPage } from "@kyoo/models";
|
||||||
import { Button, P, Input, ts, H1, A } from "@kyoo/primitives";
|
import { Button, P, Input, ts, H1, A } from "@kyoo/primitives";
|
||||||
import { useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Platform } from "react-native";
|
|
||||||
import { Trans } from "react-i18next";
|
import { Trans } from "react-i18next";
|
||||||
import { useRouter } from "solito/router";
|
import { useRouter } from "solito/router";
|
||||||
import { percent, px, useYoshiki } from "yoshiki/native";
|
import { percent, px, useYoshiki } from "yoshiki/native";
|
||||||
@ -30,8 +29,12 @@ import { DefaultLayout } from "../layout";
|
|||||||
import { FormPage } from "./form";
|
import { FormPage } from "./form";
|
||||||
import { PasswordInput } from "./password-input";
|
import { PasswordInput } from "./password-input";
|
||||||
import { OidcLogin } from "./oidc";
|
import { OidcLogin } from "./oidc";
|
||||||
|
import { Platform } from "react-native";
|
||||||
|
|
||||||
export const LoginPage: QueryPage<{ error?: string }> = ({ error: initialError }) => {
|
export const LoginPage: QueryPage<{ apiUrl?: string; error?: string }> = ({
|
||||||
|
apiUrl,
|
||||||
|
error: initialError,
|
||||||
|
}) => {
|
||||||
const [username, setUsername] = useState("");
|
const [username, setUsername] = useState("");
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
const [error, setError] = useState<string | undefined>(initialError);
|
const [error, setError] = useState<string | undefined>(initialError);
|
||||||
@ -40,10 +43,17 @@ export const LoginPage: QueryPage<{ error?: string }> = ({ error: initialError }
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { css } = useYoshiki();
|
const { css } = useYoshiki();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!apiUrl && Platform.OS !== "web")
|
||||||
|
router.replace("/server-url", undefined, {
|
||||||
|
experimental: { nativeBehavior: "stack-replace", isNestedNavigator: false },
|
||||||
|
});
|
||||||
|
}, [apiUrl, router]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormPage>
|
<FormPage>
|
||||||
<H1>{t("login.login")}</H1>
|
<H1>{t("login.login")}</H1>
|
||||||
<OidcLogin />
|
<OidcLogin apiUrl={apiUrl} />
|
||||||
<P {...css({ paddingLeft: ts(1) })}>{t("login.username")}</P>
|
<P {...css({ paddingLeft: ts(1) })}>{t("login.username")}</P>
|
||||||
<Input autoComplete="username" variant="big" onChangeText={(value) => setUsername(value)} />
|
<Input autoComplete="username" variant="big" onChangeText={(value) => setUsername(value)} />
|
||||||
<P {...css({ paddingLeft: ts(1) })}>{t("login.password")}</P>
|
<P {...css({ paddingLeft: ts(1) })}>{t("login.password")}</P>
|
||||||
@ -59,7 +69,7 @@ export const LoginPage: QueryPage<{ error?: string }> = ({ error: initialError }
|
|||||||
const { error } = await login("login", {
|
const { error } = await login("login", {
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
apiUrl: cleanApiUrl(apiUrl),
|
apiUrl,
|
||||||
});
|
});
|
||||||
setError(error);
|
setError(error);
|
||||||
if (error) return;
|
if (error) return;
|
||||||
|
@ -34,10 +34,10 @@ import { useEffect, useRef } from "react";
|
|||||||
import { useRouter } from "solito/router";
|
import { useRouter } from "solito/router";
|
||||||
import { ErrorView } from "../errors";
|
import { ErrorView } from "../errors";
|
||||||
|
|
||||||
export const OidcLogin = () => {
|
export const OidcLogin = ({ apiUrl }: { apiUrl?: string }) => {
|
||||||
const { css } = useYoshiki();
|
const { css } = useYoshiki();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { data, error } = useFetch(OidcLogin.query());
|
const { data, error } = useFetch({ options: { apiUrl }, ...OidcLogin.query() });
|
||||||
|
|
||||||
const btn = css({ width: { xs: percent(100), sm: percent(75) }, marginY: ts(1) });
|
const btn = css({ width: { xs: percent(100), sm: percent(75) }, marginY: ts(1) });
|
||||||
|
|
||||||
@ -48,7 +48,7 @@ export const OidcLogin = () => {
|
|||||||
) : data ? (
|
) : data ? (
|
||||||
Object.values(data.oidc).map((x) => (
|
Object.values(data.oidc).map((x) => (
|
||||||
<Button
|
<Button
|
||||||
href={x.link}
|
href={apiUrl ? `${x.link}&apiUrl=${apiUrl}` : x.link}
|
||||||
key={x.displayName}
|
key={x.displayName}
|
||||||
licon={
|
licon={
|
||||||
x.logoUrl != null && (
|
x.logoUrl != null && (
|
||||||
@ -90,11 +90,12 @@ OidcLogin.query = (): QueryIdentifier<ServerInfo> => ({
|
|||||||
parser: ServerInfoP,
|
parser: ServerInfoP,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const OidcCallbackPage: QueryPage<{ provider: string; code: string; error?: string }> = ({
|
export const OidcCallbackPage: QueryPage<{
|
||||||
provider,
|
apiUrl?: string;
|
||||||
code,
|
provider: string;
|
||||||
error,
|
code: string;
|
||||||
}) => {
|
error?: string;
|
||||||
|
}> = ({ apiUrl, provider, code, error }) => {
|
||||||
const hasRun = useRef(false);
|
const hasRun = useRef(false);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@ -103,12 +104,12 @@ export const OidcCallbackPage: QueryPage<{ provider: string; code: string; error
|
|||||||
hasRun.current = true;
|
hasRun.current = true;
|
||||||
|
|
||||||
function onError(error: string) {
|
function onError(error: string) {
|
||||||
router.replace(`/login?error=${error}`, undefined, {
|
router.replace(`/login?error=${error}${apiUrl ? `&apiUrl=${apiUrl}` : ""}`, undefined, {
|
||||||
experimental: { nativeBehavior: "stack-replace", isNestedNavigator: false },
|
experimental: { nativeBehavior: "stack-replace", isNestedNavigator: false },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async function run() {
|
async function run() {
|
||||||
const { error: loginError } = await oidcLogin(provider, code);
|
const { error: loginError } = await oidcLogin(provider, code, apiUrl);
|
||||||
if (loginError) onError(loginError);
|
if (loginError) onError(loginError);
|
||||||
else {
|
else {
|
||||||
router.replace("/", undefined, {
|
router.replace("/", undefined, {
|
||||||
@ -119,6 +120,6 @@ export const OidcCallbackPage: QueryPage<{ provider: string; code: string; error
|
|||||||
|
|
||||||
if (error) onError(error);
|
if (error) onError(error);
|
||||||
else run();
|
else run();
|
||||||
}, [provider, code, router, error]);
|
}, [provider, code, apiUrl, router, error]);
|
||||||
return <P>{"Loading"}</P>;
|
return <P>{"Loading"}</P>;
|
||||||
};
|
};
|
||||||
|
@ -20,9 +20,8 @@
|
|||||||
|
|
||||||
import { login, QueryPage } from "@kyoo/models";
|
import { login, QueryPage } from "@kyoo/models";
|
||||||
import { Button, P, Input, ts, H1, A } from "@kyoo/primitives";
|
import { Button, P, Input, ts, H1, A } from "@kyoo/primitives";
|
||||||
import { useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Platform } from "react-native";
|
|
||||||
import { Trans } from "react-i18next";
|
import { Trans } from "react-i18next";
|
||||||
import { useRouter } from "solito/router";
|
import { useRouter } from "solito/router";
|
||||||
import { percent, px, useYoshiki } from "yoshiki/native";
|
import { percent, px, useYoshiki } from "yoshiki/native";
|
||||||
@ -30,8 +29,9 @@ import { DefaultLayout } from "../layout";
|
|||||||
import { FormPage } from "./form";
|
import { FormPage } from "./form";
|
||||||
import { PasswordInput } from "./password-input";
|
import { PasswordInput } from "./password-input";
|
||||||
import { OidcLogin } from "./oidc";
|
import { OidcLogin } from "./oidc";
|
||||||
|
import { Platform } from "react-native";
|
||||||
|
|
||||||
export const RegisterPage: QueryPage = () => {
|
export const RegisterPage: QueryPage<{ apiUrl?: string }> = ({ apiUrl }) => {
|
||||||
const [email, setEmail] = useState("");
|
const [email, setEmail] = useState("");
|
||||||
const [username, setUsername] = useState("");
|
const [username, setUsername] = useState("");
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
@ -42,17 +42,17 @@ export const RegisterPage: QueryPage = () => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { css } = useYoshiki();
|
const { css } = useYoshiki();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!apiUrl && Platform.OS !== "web")
|
||||||
|
router.replace("/server-url", undefined, {
|
||||||
|
experimental: { nativeBehavior: "stack-replace", isNestedNavigator: false },
|
||||||
|
});
|
||||||
|
}, [apiUrl, router]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormPage>
|
<FormPage>
|
||||||
<H1>{t("login.register")}</H1>
|
<H1>{t("login.register")}</H1>
|
||||||
<OidcLogin />
|
<OidcLogin apiUrl={apiUrl} />
|
||||||
{Platform.OS !== "web" && (
|
|
||||||
<>
|
|
||||||
<P {...css({ paddingLeft: ts(1) })}>{t("login.server")}</P>
|
|
||||||
<Input variant="big" onChangeText={setApiUrl} />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<P {...css({ paddingLeft: ts(1) })}>{t("login.username")}</P>
|
<P {...css({ paddingLeft: ts(1) })}>{t("login.username")}</P>
|
||||||
<Input autoComplete="username" variant="big" onChangeText={(value) => setUsername(value)} />
|
<Input autoComplete="username" variant="big" onChangeText={(value) => setUsername(value)} />
|
||||||
|
|
||||||
@ -81,10 +81,7 @@ export const RegisterPage: QueryPage = () => {
|
|||||||
text={t("login.register")}
|
text={t("login.register")}
|
||||||
disabled={password !== confirm}
|
disabled={password !== confirm}
|
||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
const { error } = await login(
|
const { error } = await login("register", { email, username, password, apiUrl });
|
||||||
"register",
|
|
||||||
{ email, username, password, apiUrl: cleanApiUrl(apiUrl) },
|
|
||||||
);
|
|
||||||
setError(error);
|
setError(error);
|
||||||
if (error) return;
|
if (error) return;
|
||||||
router.replace("/", undefined, {
|
router.replace("/", undefined, {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user