diff --git a/packages/react-app/src/components/claim-list.tsx b/packages/react-app/src/components/claim-list.tsx index a1a47a29..d609f9f7 100644 --- a/packages/react-app/src/components/claim-list.tsx +++ b/packages/react-app/src/components/claim-list.tsx @@ -1,4 +1,4 @@ -import { Accordion } from '@1hive/1hive-ui'; +import { Accordion, Box } from '@1hive/1hive-ui'; import { ClaimModel } from 'src/models/claim.model'; import { useWallet } from 'src/contexts/wallet.context'; import styled from 'styled-components'; @@ -8,6 +8,7 @@ import { TokenAmountModel } from 'src/models/token-amount.model'; import { QuestModel } from 'src/models/quest.model'; import { useEffect, useState } from 'react'; import { Logger } from 'src/utils/logger'; +import { getObjectFromIpfsSafe } from 'src/services/ipfs.service'; import ChallengeModal from './modals/challenge-modal'; import ResolveChallengeModal from './modals/resolve-challenge-modal'; import { ChildSpacer, Outset } from './utils/spacer-util'; @@ -39,6 +40,11 @@ const HeaderStyled = styled.h1` margin-left: ${GUpx()}; `; +const BoxStyled = styled(Box)` + display: flex; + justify-content: space-around; +`; + // #endregion type Props = { @@ -55,7 +61,9 @@ export default function ClaimList({ questTotalBounty, }: Props) { const { walletAddress } = useWallet(); - const [claims, setClaims] = useState(); + const [claims, setClaims] = useState([ + { state: ENUM_CLAIM_STATE.None } as ClaimModel, + ]); useEffect(() => { fetchClaims(); @@ -71,7 +79,17 @@ export default function ClaimList({ const fetchClaims = async () => { const result = await QuestService.fetchQuestClaims(questData); - setClaims(result); + setClaims(result); // Fetch visible data + setClaims( + await Promise.all( + result.map(async (claim) => ({ + ...claim, + evidence: claim.evidenceIpfsHash + ? await getObjectFromIpfsSafe(claim.evidenceIpfsHash) + : 'No evidence', + })), + ), + ); // Fetch evidence wich is currently hidden in accordion return result; }; @@ -89,75 +107,82 @@ export default function ClaimList({ return ( - {!!claims?.length && ( - <> - - Claims - - - { - let actionButton; - if (claim.state === ENUM_CLAIM_STATE.Scheduled) { - if (walletAddress === claim.playerAddress) - actionButton = ( - - ); - else - actionButton = ( - - ); - } else if (claim.state === ENUM_CLAIM_STATE.Challenged) { + + Claims + + + {claims?.length ? ( + { + let actionButton; + if (claim.state === ENUM_CLAIM_STATE.Scheduled) { + if (walletAddress === claim.playerAddress) actionButton = ( - + ); - } else if (!claim.state) Logger.error(`Claim doesn't have state`, { claim }); - return [ -
- - - - - - - {claim.claimedAmount.parsedAmount ? ( - - ) : ( - All available - )} - {walletAddress && actionButton} - - -
, - - - , - ]; - })} - /> - + ); + } else if (claim.state === ENUM_CLAIM_STATE.Challenged) { + actionButton = ; + } else if (!claim.state) Logger.error(`Claim doesn't have state`, { claim }); + return [ +
+ + + + + + + {claim.claimedAmount?.parsedAmount ? ( + + ) : ( + + All available + + )} + {walletAddress && actionButton} + + +
, + + + , + ]; + })} + /> + ) : ( + + No claims + )}
); diff --git a/packages/react-app/src/components/dashboard.tsx b/packages/react-app/src/components/dashboard.tsx index 5222228a..6740a3c2 100644 --- a/packages/react-app/src/components/dashboard.tsx +++ b/packages/react-app/src/components/dashboard.tsx @@ -22,7 +22,7 @@ const BoxStyled = styled.div` const TextStyled = styled.span` color: ${({ theme }: any) => theme.positive}; font-weight: bold !important; - ${textStyle('title3')}; + ${textStyle('title2')}; `; const SpacerStyled = styled.div` @@ -62,14 +62,19 @@ export default function Dashboard() { tooltip="Total of the quest bounties converted into USD" isLoading={!dashboardModel} > - $ {dashboardModel?.totalFunds} + + {dashboardModel?.totalFunds?.toLocaleString('en-US', { + style: 'currency', + currency: 'USD', + })} + Open Quests} tooltip="All the quests that are currently not expired or closed" isLoading={!dashboardModel} > - {dashboardModel?.questCount} + {dashboardModel?.questCount.toLocaleString()} {walletAddress && } diff --git a/packages/react-app/src/components/field-input/address-field-input.tsx b/packages/react-app/src/components/field-input/address-field-input.tsx index d2e78ea5..6ac14896 100644 --- a/packages/react-app/src/components/field-input/address-field-input.tsx +++ b/packages/react-app/src/components/field-input/address-field-input.tsx @@ -7,12 +7,12 @@ import { FieldInput } from './field-input'; // #region Styled const TextInputStyled = styled(TextInput)` - border-radius: 8px; + border-radius: 12px; padding-right: 42px; `; const EthIdenticonStyled = styled(EthIdenticon)` - border-radius: 0 8px 8px 0; + border-radius: 0 12px 12px 0; padding: 0; `; diff --git a/packages/react-app/src/components/field-input/amount-field-input.tsx b/packages/react-app/src/components/field-input/amount-field-input.tsx index 7db2df1d..a5d9481a 100644 --- a/packages/react-app/src/components/field-input/amount-field-input.tsx +++ b/packages/react-app/src/components/field-input/amount-field-input.tsx @@ -11,7 +11,6 @@ import { connect, FormikContextType } from 'formik'; import { noop } from 'lodash-es'; import React, { ReactNode, useEffect, useState, useRef, Fragment } from 'react'; import { NETWORK_TOKENS } from 'src/constants'; -import { useWallet } from 'src/contexts/wallet.context'; import { TokenAmountModel } from 'src/models/token-amount.model'; import { TokenModel } from 'src/models/token.model'; import { getNetwork } from 'src/networks'; @@ -128,7 +127,6 @@ function AmountFieldInput({ const [token, setToken] = useState(value?.token); const [availableTokens, setAvailableTokens] = useState([]); const [_hasFocused, _setHasFocused] = useState(); - const { walletAddress } = useWallet(); const tokenInputId = tokenEditable ? id : `token-${id}`; // Handle label for const amountInputId = !tokenEditable ? id : `amount-${id}`; // Handle label for @@ -138,30 +136,28 @@ function AmountFieldInput({ hasFocusedRef.current = data; _setHasFocused(data); }; - const autoCompleteRef: React.Ref = useRef(null); + + useEffect(() => { + fetchAvailableTokens(); + }, []); + const handleFocusIn = (e: FocusEvent) => { - if ( - document.activeElement === autoCompleteRef.current && - walletAddress && - isEdit && - tokenEditable - ) { + if (document.activeElement === autoCompleteRef.current && isEdit && tokenEditable) { setHasFocused(true); - fetchAvailableTokens(); } else if (document.activeElement !== autoCompleteRef.current && hasFocusedRef.current) { - formik?.setFieldTouched(id, true); - formik?.handleBlur(e); + formik?.handleBlur({ ...e, target: { id, name: id } }); setHasFocused(false); } }; + useEffect(() => { if (!token) document.addEventListener('focusin', handleFocusIn); return () => document.removeEventListener('focusin', handleFocusIn); - }, [walletAddress, isEdit, tokenEditable, token]); + }, [isEdit, tokenEditable, token]); useEffect(() => { - if (availableTokens.length) { + if (availableTokens.length && _hasFocused) { if (searchTerm && isAddress(searchTerm)) { setTokens([]); getTokenInfo(searchTerm) @@ -178,7 +174,7 @@ function AmountFieldInput({ ); } } - }, [searchTerm, availableTokens]); + }, [searchTerm, availableTokens, _hasFocused]); useEffect(() => { if (!isEdit) { @@ -203,7 +199,7 @@ function AmountFieldInput({ const onAmountChange = (e: any) => { const newAmount = e.target.value; setAmount(newAmount); - if (token && e.target.value !== '') { + if (token && newAmount !== '') { applyChanges({ token: { ...token, @@ -232,7 +228,6 @@ function AmountFieldInput({ if (formik) formik.setFieldValue(id, nextValue); else onChange(nextValue); }; - const amountField = ( @@ -244,6 +239,9 @@ function AmountFieldInput({ onChange={onAmountChange} placeHolder={placeHolder} onBlur={(e: React.FocusEvent) => { + const el = e.target as HTMLInputElement; + if (el.value === '') el.value = '0'; + onAmountChange(e); formik?.setFieldTouched(id, true); formik?.handleBlur(e); }} @@ -277,7 +275,7 @@ function AmountFieldInput({ onSelect={onTokenChange} ref={autoCompleteRef} onBlur={(e: FocusEvent) => formik?.handleBlur(e)} - placeholder="Search name or paste address" + placeholder={availableTokens.length ? 'Search name or paste address' : 'Loading tokens'} wide={wide} renderSelected={(i: number) => ( {tokens[i].name} @@ -312,6 +310,7 @@ function AmountFieldInput({ compact direction={!!amountLabel || !!tokenLabel ? 'column' : 'row'} error={error} + className={!isEdit ? 'fit-content' : 'dd'} > {reversed ? [tokenField, amountField] : [amountField, tokenField]} diff --git a/packages/react-app/src/components/field-input/checkbox-field-input.tsx b/packages/react-app/src/components/field-input/checkbox-field-input.tsx index f04cb50c..8170aaa2 100644 --- a/packages/react-app/src/components/field-input/checkbox-field-input.tsx +++ b/packages/react-app/src/components/field-input/checkbox-field-input.tsx @@ -1,6 +1,6 @@ import { useTheme } from '@1hive/1hive-ui'; import { noop } from 'lodash-es'; -import { ReactNode } from 'react'; +import { FocusEventHandler, ReactNode } from 'react'; import { GUpx } from 'src/utils/style.util'; import styled from 'styled-components'; import { FieldInput } from './field-input'; @@ -99,6 +99,7 @@ type Props = { isLoading?: boolean; label?: string; onChange?: Function; + handleBlur?: FocusEventHandler; value?: boolean; tooltip?: ReactNode; compact?: boolean; @@ -115,6 +116,7 @@ export default function CheckboxFieldInput({ compact = false, onChange = noop, disabled = false, + handleBlur = noop, }: Props) { const theme = useTheme(); return ( @@ -126,6 +128,7 @@ export default function CheckboxFieldInput({ name={id} value={id} checked={value} + onBlur={handleBlur} disabled={disabled || !isEdit} onChange={(e) => onChange(e)} /> diff --git a/packages/react-app/src/components/field-input/date-field-input.tsx b/packages/react-app/src/components/field-input/date-field-input.tsx index 61762854..94111005 100644 --- a/packages/react-app/src/components/field-input/date-field-input.tsx +++ b/packages/react-app/src/components/field-input/date-field-input.tsx @@ -53,7 +53,7 @@ type Props = { compact?: boolean; wide?: boolean; formik?: any; - onBlur?: FocusEventHandler & Function; + onBlur?: FocusEventHandler; error?: string | false; }; diff --git a/packages/react-app/src/components/field-input/help-tooltip.tsx b/packages/react-app/src/components/field-input/help-tooltip.tsx index a901ad3c..12463da5 100644 --- a/packages/react-app/src/components/field-input/help-tooltip.tsx +++ b/packages/react-app/src/components/field-input/help-tooltip.tsx @@ -50,7 +50,7 @@ export const HelpTooltip = ({ tooltip, children }: Props) => { onMouseLeave={handleLeave} ref={wrapperRef} > - + {children ?? tooltip} diff --git a/packages/react-app/src/components/main-view.tsx b/packages/react-app/src/components/main-view.tsx index 973bc3cd..ea4fa12b 100644 --- a/packages/react-app/src/components/main-view.tsx +++ b/packages/react-app/src/components/main-view.tsx @@ -31,25 +31,6 @@ const ContentWrapperStyled = styled.div<{ const ScrollViewStyled = styled.div` height: calc(100vh - 80px); // Minus header height overflow-y: auto; - /* custom scrollbar */ - &::-webkit-scrollbar { - width: 20px; - } - - &::-webkit-scrollbar-track { - background: transparent; - } - - &::-webkit-scrollbar-thumb { - background-color: #4a4a4a; - border-radius: 20px; - border: 6px solid transparent; - background-clip: content-box; - } - - &::-webkit-scrollbar-thumb:hover { - background-color: #a8bbbf; - } `; // #endregion diff --git a/packages/react-app/src/components/modals/challenge-modal.tsx b/packages/react-app/src/components/modals/challenge-modal.tsx index aff77ddc..2bb44d3f 100644 --- a/packages/react-app/src/components/modals/challenge-modal.tsx +++ b/packages/react-app/src/components/modals/challenge-modal.tsx @@ -34,12 +34,6 @@ const OpenButtonStyled = styled(Button)` width: fit-content; `; -const HeaderStyled = styled.div` - display: flex; - flex-direction: row; - align-items: center; -`; - const OpenButtonWrapperStyled = styled.div` display: flex; flex-direction: column; @@ -268,12 +262,7 @@ export default function ChallengeModal({ claim, challengeDeposit, onClose = noop return ( -

Challenge quests

- - - } + title="Challenge quests" openButton={ {buttonLabel && ( @@ -289,6 +278,7 @@ export default function ChallengeModal({ claim, challengeDeposit, onClose = noop {!loading && challengeTimeout === false && claim.executionTimeMs && ( )} + } buttons={[ @@ -358,8 +348,8 @@ export default function ChallengeModal({ claim, challengeDeposit, onClose = noop ); } }} + validateOnChange validate={validate} - validateOnBlur > {({ values, handleSubmit, handleChange, errors, touched, handleBlur }) => ( diff --git a/packages/react-app/src/components/modals/execute-claim-modal.tsx b/packages/react-app/src/components/modals/execute-claim-modal.tsx index 95d11acb..cb3b3f63 100644 --- a/packages/react-app/src/components/modals/execute-claim-modal.tsx +++ b/packages/react-app/src/components/modals/execute-claim-modal.tsx @@ -1,4 +1,4 @@ -import { Button, IconCoin, Field, Timer } from '@1hive/1hive-ui'; +import { Button, IconCoin, Timer } from '@1hive/1hive-ui'; import { noop, uniqueId } from 'lodash-es'; import { ReactNode, useEffect, useState } from 'react'; import { ENUM_CLAIM_STATE, ENUM, ENUM_TRANSACTION_STATUS } from 'src/constants'; @@ -14,6 +14,7 @@ import { AmountFieldInputFormik } from '../field-input/amount-field-input'; import { Outset } from '../utils/spacer-util'; import ModalBase, { ModalCallback } from './modal-base'; import { AddressFieldInput } from '../field-input/address-field-input'; +import { FieldInput } from '../field-input/field-input'; // #region StyledComponents @@ -132,9 +133,9 @@ export default function ExecuteClaimModal({ claim, questTotalBounty, onClose = n openButton={ {!loading && !scheduleTimeout && claim.executionTimeMs ? ( - + - + ) : ( setOpened(true)} diff --git a/packages/react-app/src/components/modals/fund-modal.tsx b/packages/react-app/src/components/modals/fund-modal.tsx index 8f04e2e1..1947f663 100644 --- a/packages/react-app/src/components/modals/fund-modal.tsx +++ b/packages/react-app/src/components/modals/fund-modal.tsx @@ -121,8 +121,8 @@ export default function FundModal({ quest, onClose = noop }: Props) { fundModalTx(values, setSubmitting); } }} + validateOnChange validate={validate} - validateOnBlur > {({ values, handleSubmit, handleChange, touched, errors }) => ( void; isOpen: boolean; css?: React.CSSProperties; - size?: 'small' | 'normal'; + size?: 'small' | 'normal' | 'large'; }; export default function ModalBase({ @@ -54,6 +53,16 @@ export default function ModalBase({ }: Props) { const openButtonId = `open-${id}`; const { transaction, setTransaction } = useTransactionContext(); + const width = useMemo(() => { + switch (size) { + case 'small': + return 500; + case 'large': + return 1200; + default: + return 800; + } + }, [size]); useEffect(() => { if (isOpen) { // Clear tx if a tx is still there and already completed @@ -108,9 +117,7 @@ export default function ModalBase({ handleOnClose(e)} - width={(viewport: VisualViewport) => - Math.min(viewport.width - 16, size === 'small' ? 500 : 1200) - } + width={(viewport: VisualViewport) => Math.min(viewport.width - 16, width)} style={css} id={id} tabIndex="-1" diff --git a/packages/react-app/src/components/modals/quest-modal.tsx b/packages/react-app/src/components/modals/quest-modal.tsx index ec966144..380f859f 100644 --- a/packages/react-app/src/components/modals/quest-modal.tsx +++ b/packages/react-app/src/components/modals/quest-modal.tsx @@ -3,7 +3,6 @@ import { noop } from 'lodash-es'; import { useEffect, useState } from 'react'; import { ENUM_QUEST_STATE, ENUM_QUEST_VIEW_MODE } from 'src/constants'; import { QuestModel } from 'src/models/quest.model'; -import { GUpx } from 'src/utils/style.util'; import styled from 'styled-components'; import * as QuestService from 'src/services/quest.service'; import { IN_A_WEEK_IN_MS } from 'src/utils/date.utils'; @@ -14,10 +13,6 @@ import { useQuestsContext } from '../../contexts/quests.context'; // #region StyledComponents -const QuestActionButtonStyled = styled(Button)` - margin: ${GUpx()}; -`; - const ButtonLinkStyled = styled(Button)` border: none; box-shadow: none; @@ -128,18 +123,6 @@ export default function QuestModal({ /> ) } - buttons={[ - (questMode === ENUM_QUEST_VIEW_MODE.Create || - questMode === ENUM_QUEST_VIEW_MODE.Update) && ( - - ), - ]} isOpen={opened} onClose={closeModal} > diff --git a/packages/react-app/src/components/modals/schedule-claim-modal.tsx b/packages/react-app/src/components/modals/schedule-claim-modal.tsx index 558fcccd..6e459d30 100644 --- a/packages/react-app/src/components/modals/schedule-claim-modal.tsx +++ b/packages/react-app/src/components/modals/schedule-claim-modal.tsx @@ -4,7 +4,7 @@ import { useState, useRef } from 'react'; import { GiBroadsword } from 'react-icons/gi'; import styled from 'styled-components'; import { Formik, Form } from 'formik'; -import { ENUM_TRANSACTION_STATUS, ENUM } from 'src/constants'; +import { ENUM_TRANSACTION_STATUS, ENUM, DEFAULT_CLAIM_EXECUTION_DELAY_MS } from 'src/constants'; import { TokenAmountModel } from 'src/models/token-amount.model'; import { ClaimModel } from 'src/models/claim.model'; import { useTransactionContext } from 'src/contexts/transaction.context'; @@ -17,17 +17,20 @@ import { computeTransactionErrorMessage } from 'src/utils/errors.util'; import { FormErrors } from 'src/models/form-errors'; import ModalBase, { ModalCallback } from './modal-base'; import * as QuestService from '../../services/quest.service'; -import { AmountFieldInputFormik } from '../field-input/amount-field-input'; +import AmountFieldInput, { AmountFieldInputFormik } from '../field-input/amount-field-input'; import TextFieldInput from '../field-input/text-field-input'; import { ChildSpacer, Outset } from '../utils/spacer-util'; import CheckboxFieldInput from '../field-input/checkbox-field-input'; import { AddressFieldInput } from '../field-input/address-field-input'; import { WalletBallance } from '../wallet-balance'; +import Stepper from '../utils/stepper'; // #region StyledComponents const FormStyled = styled(Form)` width: 100%; + padding: ${GUpx()}; + padding-bottom: 0; `; const OpenButtonStyled = styled(Button)` @@ -35,8 +38,9 @@ const OpenButtonStyled = styled(Button)` width: fit-content; `; -const LineStyled = styled.div` +const WrapperStyled = styled.div` display: flex; + flex-direction: column; align-content: center; `; @@ -202,31 +206,6 @@ export default function ScheduleClaimModal({ mode="positive" /> } - buttons={[ - , - , -