mirror of
https://github.com/movie-web/movie-web.git
synced 2024-11-11 04:55:07 +01:00
finish register and login flow + suspense fallback fix
Co-authored-by: Jip Frijlink <JipFr@users.noreply.github.com>
This commit is contained in:
parent
567c6a3894
commit
061c944034
@ -1,4 +1,4 @@
|
||||
import { Icon, Icons } from "@/components/Icon";
|
||||
import { UserIcon } from "@/components/UserIcon";
|
||||
import { AccountProfile } from "@/pages/parts/auth/AccountCreatePart";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
|
||||
@ -6,13 +6,7 @@ export interface AvatarProps {
|
||||
profile: AccountProfile["profile"];
|
||||
}
|
||||
|
||||
const possibleIcons = ["bookmark"] as const;
|
||||
const avatarIconMap: Record<(typeof possibleIcons)[number], Icons> = {
|
||||
bookmark: Icons.BOOKMARK,
|
||||
};
|
||||
|
||||
export function Avatar(props: AvatarProps) {
|
||||
const icon = (avatarIconMap as any)[props.profile.icon] ?? Icons.X;
|
||||
return (
|
||||
<div
|
||||
className="h-[2em] w-[2em] rounded-full overflow-hidden flex items-center justify-center text-white"
|
||||
@ -20,7 +14,7 @@ export function Avatar(props: AvatarProps) {
|
||||
background: `linear-gradient(to bottom right, ${props.profile.colorA}, ${props.profile.colorB})`,
|
||||
}}
|
||||
>
|
||||
<Icon icon={icon} />
|
||||
<UserIcon icon={props.profile.icon as any} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
import classNames from "classnames";
|
||||
import { ReactNode } from "react";
|
||||
import { ReactNode, useCallback } from "react";
|
||||
import { useHistory } from "react-router-dom";
|
||||
|
||||
import { Icon, Icons } from "@/components/Icon";
|
||||
import { Spinner } from "@/components/layout/Spinner";
|
||||
|
||||
interface Props {
|
||||
icon?: Icons;
|
||||
@ -14,18 +15,24 @@ interface Props {
|
||||
href?: string;
|
||||
disabled?: boolean;
|
||||
download?: string;
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
export function Button(props: Props) {
|
||||
const history = useHistory();
|
||||
const { onClick, href, loading } = props;
|
||||
const cb = useCallback(() => {
|
||||
if (loading) return;
|
||||
if (href) history.push(href);
|
||||
else onClick?.();
|
||||
}, [onClick, href, history, loading]);
|
||||
|
||||
let colorClasses = "bg-white hover:bg-gray-200 text-black";
|
||||
if (props.theme === "purple")
|
||||
colorClasses =
|
||||
"bg-video-buttons-purple hover:bg-video-buttons-purpleHover text-white";
|
||||
colorClasses = "bg-buttons-purple hover:bg-buttons-purpleHover text-white";
|
||||
if (props.theme === "secondary")
|
||||
colorClasses =
|
||||
"bg-video-buttons-cancel hover:bg-video-buttons-cancelHover transition-colors duration-100 text-white";
|
||||
"bg-buttons-cancel hover:bg-buttons-cancelHover transition-colors duration-100 text-white";
|
||||
if (props.theme === "danger")
|
||||
colorClasses = "bg-buttons-danger hover:bg-buttons-dangerHover text-white";
|
||||
|
||||
@ -48,19 +55,20 @@ export function Button(props: Props) {
|
||||
|
||||
const content = (
|
||||
<>
|
||||
{props.icon ? (
|
||||
{props.icon && !props.loading ? (
|
||||
<span className="mr-3 hidden md:inline-block">
|
||||
<Icon icon={props.icon} />
|
||||
</span>
|
||||
) : null}
|
||||
{props.loading ? (
|
||||
<span className="mr-3 inline-flex justify-center">
|
||||
<Spinner className="text-lg" />
|
||||
</span>
|
||||
) : null}
|
||||
{props.children}
|
||||
</>
|
||||
);
|
||||
|
||||
function goTo(href: string) {
|
||||
history.push(href);
|
||||
}
|
||||
|
||||
if (
|
||||
props.href &&
|
||||
(props.href.startsWith("https://") || props.href?.startsWith("data:"))
|
||||
@ -79,13 +87,13 @@ export function Button(props: Props) {
|
||||
|
||||
if (props.href)
|
||||
return (
|
||||
<a className={classes} onClick={() => goTo(props.href || "")}>
|
||||
<a className={classes} onClick={cb}>
|
||||
{content}
|
||||
</a>
|
||||
);
|
||||
|
||||
return (
|
||||
<button type="button" onClick={props.onClick} className={classes}>
|
||||
<button type="button" onClick={cb} className={classes}>
|
||||
{content}
|
||||
</button>
|
||||
);
|
||||
@ -101,11 +109,10 @@ interface ButtonPlainProps {
|
||||
export function ButtonPlain(props: ButtonPlainProps) {
|
||||
let colorClasses = "bg-white hover:bg-gray-200 text-black";
|
||||
if (props.theme === "purple")
|
||||
colorClasses =
|
||||
"bg-video-buttons-purple hover:bg-video-buttons-purpleHover text-white";
|
||||
colorClasses = "bg-buttons-purple hover:bg-buttons-purpleHover text-white";
|
||||
if (props.theme === "secondary")
|
||||
colorClasses =
|
||||
"bg-video-buttons-cancel hover:bg-video-buttons-cancelHover transition-colors duration-100 text-white";
|
||||
"bg-buttons-cancel hover:bg-buttons-cancelHover transition-colors duration-100 text-white";
|
||||
|
||||
const classes = classNames(
|
||||
"cursor-pointer inline-flex items-center justify-center rounded-lg font-medium transition-[transform,background-color] duration-100 active:scale-105 md:px-8",
|
||||
|
35
src/components/UserIcon.tsx
Normal file
35
src/components/UserIcon.tsx
Normal file
@ -0,0 +1,35 @@
|
||||
import { memo } from "react";
|
||||
|
||||
import { Icon, Icons } from "@/components/Icon";
|
||||
|
||||
export enum UserIcons {
|
||||
SEARCH = "search",
|
||||
BOOKMARK = "bookmark",
|
||||
CLOCK = "clock",
|
||||
EYE_SLASH = "eyeSlash",
|
||||
USER = "user",
|
||||
}
|
||||
|
||||
export interface UserIconProps {
|
||||
icon: UserIcons;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const iconList: Record<UserIcons, string> = {
|
||||
search: `<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path fill="currentColor" d="M500.3 443.7l-119.7-119.7c27.22-40.41 40.65-90.9 33.46-144.7C401.8 87.79 326.8 13.32 235.2 1.723C99.01-15.51-15.51 99.01 1.724 235.2c11.6 91.64 86.08 166.7 177.6 178.9c53.8 7.189 104.3-6.236 144.7-33.46l119.7 119.7c15.62 15.62 40.95 15.62 56.57 0C515.9 484.7 515.9 459.3 500.3 443.7zM79.1 208c0-70.58 57.42-128 128-128s128 57.42 128 128c0 70.58-57.42 128-128 128S79.1 278.6 79.1 208z"/></svg>`,
|
||||
bookmark: `<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 384 512"><!--! Font Awesome Pro 6.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path fill="currentColor" d="M384 48V512l-192-112L0 512V48C0 21.5 21.5 0 48 0h288C362.5 0 384 21.5 384 48z"/></svg>`,
|
||||
clock: `<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path fill="currentColor" d="M256 512C114.6 512 0 397.4 0 256C0 114.6 114.6 0 256 0C397.4 0 512 114.6 512 256C512 397.4 397.4 512 256 512zM232 256C232 264 236 271.5 242.7 275.1L338.7 339.1C349.7 347.3 364.6 344.3 371.1 333.3C379.3 322.3 376.3 307.4 365.3 300L280 243.2V120C280 106.7 269.3 96 255.1 96C242.7 96 231.1 106.7 231.1 120L232 256z"/></svg>`,
|
||||
eyeSlash: `<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 640 512"><!--! Font Awesome Pro 6.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path fill="currentColor" d="M150.7 92.77C195 58.27 251.8 32 320 32C400.8 32 465.5 68.84 512.6 112.6C559.4 156 590.7 207.1 605.5 243.7C608.8 251.6 608.8 260.4 605.5 268.3C592.1 300.6 565.2 346.1 525.6 386.7L630.8 469.1C641.2 477.3 643.1 492.4 634.9 502.8C626.7 513.2 611.6 515.1 601.2 506.9L9.196 42.89C-1.236 34.71-3.065 19.63 5.112 9.196C13.29-1.236 28.37-3.065 38.81 5.112L150.7 92.77zM223.1 149.5L313.4 220.3C317.6 211.8 320 202.2 320 191.1C320 180.5 316.1 169.7 311.6 160.4C314.4 160.1 317.2 159.1 320 159.1C373 159.1 416 202.1 416 255.1C416 269.7 413.1 282.7 407.1 294.5L446.6 324.7C457.7 304.3 464 280.9 464 255.1C464 176.5 399.5 111.1 320 111.1C282.7 111.1 248.6 126.2 223.1 149.5zM320 480C239.2 480 174.5 443.2 127.4 399.4C80.62 355.1 49.34 304 34.46 268.3C31.18 260.4 31.18 251.6 34.46 243.7C44 220.8 60.29 191.2 83.09 161.5L177.4 235.8C176.5 242.4 176 249.1 176 255.1C176 335.5 240.5 400 320 400C338.7 400 356.6 396.4 373 389.9L446.2 447.5C409.9 467.1 367.8 480 320 480H320z"/></svg>`,
|
||||
user: `<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-user"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path><circle cx="12" cy="7" r="4"></circle></svg>`,
|
||||
};
|
||||
|
||||
export const UserIcon = memo((props: UserIconProps) => {
|
||||
const icon = iconList[props.icon];
|
||||
if (!icon) return <Icon icon={Icons.X} />;
|
||||
return (
|
||||
<span
|
||||
dangerouslySetInnerHTML={{ __html: icon }} // eslint-disable-line react/no-danger
|
||||
className={props.className}
|
||||
/>
|
||||
);
|
||||
});
|
39
src/components/form/ColorPicker.tsx
Normal file
39
src/components/form/ColorPicker.tsx
Normal file
@ -0,0 +1,39 @@
|
||||
import classNames from "classnames";
|
||||
|
||||
import { Icon, Icons } from "../Icon";
|
||||
|
||||
export function ColorPicker(props: {
|
||||
label: string;
|
||||
value: string;
|
||||
onInput: (v: string) => void;
|
||||
}) {
|
||||
// Migrate this to another file later
|
||||
const colors = ["#2E65CF", "#7652DD", "#CF2E68", "#C2CF2E", "#2ECFA8"];
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
{props.label ? (
|
||||
<p className="font-bold text-white">{props.label}</p>
|
||||
) : null}
|
||||
|
||||
<div className="flex gap-3">
|
||||
{colors.map((color) => {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
tabIndex={0}
|
||||
className={classNames(
|
||||
"w-full h-10 rounded flex justify-center items-center text-white pointer border-2 border-opacity-10 cursor-pointer",
|
||||
props.value === color ? "border-white" : "border-transparent"
|
||||
)}
|
||||
onClick={() => props.onInput(color)}
|
||||
style={{ backgroundColor: color }}
|
||||
key={color}
|
||||
>
|
||||
{props.value === color ? <Icon icon={Icons.CHECKMARK} /> : null}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
47
src/components/form/IconPicker.tsx
Normal file
47
src/components/form/IconPicker.tsx
Normal file
@ -0,0 +1,47 @@
|
||||
import classNames from "classnames";
|
||||
|
||||
import { UserIcon, UserIcons } from "../UserIcon";
|
||||
|
||||
export function IconPicker(props: {
|
||||
label: string;
|
||||
value: UserIcons;
|
||||
onInput: (v: UserIcons) => void;
|
||||
}) {
|
||||
// Migrate this to another file later
|
||||
const icons = [
|
||||
UserIcons.USER,
|
||||
UserIcons.BOOKMARK,
|
||||
UserIcons.CLOCK,
|
||||
UserIcons.EYE_SLASH,
|
||||
UserIcons.SEARCH,
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
{props.label ? (
|
||||
<p className="font-bold text-white">{props.label}</p>
|
||||
) : null}
|
||||
|
||||
<div className="flex gap-3">
|
||||
{icons.map((icon) => {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
tabIndex={0}
|
||||
className={classNames(
|
||||
"w-full h-10 rounded flex justify-center items-center text-white pointer border-2 border-opacity-10 cursor-pointer",
|
||||
props.value === icon
|
||||
? "bg-buttons-purple border-white"
|
||||
: "bg-authentication-inputBg border-transparent"
|
||||
)}
|
||||
onClick={() => props.onInput(icon)}
|
||||
key={icon}
|
||||
>
|
||||
<UserIcon className="text-xl" icon={icon} />
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -38,7 +38,7 @@ export function LargeCardText(props: {
|
||||
|
||||
export function LargeCardButtons(props: { children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="flex justify-center mt-8">
|
||||
<div className="flex justify-center mt-12">
|
||||
<div className="mx-auto inline-grid grid-cols-1 gap-3 justify-center items-center">
|
||||
{props.children}
|
||||
</div>
|
||||
|
@ -93,14 +93,14 @@ export function NextEpisodeButton(props: {
|
||||
])}
|
||||
>
|
||||
<Button
|
||||
className="py-px box-content bg-video-buttons-secondary hover:bg-video-buttons-secondaryHover bg-opacity-90 text-video-buttons-secondaryText"
|
||||
className="py-px box-content bg-buttons-secondary hover:bg-buttons-secondaryHover bg-opacity-90 text-buttons-secondaryText"
|
||||
onClick={hideNextEpisodeButton}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => loadNextEpisode()}
|
||||
className="bg-video-buttons-primary hover:bg-video-buttons-primaryHover text-video-buttons-primaryText flex justify-center items-center"
|
||||
className="bg-buttons-primary hover:bg-buttons-primaryHover text-buttons-primaryText flex justify-center items-center"
|
||||
>
|
||||
<Icon className="text-xl mr-1" icon={Icons.SKIP_EPISODE} />
|
||||
Next episode
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { useRef } from "react";
|
||||
import { useAsync, useInterval } from "react-use";
|
||||
|
||||
import { useAuth } from "@/hooks/auth/useAuth";
|
||||
@ -6,11 +7,18 @@ const AUTH_CHECK_INTERVAL = 12 * 60 * 60 * 1000;
|
||||
|
||||
export function useAuthRestore() {
|
||||
const { restore } = useAuth();
|
||||
const hasRestored = useRef(false);
|
||||
|
||||
useInterval(() => {
|
||||
restore();
|
||||
}, AUTH_CHECK_INTERVAL);
|
||||
|
||||
const result = useAsync(() => restore(), [restore]);
|
||||
const result = useAsync(async () => {
|
||||
if (hasRestored.current) return;
|
||||
await restore().finally(() => {
|
||||
hasRestored.current = true;
|
||||
});
|
||||
}, []); // no deps because we don't want to it ever rerun after the first time
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import "core-js/stable";
|
||||
import React from "react";
|
||||
import React, { Suspense } from "react";
|
||||
import type { ReactNode } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { HelmetProvider } from "react-helmet-async";
|
||||
@ -32,10 +32,15 @@ registerSW({
|
||||
immediate: true,
|
||||
});
|
||||
|
||||
function LoadingScreen(props: { type: "user" | "lazy" }) {
|
||||
return <p>Loading: {props.type}</p>;
|
||||
}
|
||||
|
||||
function AuthWrapper() {
|
||||
const status = useAuthRestore();
|
||||
|
||||
if (status.loading) return <p>Fetching user data</p>;
|
||||
// TODO what to do when failing to load user data?
|
||||
if (status.loading) return <LoadingScreen type="user" />;
|
||||
if (status.error) return <p>Failed to fetch user data</p>;
|
||||
return <App />;
|
||||
}
|
||||
@ -62,9 +67,11 @@ ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<ErrorBoundary>
|
||||
<HelmetProvider>
|
||||
<TheRouter>
|
||||
<MigrationRunner />
|
||||
</TheRouter>
|
||||
<Suspense fallback={<LoadingScreen type="lazy" />}>
|
||||
<TheRouter>
|
||||
<MigrationRunner />
|
||||
</TheRouter>
|
||||
</Suspense>
|
||||
</HelmetProvider>
|
||||
</ErrorBoundary>
|
||||
</React.StrictMode>,
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { useState } from "react";
|
||||
import { GoogleReCaptchaProvider } from "react-google-recaptcha-v3";
|
||||
import { useHistory } from "react-router-dom";
|
||||
|
||||
import { MetaResponse } from "@/backend/accounts/meta";
|
||||
import { SubPageLayout } from "@/pages/layouts/SubPageLayout";
|
||||
@ -24,13 +25,12 @@ function CaptchaProvider(props: {
|
||||
}
|
||||
|
||||
export function RegisterPage() {
|
||||
const history = useHistory();
|
||||
const [step, setStep] = useState(0);
|
||||
const [mnemonic, setMnemonic] = useState<null | string>(null);
|
||||
const [account, setAccount] = useState<null | AccountProfile>(null);
|
||||
const [siteKey, setSiteKey] = useState<string | null>(null);
|
||||
|
||||
// TODO because of user data loading (in useAuthRestore()), the register page gets unmounted before finishing the register flow
|
||||
|
||||
return (
|
||||
<CaptchaProvider siteKey={siteKey}>
|
||||
<SubPageLayout>
|
||||
@ -68,11 +68,10 @@ export function RegisterPage() {
|
||||
mnemonic={mnemonic}
|
||||
userData={account}
|
||||
onNext={() => {
|
||||
setStep(4);
|
||||
history.push("/");
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
{step === 4 ? <p>Success, account now exists</p> : null}
|
||||
</SubPageLayout>
|
||||
</CaptchaProvider>
|
||||
);
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
import { Button } from "@/components/Button";
|
||||
import { ColorPicker } from "@/components/form/ColorPicker";
|
||||
import { IconPicker } from "@/components/form/IconPicker";
|
||||
import { Icon, Icons } from "@/components/Icon";
|
||||
import {
|
||||
LargeCard,
|
||||
@ -8,6 +10,7 @@ import {
|
||||
LargeCardText,
|
||||
} from "@/components/layout/LargeCard";
|
||||
import { AuthInputBox } from "@/components/text-inputs/AuthInputBox";
|
||||
import { UserIcons } from "@/components/UserIcon";
|
||||
|
||||
export interface AccountProfile {
|
||||
device: string;
|
||||
@ -24,18 +27,21 @@ interface AccountCreatePartProps {
|
||||
|
||||
export function AccountCreatePart(props: AccountCreatePartProps) {
|
||||
const [device, setDevice] = useState("");
|
||||
const [colorA, setColorA] = useState("#2E65CF");
|
||||
const [colorB, setColorB] = useState("#2E65CF");
|
||||
const [userIcon, setUserIcon] = useState<UserIcons>(UserIcons.USER);
|
||||
// TODO validate device and account before next step
|
||||
|
||||
const nextStep = useCallback(() => {
|
||||
props.onNext?.({
|
||||
device,
|
||||
profile: {
|
||||
colorA: "#fff",
|
||||
colorB: "#000",
|
||||
icon: "brush",
|
||||
colorA,
|
||||
colorB,
|
||||
icon: userIcon,
|
||||
},
|
||||
});
|
||||
}, [device, props]);
|
||||
}, [device, props, colorA, colorB, userIcon]);
|
||||
|
||||
return (
|
||||
<LargeCard>
|
||||
@ -45,12 +51,17 @@ export function AccountCreatePart(props: AccountCreatePartProps) {
|
||||
>
|
||||
Set up your account.... OR ELSE!
|
||||
</LargeCardText>
|
||||
<AuthInputBox
|
||||
label="Device name"
|
||||
value={device}
|
||||
onChange={setDevice}
|
||||
placeholder="Muad'Dib's Nintendo Switch"
|
||||
/>
|
||||
<div className="space-y-6">
|
||||
<AuthInputBox
|
||||
label="Device name"
|
||||
value={device}
|
||||
onChange={setDevice}
|
||||
placeholder="Muad'Dib's Nintendo Switch"
|
||||
/>
|
||||
<ColorPicker label="First color" value={colorA} onInput={setColorA} />
|
||||
<ColorPicker label="Second color" value={colorB} onInput={setColorB} />
|
||||
<IconPicker label="User icon" value={userIcon} onInput={setUserIcon} />
|
||||
</div>
|
||||
<LargeCardButtons>
|
||||
<Button theme="purple" onClick={() => nextStep()}>
|
||||
Next
|
||||
|
@ -27,7 +27,6 @@ export function LoginFormPart(props: LoginFormPartProps) {
|
||||
if (!verifyValidMnemonic(inputMnemonic))
|
||||
throw new Error("Invalid or incomplete passphrase");
|
||||
|
||||
// TODO captcha?
|
||||
await login({
|
||||
mnemonic: inputMnemonic,
|
||||
userData: {
|
||||
@ -64,7 +63,6 @@ export function LoginFormPart(props: LoginFormPartProps) {
|
||||
onChange={setDevice}
|
||||
placeholder="Device"
|
||||
/>
|
||||
{result.loading ? <p>Loading...</p> : null}
|
||||
{result.error && !result.loading ? (
|
||||
<p className="text-authentication-errorText">
|
||||
{result.error.message}
|
||||
@ -73,7 +71,11 @@ export function LoginFormPart(props: LoginFormPartProps) {
|
||||
</div>
|
||||
|
||||
<LargeCardButtons>
|
||||
<Button theme="purple" onClick={() => execute(mnemonic, device)}>
|
||||
<Button
|
||||
theme="purple"
|
||||
loading={result.loading}
|
||||
onClick={() => execute(mnemonic, device)}
|
||||
>
|
||||
LET ME IN!
|
||||
</Button>
|
||||
</LargeCardButtons>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { GoogleReCaptcha, useGoogleReCaptcha } from "react-google-recaptcha-v3";
|
||||
import { useState } from "react";
|
||||
import { useGoogleReCaptcha } from "react-google-recaptcha-v3";
|
||||
import { useAsyncFn } from "react-use";
|
||||
|
||||
import { Button } from "@/components/Button";
|
||||
@ -77,7 +77,11 @@ export function VerifyPassphrase(props: VerifyPassphraseProps) {
|
||||
</p>
|
||||
) : null}
|
||||
<LargeCardButtons>
|
||||
<Button theme="purple" onClick={() => execute(mnemonic)}>
|
||||
<Button
|
||||
theme="purple"
|
||||
loading={result.loading}
|
||||
onClick={() => execute(mnemonic)}
|
||||
>
|
||||
Register
|
||||
</Button>
|
||||
</LargeCardButtons>
|
||||
|
@ -26,23 +26,23 @@ module.exports = {
|
||||
"ash-400": "#3D394D",
|
||||
"ash-300": "#2C293A",
|
||||
"ash-200": "#2B2836",
|
||||
"ash-100": "#1E1C26",
|
||||
"ash-100": "#1E1C26"
|
||||
},
|
||||
|
||||
/* fonts */
|
||||
fontFamily: {
|
||||
"open-sans": "'Open Sans'",
|
||||
"open-sans": "'Open Sans'"
|
||||
},
|
||||
|
||||
/* animations */
|
||||
keyframes: {
|
||||
"loading-pin": {
|
||||
"0%, 40%, 100%": { height: "0.5em", "background-color": "#282336" },
|
||||
"20%": { height: "1em", "background-color": "white" },
|
||||
},
|
||||
"20%": { height: "1em", "background-color": "white" }
|
||||
}
|
||||
},
|
||||
animation: { "loading-pin": "loading-pin 1.8s ease-in-out infinite" },
|
||||
},
|
||||
animation: { "loading-pin": "loading-pin 1.8s ease-in-out infinite" }
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
require("tailwind-scrollbar"),
|
||||
@ -52,18 +52,18 @@ module.exports = {
|
||||
colors: {
|
||||
// Branding
|
||||
pill: {
|
||||
background: "#1C1C36",
|
||||
background: "#1C1C36"
|
||||
},
|
||||
|
||||
// meta data for the theme itself
|
||||
global: {
|
||||
accentA: "#505DBD",
|
||||
accentB: "#3440A1",
|
||||
accentB: "#3440A1"
|
||||
},
|
||||
|
||||
// light bar
|
||||
lightBar: {
|
||||
light: "#2A2A71",
|
||||
light: "#2A2A71"
|
||||
},
|
||||
|
||||
// Buttons
|
||||
@ -72,13 +72,24 @@ module.exports = {
|
||||
toggleDisabled: "#202836",
|
||||
danger: "#792131",
|
||||
dangerHover: "#8a293b",
|
||||
|
||||
secondary: "#161F25",
|
||||
secondaryText: "#8EA3B0",
|
||||
secondaryHover: "#1B262E",
|
||||
primary: "#fff",
|
||||
primaryText: "#000",
|
||||
primaryHover: "#dedede",
|
||||
purple: "#6b298a",
|
||||
purpleHover: "#7f35a1",
|
||||
cancel: "#252533",
|
||||
cancelHover: "#3C3C4A"
|
||||
},
|
||||
|
||||
// only used for body colors/textures
|
||||
background: {
|
||||
main: "#0A0A10",
|
||||
accentA: "#6E3B80",
|
||||
accentB: "#1F1F50",
|
||||
accentB: "#1F1F50"
|
||||
},
|
||||
|
||||
// typography
|
||||
@ -87,7 +98,7 @@ module.exports = {
|
||||
text: "#73739D",
|
||||
dimmed: "#926CAD",
|
||||
divider: "#262632",
|
||||
secondary: "#64647B",
|
||||
secondary: "#64647B"
|
||||
},
|
||||
|
||||
// search bar
|
||||
@ -96,7 +107,7 @@ module.exports = {
|
||||
focused: "#24243C",
|
||||
placeholder: "#4A4A71",
|
||||
icon: "#545476",
|
||||
text: "#FFFFFF",
|
||||
text: "#FFFFFF"
|
||||
},
|
||||
|
||||
// media cards
|
||||
@ -108,13 +119,13 @@ module.exports = {
|
||||
barColor: "#4B4B63",
|
||||
barFillColor: "#BA7FD6",
|
||||
badge: "#151522",
|
||||
badgeText: "#5F5F7A",
|
||||
badgeText: "#5F5F7A"
|
||||
},
|
||||
|
||||
// Large card
|
||||
largeCard: {
|
||||
background: "#171728",
|
||||
icon: "#6741A5",
|
||||
icon: "#6741A5"
|
||||
},
|
||||
|
||||
// Passphrase
|
||||
@ -124,7 +135,7 @@ module.exports = {
|
||||
wordBackground: "#171728",
|
||||
copyText: "#58587A",
|
||||
copyTextHover: "#8888AA",
|
||||
errorText: "#DB3D62",
|
||||
errorText: "#DB3D62"
|
||||
},
|
||||
|
||||
// Settings page
|
||||
@ -137,19 +148,19 @@ module.exports = {
|
||||
inactive: "#8D68A9",
|
||||
icon: "#926CAD",
|
||||
iconActivated: "#6942A8",
|
||||
activated: "#CBA1E8",
|
||||
},
|
||||
activated: "#CBA1E8"
|
||||
}
|
||||
},
|
||||
|
||||
card: {
|
||||
border: "#2A243E",
|
||||
background: "#29243D",
|
||||
altBackground: "#29243D",
|
||||
},
|
||||
altBackground: "#29243D"
|
||||
}
|
||||
},
|
||||
|
||||
utils: {
|
||||
divider: "#353549",
|
||||
divider: "#353549"
|
||||
},
|
||||
|
||||
// Error page
|
||||
@ -158,20 +169,20 @@ module.exports = {
|
||||
border: "#252534",
|
||||
|
||||
type: {
|
||||
secondary: "#62627D",
|
||||
},
|
||||
secondary: "#62627D"
|
||||
}
|
||||
},
|
||||
|
||||
// About page
|
||||
about: {
|
||||
circle: "#262632",
|
||||
circleText: "#9A9AC3",
|
||||
circleText: "#9A9AC3"
|
||||
},
|
||||
|
||||
progress: {
|
||||
background: "#8787A8",
|
||||
preloaded: "#8787A8",
|
||||
filled: "#A75FC9",
|
||||
filled: "#A75FC9"
|
||||
},
|
||||
|
||||
// video player
|
||||
@ -183,24 +194,11 @@ module.exports = {
|
||||
error: "#E44F4F",
|
||||
success: "#40B44B",
|
||||
loading: "#B759D8",
|
||||
noresult: "#64647B",
|
||||
noresult: "#64647B"
|
||||
},
|
||||
|
||||
audio: {
|
||||
set: "#A75FC9",
|
||||
},
|
||||
|
||||
buttons: {
|
||||
secondary: "#161F25",
|
||||
secondaryText: "#8EA3B0",
|
||||
secondaryHover: "#1B262E",
|
||||
primary: "#fff",
|
||||
primaryText: "#000",
|
||||
primaryHover: "#dedede",
|
||||
purple: "#6b298a",
|
||||
purpleHover: "#7f35a1",
|
||||
cancel: "#252533",
|
||||
cancelHover: "#3C3C4A",
|
||||
set: "#A75FC9"
|
||||
},
|
||||
|
||||
context: {
|
||||
@ -220,19 +218,19 @@ module.exports = {
|
||||
|
||||
buttons: {
|
||||
list: "#161C26",
|
||||
active: "#0D1317",
|
||||
active: "#0D1317"
|
||||
},
|
||||
|
||||
type: {
|
||||
main: "#617A8A",
|
||||
secondary: "#374A56",
|
||||
accent: "#A570FA",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
accent: "#A570FA"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
]
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user