mirror of
https://github.com/sussy-code/smov.git
synced 2024-12-29 16:07:40 +01:00
Add "top" part to large card, create auth input, add captcha things, put rest of auth flow in cards
Co-authored-by: William Oldham <github@binaryoverload.co.uk>
This commit is contained in:
parent
a25b3dee54
commit
a5512b95e5
14 changed files with 216 additions and 73 deletions
|
@ -27,6 +27,7 @@
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-ga4": "^2.0.0",
|
"react-ga4": "^2.0.0",
|
||||||
|
"react-google-recaptcha-v3": "^1.10.1",
|
||||||
"react-helmet-async": "^1.3.0",
|
"react-helmet-async": "^1.3.0",
|
||||||
"react-i18next": "^12.1.1",
|
"react-i18next": "^12.1.1",
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
|
|
|
@ -80,6 +80,9 @@ dependencies:
|
||||||
react-ga4:
|
react-ga4:
|
||||||
specifier: ^2.0.0
|
specifier: ^2.0.0
|
||||||
version: 2.1.0
|
version: 2.1.0
|
||||||
|
react-google-recaptcha-v3:
|
||||||
|
specifier: ^1.10.1
|
||||||
|
version: 1.10.1(react-dom@17.0.2)(react@17.0.2)
|
||||||
react-helmet-async:
|
react-helmet-async:
|
||||||
specifier: ^1.3.0
|
specifier: ^1.3.0
|
||||||
version: 1.3.0(react-dom@17.0.2)(react@17.0.2)
|
version: 1.3.0(react-dom@17.0.2)(react@17.0.2)
|
||||||
|
@ -5187,6 +5190,17 @@ packages:
|
||||||
resolution: {integrity: sha512-ZKS7PGNFqqMd3PJ6+C2Jtz/o1iU9ggiy8Y8nUeksgVuvNISbmrQtJiZNvC/TjDsqD0QlU5Wkgs7i+w9+OjHhhQ==}
|
resolution: {integrity: sha512-ZKS7PGNFqqMd3PJ6+C2Jtz/o1iU9ggiy8Y8nUeksgVuvNISbmrQtJiZNvC/TjDsqD0QlU5Wkgs7i+w9+OjHhhQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/react-google-recaptcha-v3@1.10.1(react-dom@17.0.2)(react@17.0.2):
|
||||||
|
resolution: {integrity: sha512-K3AYzSE0SasTn+XvV2tq+6YaxM+zQypk9rbCgG4OVUt7Rh4ze9basIKefoBz9sC0CNslJj9N1uwTTgRMJQbQJQ==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.3 || ^17.0 || ^18.0
|
||||||
|
react-dom: ^17.0 || ^18.0
|
||||||
|
dependencies:
|
||||||
|
hoist-non-react-statics: 3.3.2
|
||||||
|
react: 17.0.2
|
||||||
|
react-dom: 17.0.2(react@17.0.2)
|
||||||
|
dev: false
|
||||||
|
|
||||||
/react-helmet-async@1.3.0(react-dom@17.0.2)(react@17.0.2):
|
/react-helmet-async@1.3.0(react-dom@17.0.2)(react@17.0.2):
|
||||||
resolution: {integrity: sha512-9jZ57/dAn9t3q6hneQS0wukqC2ENOBgMNVEhb/ZG9ZSxUetzVIw4iAmEU38IaVg3QGYauQPhSeUTuIUtFglWpg==}
|
resolution: {integrity: sha512-9jZ57/dAn9t3q6hneQS0wukqC2ENOBgMNVEhb/ZG9ZSxUetzVIw4iAmEU38IaVg3QGYauQPhSeUTuIUtFglWpg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { Icon, Icons } from "./Icon";
|
||||||
export function PassphaseDisplay(props: { mnemonic: string }) {
|
export function PassphaseDisplay(props: { mnemonic: string }) {
|
||||||
const individualWords = props.mnemonic.split(" ");
|
const individualWords = props.mnemonic.split(" ");
|
||||||
|
|
||||||
const [_, copy] = useCopyToClipboard();
|
const [, copy] = useCopyToClipboard();
|
||||||
|
|
||||||
const [hasCopied, setHasCopied] = useState(false);
|
const [hasCopied, setHasCopied] = useState(false);
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,18 @@
|
||||||
export function LargeCard(props: { children: React.ReactNode }) {
|
export function LargeCard(props: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
top?: React.ReactNode;
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="rounded-xl bg-largeCard-background bg-opacity-50 max-w-[600px] mx-auto p-[3rem]">
|
<div className="flex flex-col items-center">
|
||||||
|
{props.top ? (
|
||||||
|
<div className="inline-block transform translate-y-1/2">
|
||||||
|
{props.top}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
<div className="w-full rounded-xl bg-largeCard-background bg-opacity-50 max-w-[600px] mx-auto p-[3rem]">
|
||||||
{props.children}
|
{props.children}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ export function Input(props: {
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
placeholder="Search"
|
placeholder="Search"
|
||||||
className="w-full py-2 px-3 pl-[calc(0.75rem+24px)] bg-video-context-inputBg rounded placeholder:text-video-context-inputPlaceholder"
|
className="w-full py-2 px-3 pl-[calc(0.75rem+24px)] focus:outline-none bg-video-context-inputBg rounded placeholder:text-video-context-inputPlaceholder"
|
||||||
value={props.value}
|
value={props.value}
|
||||||
onInput={(e) => props.onInput(e.currentTarget.value)}
|
onInput={(e) => props.onInput(e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
|
|
22
src/components/text-inputs/AuthInputBox.tsx
Normal file
22
src/components/text-inputs/AuthInputBox.tsx
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { TextInputControl } from "./TextInputControl";
|
||||||
|
|
||||||
|
export function AuthInputBox(props: {
|
||||||
|
value?: string;
|
||||||
|
label?: string;
|
||||||
|
placeholder?: string;
|
||||||
|
onChange?: (data: string) => void;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className="space-y-3">
|
||||||
|
{props.label ? (
|
||||||
|
<p className="font-bold text-white">{props.label}</p>
|
||||||
|
) : null}
|
||||||
|
<TextInputControl
|
||||||
|
value={props.value}
|
||||||
|
onChange={props.onChange}
|
||||||
|
placeholder={props.placeholder}
|
||||||
|
className="w-full flex-1 bg-authentication-inputBg px-4 py-3 text-search-text focus:outline-none rounded-lg placeholder:text-gray-700"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ import { useBackendUrl } from "@/hooks/auth/useBackendUrl";
|
||||||
import { useAuthStore } from "@/stores/auth";
|
import { useAuthStore } from "@/stores/auth";
|
||||||
|
|
||||||
export interface RegistrationData {
|
export interface RegistrationData {
|
||||||
|
recaptchaToken: string;
|
||||||
mnemonic: string;
|
mnemonic: string;
|
||||||
userData: {
|
userData: {
|
||||||
device: string;
|
device: string;
|
||||||
|
@ -89,7 +90,10 @@ export function useAuth() {
|
||||||
|
|
||||||
const register = useCallback(
|
const register = useCallback(
|
||||||
async (registerData: RegistrationData) => {
|
async (registerData: RegistrationData) => {
|
||||||
const { challenge } = await getRegisterChallengeToken(backendUrl);
|
const { challenge } = await getRegisterChallengeToken(
|
||||||
|
backendUrl,
|
||||||
|
registerData.recaptchaToken
|
||||||
|
);
|
||||||
const keys = await keysFromMnemonic(registerData.mnemonic);
|
const keys = await keysFromMnemonic(registerData.mnemonic);
|
||||||
const signature = await signChallenge(keys, challenge);
|
const signature = await signChallenge(keys, challenge);
|
||||||
const registerResult = await registerAccount(backendUrl, {
|
const registerResult = await registerAccount(backendUrl, {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { GoogleReCaptchaProvider } from "react-google-recaptcha-v3";
|
||||||
|
|
||||||
import { SubPageLayout } from "@/pages/layouts/SubPageLayout";
|
import { SubPageLayout } from "@/pages/layouts/SubPageLayout";
|
||||||
import {
|
import {
|
||||||
|
@ -8,13 +9,16 @@ import {
|
||||||
import { PassphraseGeneratePart } from "@/pages/parts/auth/PassphraseGeneratePart";
|
import { PassphraseGeneratePart } from "@/pages/parts/auth/PassphraseGeneratePart";
|
||||||
import { TrustBackendPart } from "@/pages/parts/auth/TrustBackendPart";
|
import { TrustBackendPart } from "@/pages/parts/auth/TrustBackendPart";
|
||||||
import { VerifyPassphrase } from "@/pages/parts/auth/VerifyPassphrasePart";
|
import { VerifyPassphrase } from "@/pages/parts/auth/VerifyPassphrasePart";
|
||||||
|
import { conf } from "@/setup/config";
|
||||||
|
|
||||||
export function RegisterPage() {
|
export function RegisterPage() {
|
||||||
const [step, setStep] = useState(0);
|
const [step, setStep] = useState(0);
|
||||||
const [mnemonic, setMnemonic] = useState<null | string>(null);
|
const [mnemonic, setMnemonic] = useState<null | string>(null);
|
||||||
const [account, setAccount] = useState<null | AccountProfile>(null);
|
const [account, setAccount] = useState<null | AccountProfile>(null);
|
||||||
|
const reCaptchaKey = conf().RECAPTCHA_SITE_KEY;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<GoogleReCaptchaProvider reCaptchaKey={reCaptchaKey}>
|
||||||
<SubPageLayout>
|
<SubPageLayout>
|
||||||
{step === 0 ? (
|
{step === 0 ? (
|
||||||
<TrustBackendPart
|
<TrustBackendPart
|
||||||
|
@ -25,16 +29,16 @@ export function RegisterPage() {
|
||||||
) : null}
|
) : null}
|
||||||
{step === 1 ? (
|
{step === 1 ? (
|
||||||
<PassphraseGeneratePart
|
<PassphraseGeneratePart
|
||||||
onNext={(n) => {
|
onNext={(m) => {
|
||||||
setMnemonic(n);
|
setMnemonic(m);
|
||||||
setStep(2);
|
setStep(2);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{step === 2 ? (
|
{step === 2 ? (
|
||||||
<AccountCreatePart
|
<AccountCreatePart
|
||||||
onNext={(v) => {
|
onNext={(a) => {
|
||||||
setAccount(v);
|
setAccount(a);
|
||||||
setStep(3);
|
setStep(3);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -50,5 +54,6 @@ export function RegisterPage() {
|
||||||
) : null}
|
) : null}
|
||||||
{step === 4 ? <p>Success, account now exists</p> : null}
|
{step === 4 ? <p>Success, account now exists</p> : null}
|
||||||
</SubPageLayout>
|
</SubPageLayout>
|
||||||
|
</GoogleReCaptchaProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
import { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
|
|
||||||
import { Button } from "@/components/Button";
|
import { Button } from "@/components/Button";
|
||||||
import { Input } from "@/components/player/internals/ContextMenu/Input";
|
import { Icon, Icons } from "@/components/Icon";
|
||||||
|
import {
|
||||||
|
LargeCard,
|
||||||
|
LargeCardButtons,
|
||||||
|
LargeCardText,
|
||||||
|
} from "@/components/layout/LargeCard";
|
||||||
|
import { AuthInputBox } from "@/components/text-inputs/AuthInputBox";
|
||||||
|
|
||||||
export interface AccountProfile {
|
export interface AccountProfile {
|
||||||
device: string;
|
device: string;
|
||||||
account: string;
|
|
||||||
profile: {
|
profile: {
|
||||||
colorA: string;
|
colorA: string;
|
||||||
colorB: string;
|
colorB: string;
|
||||||
|
@ -18,13 +23,11 @@ interface AccountCreatePartProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AccountCreatePart(props: AccountCreatePartProps) {
|
export function AccountCreatePart(props: AccountCreatePartProps) {
|
||||||
const [account, setAccount] = useState("");
|
|
||||||
const [device, setDevice] = useState("");
|
const [device, setDevice] = useState("");
|
||||||
// TODO validate device and account before next step
|
// TODO validate device and account before next step
|
||||||
|
|
||||||
const nextStep = useCallback(() => {
|
const nextStep = useCallback(() => {
|
||||||
props.onNext?.({
|
props.onNext?.({
|
||||||
account,
|
|
||||||
device,
|
device,
|
||||||
profile: {
|
profile: {
|
||||||
colorA: "#fff",
|
colorA: "#fff",
|
||||||
|
@ -32,15 +35,27 @@ export function AccountCreatePart(props: AccountCreatePartProps) {
|
||||||
icon: "brush",
|
icon: "brush",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}, [account, device, props]);
|
}, [device, props]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<LargeCard>
|
||||||
<p>Account name</p>
|
<LargeCardText
|
||||||
<Input value={account} onInput={setAccount} />
|
icon={<Icon icon={Icons.USER} />}
|
||||||
<p>Device name</p>
|
title="Account information"
|
||||||
<Input value={device} onInput={setDevice} />
|
>
|
||||||
<Button onClick={() => nextStep()}>Next</Button>
|
Set up your account.... OR ELSE!
|
||||||
</div>
|
</LargeCardText>
|
||||||
|
<AuthInputBox
|
||||||
|
label="Device name"
|
||||||
|
value={device}
|
||||||
|
onChange={setDevice}
|
||||||
|
placeholder="Muad'Dib's Nintendo Switch"
|
||||||
|
/>
|
||||||
|
<LargeCardButtons>
|
||||||
|
<Button theme="purple" onClick={() => nextStep()}>
|
||||||
|
Next
|
||||||
|
</Button>
|
||||||
|
</LargeCardButtons>
|
||||||
|
</LargeCard>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,13 @@ import { useAsyncFn } from "react-use";
|
||||||
|
|
||||||
import { verifyValidMnemonic } from "@/backend/accounts/crypto";
|
import { verifyValidMnemonic } from "@/backend/accounts/crypto";
|
||||||
import { Button } from "@/components/Button";
|
import { Button } from "@/components/Button";
|
||||||
import { Input } from "@/components/player/internals/ContextMenu/Input";
|
import { BrandPill } from "@/components/layout/BrandPill";
|
||||||
|
import {
|
||||||
|
LargeCard,
|
||||||
|
LargeCardButtons,
|
||||||
|
LargeCardText,
|
||||||
|
} from "@/components/layout/LargeCard";
|
||||||
|
import { AuthInputBox } from "@/components/text-inputs/AuthInputBox";
|
||||||
import { useAuth } from "@/hooks/auth/useAuth";
|
import { useAuth } from "@/hooks/auth/useAuth";
|
||||||
|
|
||||||
interface LoginFormPartProps {
|
interface LoginFormPartProps {
|
||||||
|
@ -39,14 +45,38 @@ export function LoginFormPart(props: LoginFormPartProps) {
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<LargeCard top={<BrandPill />}>
|
||||||
<p>passphrase</p>
|
<LargeCardText title="Login to your account">
|
||||||
<Input value={mnemonic} onInput={setMnemonic} />
|
Oh, you're asking for the key to my top-secret lair, also known as
|
||||||
<p>Device name</p>
|
The Fortress of Wordsmithery, accessed only by reciting the sacred
|
||||||
<Input value={device} onInput={setDevice} />
|
incantation of the 12-word passphrase!
|
||||||
|
</LargeCardText>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<AuthInputBox
|
||||||
|
label="12-Word Passphrase"
|
||||||
|
value={mnemonic}
|
||||||
|
onChange={setMnemonic}
|
||||||
|
placeholder="Passphrase"
|
||||||
|
/>
|
||||||
|
<AuthInputBox
|
||||||
|
label="Device name"
|
||||||
|
value={device}
|
||||||
|
onChange={setDevice}
|
||||||
|
placeholder="Device"
|
||||||
|
/>
|
||||||
{result.loading ? <p>Loading...</p> : null}
|
{result.loading ? <p>Loading...</p> : null}
|
||||||
{result.error ? <p>error: {result.error.toString()}</p> : null}
|
{result.error && !result.loading ? (
|
||||||
<Button onClick={() => execute(mnemonic, device)}>Login</Button>
|
<p className="text-authentication-errorText">
|
||||||
|
{result.error.message}
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<LargeCardButtons>
|
||||||
|
<Button theme="purple" onClick={() => execute(mnemonic, device)}>
|
||||||
|
LET ME IN!
|
||||||
|
</Button>
|
||||||
|
</LargeCardButtons>
|
||||||
|
</LargeCard>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,15 @@
|
||||||
import { useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
import { GoogleReCaptcha, useGoogleReCaptcha } from "react-google-recaptcha-v3";
|
||||||
import { useAsyncFn } from "react-use";
|
import { useAsyncFn } from "react-use";
|
||||||
|
|
||||||
import { Button } from "@/components/Button";
|
import { Button } from "@/components/Button";
|
||||||
import { Input } from "@/components/player/internals/ContextMenu/Input";
|
import { Icon, Icons } from "@/components/Icon";
|
||||||
|
import {
|
||||||
|
LargeCard,
|
||||||
|
LargeCardButtons,
|
||||||
|
LargeCardText,
|
||||||
|
} from "@/components/layout/LargeCard";
|
||||||
|
import { AuthInputBox } from "@/components/text-inputs/AuthInputBox";
|
||||||
import { useAuth } from "@/hooks/auth/useAuth";
|
import { useAuth } from "@/hooks/auth/useAuth";
|
||||||
import { AccountProfile } from "@/pages/parts/auth/AccountCreatePart";
|
import { AccountProfile } from "@/pages/parts/auth/AccountCreatePart";
|
||||||
|
|
||||||
|
@ -16,10 +23,17 @@ export function VerifyPassphrase(props: VerifyPassphraseProps) {
|
||||||
const [mnemonic, setMnemonic] = useState("");
|
const [mnemonic, setMnemonic] = useState("");
|
||||||
const { register, restore } = useAuth();
|
const { register, restore } = useAuth();
|
||||||
|
|
||||||
|
const { executeRecaptcha } = useGoogleReCaptcha();
|
||||||
|
|
||||||
const [result, execute] = useAsyncFn(
|
const [result, execute] = useAsyncFn(
|
||||||
async (inputMnemonic: string) => {
|
async (inputMnemonic: string) => {
|
||||||
|
const recaptchaToken = executeRecaptcha
|
||||||
|
? await executeRecaptcha()
|
||||||
|
: undefined;
|
||||||
|
|
||||||
if (!props.mnemonic || !props.userData)
|
if (!props.mnemonic || !props.userData)
|
||||||
throw new Error("invalid input data");
|
throw new Error("Data is not valid");
|
||||||
|
if (!recaptchaToken) throw new Error("ReCaptcha validation failed");
|
||||||
if (inputMnemonic !== props.mnemonic)
|
if (inputMnemonic !== props.mnemonic)
|
||||||
throw new Error("Passphrase doesn't match");
|
throw new Error("Passphrase doesn't match");
|
||||||
|
|
||||||
|
@ -28,6 +42,7 @@ export function VerifyPassphrase(props: VerifyPassphraseProps) {
|
||||||
await register({
|
await register({
|
||||||
mnemonic: inputMnemonic,
|
mnemonic: inputMnemonic,
|
||||||
userData: props.userData,
|
userData: props.userData,
|
||||||
|
recaptchaToken,
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO import (and sort out conflicts)
|
// TODO import (and sort out conflicts)
|
||||||
|
@ -40,12 +55,29 @@ export function VerifyPassphrase(props: VerifyPassphraseProps) {
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<LargeCard>
|
||||||
<p>verify passphrase</p>
|
<LargeCardText
|
||||||
<Input value={mnemonic} onInput={setMnemonic} />
|
icon={<Icon icon={Icons.CIRCLE_CHECK} />}
|
||||||
{result.loading ? <p>Loading...</p> : null}
|
title="Enter your passphrase"
|
||||||
{result.error ? <p>error: {result.error.toString()}</p> : null}
|
>
|
||||||
<Button onClick={() => execute(mnemonic)}>Register</Button>
|
If you've already lost it, how will you ever be able to take care
|
||||||
</div>
|
of a child?
|
||||||
|
</LargeCardText>
|
||||||
|
<AuthInputBox
|
||||||
|
label="Your passphrase"
|
||||||
|
value={mnemonic}
|
||||||
|
onChange={setMnemonic}
|
||||||
|
/>
|
||||||
|
{result.error ? (
|
||||||
|
<p className="mt-3 text-authentication-errorText">
|
||||||
|
{result.error.message}
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
|
<LargeCardButtons>
|
||||||
|
<Button theme="purple" onClick={() => execute(mnemonic)}>
|
||||||
|
Register
|
||||||
|
</Button>
|
||||||
|
</LargeCardButtons>
|
||||||
|
</LargeCard>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ interface Config {
|
||||||
CORS_PROXY_URL: string;
|
CORS_PROXY_URL: string;
|
||||||
NORMAL_ROUTER: boolean;
|
NORMAL_ROUTER: boolean;
|
||||||
BACKEND_URL: string;
|
BACKEND_URL: string;
|
||||||
|
RECAPTCHA_SITE_KEY: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RuntimeConfig {
|
export interface RuntimeConfig {
|
||||||
|
@ -18,6 +19,7 @@ export interface RuntimeConfig {
|
||||||
NORMAL_ROUTER: boolean;
|
NORMAL_ROUTER: boolean;
|
||||||
PROXY_URLS: string[];
|
PROXY_URLS: string[];
|
||||||
BACKEND_URL: string;
|
BACKEND_URL: string;
|
||||||
|
RECAPTCHA_SITE_KEY: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const env: Record<keyof Config, undefined | string> = {
|
const env: Record<keyof Config, undefined | string> = {
|
||||||
|
@ -28,6 +30,7 @@ const env: Record<keyof Config, undefined | string> = {
|
||||||
CORS_PROXY_URL: import.meta.env.VITE_CORS_PROXY_URL,
|
CORS_PROXY_URL: import.meta.env.VITE_CORS_PROXY_URL,
|
||||||
NORMAL_ROUTER: import.meta.env.VITE_NORMAL_ROUTER,
|
NORMAL_ROUTER: import.meta.env.VITE_NORMAL_ROUTER,
|
||||||
BACKEND_URL: import.meta.env.VITE_BACKEND_URL,
|
BACKEND_URL: import.meta.env.VITE_BACKEND_URL,
|
||||||
|
RECAPTCHA_SITE_KEY: import.meta.env.VITE_RECAPTCHA_SITE_KEY,
|
||||||
};
|
};
|
||||||
|
|
||||||
// loads from different locations, in order: environment (VITE_{KEY}), window (public/config.js)
|
// loads from different locations, in order: environment (VITE_{KEY}), window (public/config.js)
|
||||||
|
@ -53,5 +56,6 @@ export function conf(): RuntimeConfig {
|
||||||
.split(",")
|
.split(",")
|
||||||
.map((v) => v.trim()),
|
.map((v) => v.trim()),
|
||||||
NORMAL_ROUTER: getKey("NORMAL_ROUTER", "false") === "true",
|
NORMAL_ROUTER: getKey("NORMAL_ROUTER", "false") === "true",
|
||||||
|
RECAPTCHA_SITE_KEY: getKey("RECAPTCHA_SITE_KEY"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -210,3 +210,7 @@ input[type=range].styled-slider.slider-progress::-ms-fill-lower {
|
||||||
/* For some reason the styles don't get applied without the width */
|
/* For some reason the styles don't get applied without the width */
|
||||||
width: 13px;
|
width: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.grecaptcha-badge {
|
||||||
|
display: none !important;
|
||||||
|
}
|
|
@ -120,9 +120,11 @@ module.exports = {
|
||||||
// Passphrase
|
// Passphrase
|
||||||
authentication: {
|
authentication: {
|
||||||
border: "#393954",
|
border: "#393954",
|
||||||
|
inputBg: "#171728",
|
||||||
wordBackground: "#171728",
|
wordBackground: "#171728",
|
||||||
copyText: "#58587A",
|
copyText: "#58587A",
|
||||||
copyTextHover: "#8888AA",
|
copyTextHover: "#8888AA",
|
||||||
|
errorText: "#DB3D62",
|
||||||
},
|
},
|
||||||
|
|
||||||
// Settings page
|
// Settings page
|
||||||
|
|
Loading…
Reference in a new issue