Skip to content

Commit

Permalink
feat/1055 - Faucet settings (#1056)
Browse files Browse the repository at this point in the history
* fix: clean up, fix faucet form UI issue

* feat: hook up settings modal

* feat: improve forms & components

* fix: touch up styles
  • Loading branch information
jurevans authored Sep 3, 2024
1 parent 5901f3e commit 9d8711e
Show file tree
Hide file tree
Showing 19 changed files with 293 additions and 88 deletions.
47 changes: 47 additions & 0 deletions apps/faucet/src/App/App.components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,50 @@ export const ContentContainer = styled.div`
padding: 0 16px;
}
`;

export const InputContainer = styled.div`
margin: 12px 0;
`;

export const ButtonContainer = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin: 13px 0 0 0;
`;

export const SettingsButtonContainer = styled.div`
display: flex;
align-items: center;
justify-content: flex-end;
width: 100%;
`;

export const SettingsButton = styled.button`
& > svg {
width: 20px;
height: 20px;
color: ${(props) => props.theme.colors.primary.main20};
}
`;

export const SettingsContainer = styled.div`
flex-direction: column;
justify-content: start;
align-items: center;
box-sizing: border-box;
background-color: ${(props) =>
getColor(ComponentColor.BackgroundColor, props.theme)};
border: 1px solid ${(props) => props.theme.colors.primary.main20};
border-radius: ${(props) => props.theme.borderRadius.mainContainer};
transition: background-color 0.3s linear;
`;

export const SettingsFormContainer = styled.form`
flex-direction: column;
justify-content: start;
align-items: center;
padding: 32px 40px;
width: 500px;
`;
138 changes: 80 additions & 58 deletions apps/faucet/src/App/App.tsx
Original file line number Diff line number Diff line change
@@ -1,49 +1,50 @@
import React, { createContext, useCallback, useEffect, useState } from "react";
import { GoGear } from "react-icons/go";
import { ThemeProvider } from "styled-components";

import { ActionButton, Alert, Heading } from "@namada/components";
import { ActionButton, Alert, Modal } from "@namada/components";
import { Namada } from "@namada/integrations";
import { ColorMode, getTheme } from "@namada/utils";

import {
AppContainer,
BackgroundImage,
Banner,
BannerContents,
BottomSection,
ContentContainer,
FaucetContainer,
GlobalStyles,
InfoContainer,
SettingsButton,
SettingsButtonContainer,
TopSection,
} from "App/App.components";
import { FaucetForm } from "App/Faucet";

import { chains } from "@namada/chains";
import { useUntil } from "@namada/hooks";
import { Account } from "@namada/types";
import { API } from "utils";
import { API, toNam } from "utils";
import dotsBackground from "../../public/bg-dots.svg";
import { CallToActionCard } from "./CallToActionCard";
import { CardsContainer } from "./Card.components";
import { Faq } from "./Faq";
import {
AppBanner,
AppHeader,
CallToActionCard,
CardsContainer,
Faq,
} from "./Common";
import { SettingsForm } from "./SettingsForm";

const DEFAULT_URL = "http://localhost:5000";
const DEFAULT_ENDPOINT = "/api/v1/faucet";
const DEFAULT_FAUCET_LIMIT = "1000";
const DEFAULT_LIMIT = 1_000_000_000;

const {
NAMADA_INTERFACE_FAUCET_API_URL: faucetApiUrl = DEFAULT_URL,
NAMADA_INTERFACE_FAUCET_API_ENDPOINT: faucetApiEndpoint = DEFAULT_ENDPOINT,
NAMADA_INTERFACE_FAUCET_LIMIT: faucetLimit = DEFAULT_FAUCET_LIMIT,
NAMADA_INTERFACE_PROXY: isProxied,
NAMADA_INTERFACE_PROXY_PORT: proxyPort = 9000,
} = process.env;

const apiUrl = isProxied ? `http://localhost:${proxyPort}/proxy` : faucetApiUrl;
const url = `${apiUrl}${faucetApiEndpoint}`;
const api = new API(url);
const limit = parseInt(faucetLimit);
const baseUrl =
isProxied ? `http://localhost:${proxyPort}/proxy` : faucetApiUrl;
const runFullNodeUrl = "https://docs.namada.net/operators/ledger";
const becomeBuilderUrl = "https://docs.namada.net/integrating-with-namada";

Expand All @@ -52,13 +53,18 @@ type Settings = {
tokens?: Record<string, string>;
startsAt: number;
startsAtText?: string;
withdrawLimit: number;
};

type AppContext = Settings & {
limit: number;
url: string;
type AppContext = {
baseUrl: string;
settingsError?: string;
api: API;
isTestnetLive: boolean;
settings: Settings;
setApi: (api: API) => void;
setUrl: (url: string) => void;
setIsModalOpen: (value: boolean) => void;
};

const START_TIME_UTC = 1702918800;
Expand All @@ -74,17 +80,7 @@ const START_TIME_TEXT = new Date(START_TIME_UTC * 1000).toLocaleString(
}
);

const defaults = {
startsAt: START_TIME_UTC,
startsAtText: `${START_TIME_TEXT} UTC`,
};

export const AppContext = createContext<AppContext>({
...defaults,
limit,
url,
api,
});
export const AppContext = createContext<AppContext | null>(null);

enum ExtensionAttachStatus {
PendingDetection,
Expand All @@ -104,8 +100,13 @@ export const App: React.FC = () => {
const [colorMode, _] = useState<ColorMode>(initialColorMode);
const [isTestnetLive, setIsTestnetLive] = useState(true);
const [settings, setSettings] = useState<Settings>({
...defaults,
startsAt: START_TIME_UTC,
startsAtText: `${START_TIME_TEXT} UTC`,
withdrawLimit: toNam(DEFAULT_LIMIT),
});
const [url, setUrl] = useState(localStorage.getItem("baseUrl") || baseUrl);
const [api, setApi] = useState<API>(new API(url));
const [isModalOpen, setIsModalOpen] = useState(false);
const [settingsError, setSettingsError] = useState<string>();
const theme = getTheme(colorMode);

Expand All @@ -124,6 +125,10 @@ export const App: React.FC = () => {
);

useEffect(() => {
// Sync url to localStorage
localStorage.setItem("baseUrl", url);
const api = new API(url);
setApi(api);
const { startsAt } = settings;
const now = new Date();
const nowUTC = Date.UTC(
Expand All @@ -141,26 +146,28 @@ export const App: React.FC = () => {
// Fetch settings from faucet API
(async () => {
try {
const { difficulty, tokens_alias_to_address: tokens } = await api
.settings()
.catch((e) => {
const message = e.errors?.message;
setSettingsError(
`Error requesting settings: ${message?.join(" ")}`
);
throw new Error(e);
});
const {
difficulty,
tokens_alias_to_address: tokens,
withdraw_limit: withdrawLimit = DEFAULT_LIMIT,
} = await api.settings().catch((e) => {
const message = e.errors?.message;
setSettingsError(`Error requesting settings: ${message?.join(" ")}`);
throw new Error(e);
});
// Append difficulty level and tokens to settings
setSettings({
...settings,
difficulty,
tokens,
withdrawLimit: toNam(withdrawLimit),
});
setSettingsError(undefined);
} catch (e) {
setSettingsError(`Failed to load settings! ${e}`);
}
})();
}, []);
}, [url]);

const handleConnectExtensionClick = useCallback(async (): Promise<void> => {
if (integration) {
Expand All @@ -186,43 +193,53 @@ export const App: React.FC = () => {
return (
<AppContext.Provider
value={{
settingsError,
limit,
url,
api,
...settings,
isTestnetLive,
baseUrl: url,
settingsError,
settings,
setApi,
setUrl,
setIsModalOpen,
}}
>
<ThemeProvider theme={theme}>
<GlobalStyles colorMode={colorMode} />
{!isTestnetLive && settings?.startsAtText && (
<Banner>
<BannerContents>
Testnet will go live {settings.startsAtText}! Faucet is disabled
until then.
</BannerContents>
</Banner>
)}
<AppBanner />
<BackgroundImage imageUrl={dotsBackground} />
<AppContainer>
<ContentContainer>
<SettingsButtonContainer>
<SettingsButton
onClick={() => setIsModalOpen(true)}
title="Settings"
>
<GoGear />
</SettingsButton>
</SettingsButtonContainer>

<TopSection>
<Heading className="uppercase text-black text-4xl" level="h1">
Namada Faucet
</Heading>
<AppHeader />
</TopSection>
<FaucetContainer>
{extensionAttachStatus ===
ExtensionAttachStatus.PendingDetection && (
{settingsError && (
<InfoContainer>
<Alert type="info">Detecting extension...</Alert>
<Alert type="error">{settingsError}</Alert>
</InfoContainer>
)}

{extensionAttachStatus ===
ExtensionAttachStatus.PendingDetection && (
<InfoContainer>
<Alert type="info">Detecting extension...</Alert>
</InfoContainer>
)}
{extensionAttachStatus === ExtensionAttachStatus.NotInstalled && (
<InfoContainer>
<Alert type="error">You must download the extension!</Alert>
</InfoContainer>
)}

{isExtensionConnected && (
<FaucetForm
accounts={accounts}
Expand All @@ -239,6 +256,11 @@ export const App: React.FC = () => {
</InfoContainer>
)}
</FaucetContainer>
{isModalOpen && (
<Modal onClose={() => setIsModalOpen(false)}>
<SettingsForm />
</Modal>
)}
<BottomSection>
<CardsContainer>
<CallToActionCard
Expand Down
19 changes: 19 additions & 0 deletions apps/faucet/src/App/Common/AppBanner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { AppContext } from "App/App";
import React, { useContext } from "react";
import { Banner, BannerContents } from "./Banner.components";

export const AppBanner: React.FC = () => {
const { isTestnetLive, settings } = useContext(AppContext)!;
return (
<>
{!isTestnetLive && settings?.startsAtText && (
<Banner>
<BannerContents>
Testnet will go live {settings.startsAtText}! Faucet is disabled
until then.
</BannerContents>
</Banner>
)}
</>
);
};
7 changes: 7 additions & 0 deletions apps/faucet/src/App/Common/AppHeader.components.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import styled from "styled-components";

export const AppHeaderContainer = styled.div`
display: flex;
align-items: center;
justify-content: center;
`;
13 changes: 13 additions & 0 deletions apps/faucet/src/App/Common/AppHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Heading } from "@namada/components";
import React from "react";
import { AppHeaderContainer } from "./AppHeader.components";

export const AppHeader: React.FC = () => {
return (
<AppHeaderContainer>
<Heading className="uppercase text-black text-4xl" level="h1">
Namada Faucet
</Heading>
</AppHeaderContainer>
);
};
21 changes: 21 additions & 0 deletions apps/faucet/src/App/Common/Banner.components.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import styled from "styled-components";

export const Banner = styled.div`
width: 100%;
display: flex;
justify-content: center;
align-items: center;
background-color: ${(props) => props.theme.colors.utility3.highAttention};
color: ${(props) => props.theme.colors.primary.main20};
font-size: 13px;
font-weight: bold;
`;

export const BannerContents = styled.div`
display: flex;
width: 100%;
align-items: center;
max-width: 762px;
padding: 8px 0;
margin: 0 20px;
`;
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useState } from "react";
import InclineArrowBlack from "../../public/incline-arrow-black.svg";
import InclineArrowYellow from "../../public/incline-arrow-yellow.svg";
import InclineArrowBlack from "../../../public/incline-arrow-black.svg";
import InclineArrowYellow from "../../../public/incline-arrow-yellow.svg";
import {
BottomBorder,
CallToActionContainer,
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useState } from "react";
import plusIcon from "../../public/plus-icon.svg";
import plusIcon from "../../../public/plus-icon.svg";
import {
DropDownTitle,
DropDownTitleText,
Expand Down
6 changes: 6 additions & 0 deletions apps/faucet/src/App/Common/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export * from "./AppBanner";
export * from "./AppHeader";
export * from "./CallToActionCard";
export * from "./Card.components";
export * from "./Faq";
export * from "./FaqDropdown";
Loading

1 comment on commit 9d8711e

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.