Skip to content

Commit 9d8711e

Browse files
authored
feat/1055 - Faucet settings (#1056)
* fix: clean up, fix faucet form UI issue * feat: hook up settings modal * feat: improve forms & components * fix: touch up styles
1 parent 5901f3e commit 9d8711e

19 files changed

+293
-88
lines changed

apps/faucet/src/App/App.components.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,3 +140,50 @@ export const ContentContainer = styled.div`
140140
padding: 0 16px;
141141
}
142142
`;
143+
144+
export const InputContainer = styled.div`
145+
margin: 12px 0;
146+
`;
147+
148+
export const ButtonContainer = styled.div`
149+
display: flex;
150+
flex-direction: column;
151+
justify-content: center;
152+
align-items: center;
153+
margin: 13px 0 0 0;
154+
`;
155+
156+
export const SettingsButtonContainer = styled.div`
157+
display: flex;
158+
align-items: center;
159+
justify-content: flex-end;
160+
width: 100%;
161+
`;
162+
163+
export const SettingsButton = styled.button`
164+
& > svg {
165+
width: 20px;
166+
height: 20px;
167+
color: ${(props) => props.theme.colors.primary.main20};
168+
}
169+
`;
170+
171+
export const SettingsContainer = styled.div`
172+
flex-direction: column;
173+
justify-content: start;
174+
align-items: center;
175+
box-sizing: border-box;
176+
background-color: ${(props) =>
177+
getColor(ComponentColor.BackgroundColor, props.theme)};
178+
border: 1px solid ${(props) => props.theme.colors.primary.main20};
179+
border-radius: ${(props) => props.theme.borderRadius.mainContainer};
180+
transition: background-color 0.3s linear;
181+
`;
182+
183+
export const SettingsFormContainer = styled.form`
184+
flex-direction: column;
185+
justify-content: start;
186+
align-items: center;
187+
padding: 32px 40px;
188+
width: 500px;
189+
`;

apps/faucet/src/App/App.tsx

Lines changed: 80 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,50 @@
11
import React, { createContext, useCallback, useEffect, useState } from "react";
2+
import { GoGear } from "react-icons/go";
23
import { ThemeProvider } from "styled-components";
34

4-
import { ActionButton, Alert, Heading } from "@namada/components";
5+
import { ActionButton, Alert, Modal } from "@namada/components";
56
import { Namada } from "@namada/integrations";
67
import { ColorMode, getTheme } from "@namada/utils";
78

89
import {
910
AppContainer,
1011
BackgroundImage,
11-
Banner,
12-
BannerContents,
1312
BottomSection,
1413
ContentContainer,
1514
FaucetContainer,
1615
GlobalStyles,
1716
InfoContainer,
17+
SettingsButton,
18+
SettingsButtonContainer,
1819
TopSection,
1920
} from "App/App.components";
2021
import { FaucetForm } from "App/Faucet";
2122

2223
import { chains } from "@namada/chains";
2324
import { useUntil } from "@namada/hooks";
2425
import { Account } from "@namada/types";
25-
import { API } from "utils";
26+
import { API, toNam } from "utils";
2627
import dotsBackground from "../../public/bg-dots.svg";
27-
import { CallToActionCard } from "./CallToActionCard";
28-
import { CardsContainer } from "./Card.components";
29-
import { Faq } from "./Faq";
28+
import {
29+
AppBanner,
30+
AppHeader,
31+
CallToActionCard,
32+
CardsContainer,
33+
Faq,
34+
} from "./Common";
35+
import { SettingsForm } from "./SettingsForm";
3036

3137
const DEFAULT_URL = "http://localhost:5000";
32-
const DEFAULT_ENDPOINT = "/api/v1/faucet";
33-
const DEFAULT_FAUCET_LIMIT = "1000";
38+
const DEFAULT_LIMIT = 1_000_000_000;
3439

3540
const {
3641
NAMADA_INTERFACE_FAUCET_API_URL: faucetApiUrl = DEFAULT_URL,
37-
NAMADA_INTERFACE_FAUCET_API_ENDPOINT: faucetApiEndpoint = DEFAULT_ENDPOINT,
38-
NAMADA_INTERFACE_FAUCET_LIMIT: faucetLimit = DEFAULT_FAUCET_LIMIT,
3942
NAMADA_INTERFACE_PROXY: isProxied,
4043
NAMADA_INTERFACE_PROXY_PORT: proxyPort = 9000,
4144
} = process.env;
4245

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

@@ -52,13 +53,18 @@ type Settings = {
5253
tokens?: Record<string, string>;
5354
startsAt: number;
5455
startsAtText?: string;
56+
withdrawLimit: number;
5557
};
5658

57-
type AppContext = Settings & {
58-
limit: number;
59-
url: string;
59+
type AppContext = {
60+
baseUrl: string;
6061
settingsError?: string;
6162
api: API;
63+
isTestnetLive: boolean;
64+
settings: Settings;
65+
setApi: (api: API) => void;
66+
setUrl: (url: string) => void;
67+
setIsModalOpen: (value: boolean) => void;
6268
};
6369

6470
const START_TIME_UTC = 1702918800;
@@ -74,17 +80,7 @@ const START_TIME_TEXT = new Date(START_TIME_UTC * 1000).toLocaleString(
7480
}
7581
);
7682

77-
const defaults = {
78-
startsAt: START_TIME_UTC,
79-
startsAtText: `${START_TIME_TEXT} UTC`,
80-
};
81-
82-
export const AppContext = createContext<AppContext>({
83-
...defaults,
84-
limit,
85-
url,
86-
api,
87-
});
83+
export const AppContext = createContext<AppContext | null>(null);
8884

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

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

126127
useEffect(() => {
128+
// Sync url to localStorage
129+
localStorage.setItem("baseUrl", url);
130+
const api = new API(url);
131+
setApi(api);
127132
const { startsAt } = settings;
128133
const now = new Date();
129134
const nowUTC = Date.UTC(
@@ -141,26 +146,28 @@ export const App: React.FC = () => {
141146
// Fetch settings from faucet API
142147
(async () => {
143148
try {
144-
const { difficulty, tokens_alias_to_address: tokens } = await api
145-
.settings()
146-
.catch((e) => {
147-
const message = e.errors?.message;
148-
setSettingsError(
149-
`Error requesting settings: ${message?.join(" ")}`
150-
);
151-
throw new Error(e);
152-
});
149+
const {
150+
difficulty,
151+
tokens_alias_to_address: tokens,
152+
withdraw_limit: withdrawLimit = DEFAULT_LIMIT,
153+
} = await api.settings().catch((e) => {
154+
const message = e.errors?.message;
155+
setSettingsError(`Error requesting settings: ${message?.join(" ")}`);
156+
throw new Error(e);
157+
});
153158
// Append difficulty level and tokens to settings
154159
setSettings({
155160
...settings,
156161
difficulty,
157162
tokens,
163+
withdrawLimit: toNam(withdrawLimit),
158164
});
165+
setSettingsError(undefined);
159166
} catch (e) {
160167
setSettingsError(`Failed to load settings! ${e}`);
161168
}
162169
})();
163-
}, []);
170+
}, [url]);
164171

165172
const handleConnectExtensionClick = useCallback(async (): Promise<void> => {
166173
if (integration) {
@@ -186,43 +193,53 @@ export const App: React.FC = () => {
186193
return (
187194
<AppContext.Provider
188195
value={{
189-
settingsError,
190-
limit,
191-
url,
192196
api,
193-
...settings,
197+
isTestnetLive,
198+
baseUrl: url,
199+
settingsError,
200+
settings,
201+
setApi,
202+
setUrl,
203+
setIsModalOpen,
194204
}}
195205
>
196206
<ThemeProvider theme={theme}>
197207
<GlobalStyles colorMode={colorMode} />
198-
{!isTestnetLive && settings?.startsAtText && (
199-
<Banner>
200-
<BannerContents>
201-
Testnet will go live {settings.startsAtText}! Faucet is disabled
202-
until then.
203-
</BannerContents>
204-
</Banner>
205-
)}
208+
<AppBanner />
206209
<BackgroundImage imageUrl={dotsBackground} />
207210
<AppContainer>
208211
<ContentContainer>
212+
<SettingsButtonContainer>
213+
<SettingsButton
214+
onClick={() => setIsModalOpen(true)}
215+
title="Settings"
216+
>
217+
<GoGear />
218+
</SettingsButton>
219+
</SettingsButtonContainer>
220+
209221
<TopSection>
210-
<Heading className="uppercase text-black text-4xl" level="h1">
211-
Namada Faucet
212-
</Heading>
222+
<AppHeader />
213223
</TopSection>
214224
<FaucetContainer>
215-
{extensionAttachStatus ===
216-
ExtensionAttachStatus.PendingDetection && (
225+
{settingsError && (
217226
<InfoContainer>
218-
<Alert type="info">Detecting extension...</Alert>
227+
<Alert type="error">{settingsError}</Alert>
219228
</InfoContainer>
220229
)}
230+
231+
{extensionAttachStatus ===
232+
ExtensionAttachStatus.PendingDetection && (
233+
<InfoContainer>
234+
<Alert type="info">Detecting extension...</Alert>
235+
</InfoContainer>
236+
)}
221237
{extensionAttachStatus === ExtensionAttachStatus.NotInstalled && (
222238
<InfoContainer>
223239
<Alert type="error">You must download the extension!</Alert>
224240
</InfoContainer>
225241
)}
242+
226243
{isExtensionConnected && (
227244
<FaucetForm
228245
accounts={accounts}
@@ -239,6 +256,11 @@ export const App: React.FC = () => {
239256
</InfoContainer>
240257
)}
241258
</FaucetContainer>
259+
{isModalOpen && (
260+
<Modal onClose={() => setIsModalOpen(false)}>
261+
<SettingsForm />
262+
</Modal>
263+
)}
242264
<BottomSection>
243265
<CardsContainer>
244266
<CallToActionCard
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { AppContext } from "App/App";
2+
import React, { useContext } from "react";
3+
import { Banner, BannerContents } from "./Banner.components";
4+
5+
export const AppBanner: React.FC = () => {
6+
const { isTestnetLive, settings } = useContext(AppContext)!;
7+
return (
8+
<>
9+
{!isTestnetLive && settings?.startsAtText && (
10+
<Banner>
11+
<BannerContents>
12+
Testnet will go live {settings.startsAtText}! Faucet is disabled
13+
until then.
14+
</BannerContents>
15+
</Banner>
16+
)}
17+
</>
18+
);
19+
};
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import styled from "styled-components";
2+
3+
export const AppHeaderContainer = styled.div`
4+
display: flex;
5+
align-items: center;
6+
justify-content: center;
7+
`;
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Heading } from "@namada/components";
2+
import React from "react";
3+
import { AppHeaderContainer } from "./AppHeader.components";
4+
5+
export const AppHeader: React.FC = () => {
6+
return (
7+
<AppHeaderContainer>
8+
<Heading className="uppercase text-black text-4xl" level="h1">
9+
Namada Faucet
10+
</Heading>
11+
</AppHeaderContainer>
12+
);
13+
};
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import styled from "styled-components";
2+
3+
export const Banner = styled.div`
4+
width: 100%;
5+
display: flex;
6+
justify-content: center;
7+
align-items: center;
8+
background-color: ${(props) => props.theme.colors.utility3.highAttention};
9+
color: ${(props) => props.theme.colors.primary.main20};
10+
font-size: 13px;
11+
font-weight: bold;
12+
`;
13+
14+
export const BannerContents = styled.div`
15+
display: flex;
16+
width: 100%;
17+
align-items: center;
18+
max-width: 762px;
19+
padding: 8px 0;
20+
margin: 0 20px;
21+
`;

apps/faucet/src/App/CallToActionCard.tsx renamed to apps/faucet/src/App/Common/CallToActionCard.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React, { useState } from "react";
2-
import InclineArrowBlack from "../../public/incline-arrow-black.svg";
3-
import InclineArrowYellow from "../../public/incline-arrow-yellow.svg";
2+
import InclineArrowBlack from "../../../public/incline-arrow-black.svg";
3+
import InclineArrowYellow from "../../../public/incline-arrow-yellow.svg";
44
import {
55
BottomBorder,
66
CallToActionContainer,
File renamed without changes.

0 commit comments

Comments
 (0)