Skip to content

Commit

Permalink
Various updates and fixes (#21)
Browse files Browse the repository at this point in the history
* Make auto select text work on Safari too + cleanup

* getTez: Format amounts in message

* Cleanup

* Handle fractional amounts

* Let the slider go back to the min value when steps are large

* Limit length of input amount

* Allow inputting 0 to type decimal

* Add some margin
  • Loading branch information
harryttd authored Oct 6, 2023
1 parent ff3e9f4 commit 7affb55
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 85 deletions.
10 changes: 9 additions & 1 deletion getTez/getTez.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,10 @@ const verifySolution = async ({
}

/* Entrypoint */
const formatAmount = (amount: number) =>
amount.toLocaleString(undefined, {
maximumFractionDigits: 7,
})

const getTez = async (args: GetTezArgs) => {
const validatedArgs = await validateArgs(args)
Expand All @@ -317,7 +321,11 @@ const getTez = async (args: GetTezArgs) => {
)

if (!(args.amount >= minTez && args.amount <= maxTez)) {
handleError(`Amount must be between ${minTez} and ${maxTez} tez.`)
handleError(
`Amount must be between ${formatAmount(minTez)} and ${formatAmount(
maxTez
)} tez.`
)
}

if (!challengesEnabled) {
Expand Down
120 changes: 59 additions & 61 deletions src/components/Faucet/FaucetRequestButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import ReCAPTCHA from "react-google-recaptcha"

import PowWorker from "../../powWorker?worker&inline"
import Config from "../../Config"
import { autoSelectInputText } from "../../lib/Utils"
import {
Challenge,
ChallengeResponse,
Expand All @@ -15,6 +16,24 @@ import {
} from "../../lib/Types"

const { minTez, maxTez } = Config.application
// Compute the step for the Tezos amount range slider.
const tezRangeStep = (() => {
const magnitude = Math.floor(Math.log10(maxTez))

// When maxTez is greater than 1
if (maxTez > 1) {
return Math.max(0.5, Math.pow(10, magnitude - 2))
}

// When maxTez is less than or equal to 1 and minTez is fractional
const minMagnitude = Math.abs(Math.floor(Math.log10(minTez)))
return Math.max(0.001, 1 / Math.pow(10, minMagnitude))
})()

const formatAmount = (amount: number) =>
amount.toLocaleString(undefined, {
maximumFractionDigits: 5,
})

export default function FaucetRequestButton({
address,
Expand All @@ -28,10 +47,17 @@ export default function FaucetRequestButton({
status: StatusContext
}) {
const [amount, setAmount] = useState<number>(minTez)
const formattedAmount = amount.toLocaleString()
const formattedAmount = formatAmount(amount)

const [isLocalLoading, setLocalLoading] = useState<boolean>(false)
const recaptchaRef: RefObject<ReCAPTCHA> = useRef(null)

// Ensure that `isLocalLoading` is false if user canceled pow worker.
// `status.isLoading` will be false.
useEffect(() => {
!status.isLoading && setLocalLoading(false)
}, [status.isLoading])

const startLoading = () => {
status.setLoading(true)
setLocalLoading(true)
Expand All @@ -53,24 +79,29 @@ export default function FaucetRequestButton({
setLocalLoading(false)
}

const validateAmount = (amount: number) =>
amount >= minTez && amount <= maxTez

const updateAmount = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = Number(e.target.value)
if (value >= minTez && value <= maxTez) {
const value = Number(e.target.value.slice(0, 16))
if (value === 0 || validateAmount(value)) {
setAmount(value)
}
}

const validateChallenge = (data: Partial<Challenge>): data is Challenge =>
!!(
data.challenge &&
data.difficulty &&
data.challengeCounter &&
data.challengesNeeded
)

const getProgress = (challengeCounter: number, challengesNeeded: number) =>
String(
Math.min(99, Math.floor((challengeCounter / challengesNeeded) * 100))
)

// Ensure that `isLocalLoading` is false if user canceled pow worker.
// `status.isLoading` will be false.
useEffect(() => {
!status.isLoading && setLocalLoading(false)
}, [status.isLoading])

const execCaptcha = async () => {
const captchaToken: any = await recaptchaRef.current?.executeAsync()
recaptchaRef.current?.reset()
Expand Down Expand Up @@ -111,29 +142,17 @@ export default function FaucetRequestButton({
return verifySolution({ solution: "", nonce: 0 })
}

let { challenge, difficulty, challengeCounter, challengesNeeded } =
await getChallenge()
let challengeRes = await getChallenge()

const powWorker = new PowWorker()
status.setPowWorker(powWorker)

try {
while (
challenge &&
difficulty &&
challengeCounter &&
challengesNeeded
) {
const powSolution = await solvePow(
{ challenge, difficulty, challengeCounter, challengesNeeded },
powWorker
)

const response = await verifySolution(powSolution)
challenge = response.challenge
difficulty = response.difficulty
challengeCounter = response.challengeCounter
challengesNeeded = response.challengesNeeded
while (validateChallenge(challengeRes)) {
const powSolution = await solvePow(challengeRes, powWorker)

const newChallengeRes = await verifySolution(powSolution)
challengeRes = newChallengeRes
}
} finally {
powWorker.terminate()
Expand Down Expand Up @@ -162,7 +181,7 @@ export default function FaucetRequestButton({
{ timeout: 5000 }
)

if (data.challenge && data.difficulty && data.challengeCounter) {
if (validateChallenge(data)) {
return data
} else {
stopLoadingError(data?.message || "Error getting challenge")
Expand Down Expand Up @@ -195,7 +214,7 @@ export default function FaucetRequestButton({
)

// If there is another challenge
if (data.challenge && data.difficulty && data.challengeCounter) {
if (validateChallenge(data)) {
return data
} else if (data.txHash) {
// All challenges were solved
Expand All @@ -217,28 +236,7 @@ export default function FaucetRequestButton({
return {}
}

const computeStep = () => {
const magnitude = Math.floor(Math.log10(maxTez))

switch (magnitude) {
case 1:
case 2:
return 1
case 3:
return 10
case 4:
return 100
case 5:
return 1_000
case 6:
return 10_000
default:
return 100_000
}
}

const currentStep = computeStep()
const adjustedMin = Math.ceil(minTez / currentStep) * currentStep
const step = amount === tezRangeStep ? minTez : tezRangeStep

return (
<>
Expand All @@ -253,26 +251,22 @@ export default function FaucetRequestButton({
<Form.Label>Select Tez Amount</Form.Label>
<Row className="mb-2">
<Col xs="auto" className="pe-0">
<Form.Label className="fw-bold">
{minTez.toLocaleString()}
</Form.Label>
<Form.Label className="fw-bold">{formatAmount(minTez)}</Form.Label>
</Col>

<Col>
<Form.Range
min={adjustedMin}
min={step}
max={maxTez}
step={currentStep}
step={step}
value={amount}
disabled={disabled}
onChange={updateAmount}
/>
</Col>

<Col xs="auto" className="ps-0">
<Form.Label className="fw-bold">
{maxTez.toLocaleString()}
</Form.Label>
<Form.Label className="fw-bold">{formatAmount(maxTez)}</Form.Label>
</Col>
</Row>

Expand All @@ -285,12 +279,16 @@ export default function FaucetRequestButton({
value={amount}
disabled={disabled}
onChange={updateAmount}
onFocus={(e) => e.target.select()}
onClick={autoSelectInputText}
/>
</Col>

<Col xs={12} sm={6} className="d-flex justify-content-sm-end">
<Button variant="primary" disabled={disabled} onClick={getTez}>
<Button
variant="primary"
disabled={disabled || !validateAmount(amount)}
onClick={getTez}
>
<DropletFill />
&nbsp;
{isLocalLoading
Expand Down
6 changes: 4 additions & 2 deletions src/components/Faucet/FaucetToInputRequest.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { validateKeyHash } from "@taquito/utils"
import { ChangeEvent, useState } from "react"
import { Form } from "react-bootstrap"
import { Network, StatusContext } from "../../lib/Types"
import { autoSelectInputText } from "../../lib/Utils"
import FaucetRequestButton from "./FaucetRequestButton"

import { Network, StatusContext } from "../../lib/Types"

export default function FaucetToInputRequest({
network,
status,
Expand Down Expand Up @@ -40,7 +42,7 @@ export default function FaucetToInputRequest({
className={inputClass}
disabled={status.isLoading}
onChange={handleInput}
onFocus={(e) => e.target.select()}
onClick={autoSelectInputText}
/>
<Form.Control.Feedback type="invalid" className="position-absolute">
Invalid address
Expand Down
2 changes: 1 addition & 1 deletion src/components/Faucet/UserInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ function UserInfo({
displayBalance: boolean
}) {
return (
<Card bg="light" text="dark" className="d-inline-block mw-100">
<Card bg="light" text="dark" className="d-inline-block mw-100 mb-1">
<Card.Body className="d-flex align-items-center p-1">
<Wallet2 size={20} />
<span className="text-truncate fw-bold ms-2 me-2">
Expand Down
34 changes: 14 additions & 20 deletions src/lib/Utils.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
function minifyTezosAddress(address: string): string {
export const minifyTezosAddress = (address: string): string => {
if (address)
return `${address.substring(0, 4)}...${address.substring(
address.length - 4,
Expand All @@ -7,31 +7,25 @@ function minifyTezosAddress(address: string): string {
return address
}

function splitNumber(x: number) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")
}
const splitNumber = (x: number) =>
x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")

function roundBalance(balance: number): number {
return Math.trunc(balance / 10000) / 100
}
export const roundBalance = (balance: number): number =>
Math.trunc(balance / 10000) / 100

function toBalance(balance: number): string {
export const toBalance = (balance: number): string => {
let roundedBalance = roundBalance(balance)
return splitNumber(roundedBalance)
}

function getMainData(data: string): string {
return data.split("").reverse().join("")
}
export const getMainData = (data: string): string =>
data.split("").reverse().join("")

function getPlainData(data: string): string {
return Buffer.from(data, "base64").toString("utf8")
}
export const getPlainData = (data: string): string =>
Buffer.from(data, "base64").toString("utf8")

export {
minifyTezosAddress,
roundBalance,
getMainData,
getPlainData,
toBalance,
export const autoSelectInputText = (e: React.SyntheticEvent) => {
const el = e.target as HTMLInputElement
el.focus()
el.select()
}

0 comments on commit 7affb55

Please sign in to comment.