= (props) => {
dataTestId={dataTestId}
>
{
diff --git a/src/components/Text/Text.tsx b/src/components/Text/Text.tsx
index 0c26af1d..cc2339f2 100644
--- a/src/components/Text/Text.tsx
+++ b/src/components/Text/Text.tsx
@@ -1,19 +1,19 @@
import React from 'react'
import cx from 'classnames'
import intl from 'modules/intl'
-import { methods, constants, replaceReactComponents } from 'helpers'
+import { methods } from 'helpers'
+
+import { constants, replaceReactComponents } from '../../helpers'
const sizesMap = {
- text: [ 't20b', 't18m', 't18b', 't16m', 't16b', 't14', 't14m', 't14b', 't12', 't12m', 't12b' ],
- notes: [ 'n10', 'n10m', 'n10b' ],
- headers: [ 'h100', 'h90', 'h60', 'h48', 'h40', 'h32', 'h24', 'h22', 'h20' ],
+ text: [ 'lg', 'md', 'sm', 'xs' ],
+ headers: [ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' ],
} as const
export const sizes = [
...sizesMap.headers,
...sizesMap.text,
- ...sizesMap.notes,
] as const
export type TextSize = typeof sizes[number]
@@ -54,29 +54,19 @@ const Text: React.FC
= (props) => {
}
const rootClassName = cx(className, {
- 'text-h100': size === 'h100',
- 'text-h90': size === 'h90',
- 'text-h60': size === 'h60',
- 'text-h48': size === 'h48',
- 'text-h40': size === 'h40',
- 'text-h32': size === 'h32',
- 'text-h24': size === 'h24',
- 'text-h22': size === 'h22',
- 'text-h20': size === 'h20',
- 'text-t20b': size === 't20b',
- 'text-t18m': size === 't18m',
- 'text-t18b': size === 't18b',
- 'text-t16m': size === 't16m',
- 'text-t16b': size === 't16b',
- 'text-t14': size === 't14',
- 'text-t14m': size === 't14m',
- 'text-t14b': size === 't14b',
- 'text-t12': size === 't12',
- 'text-t12m': size === 't12m',
- 'text-t12b': size === 't12b',
- 'text-n10': size === 'n10',
- 'text-n10m': size === 'n10m',
- 'text-n10b': size === 'n10b',
+ // New sizes
+ 'text-h1': size === 'h1',
+ 'text-h2': size === 'h2',
+ 'text-h3': size === 'h3',
+ 'text-h4': size === 'h4',
+ 'text-h5': size === 'h5',
+ 'text-h6': size === 'h6',
+ 'font-heading': sizesMap.headers.includes(size as typeof sizesMap.headers[number]),
+
+ 'text-lg': size === 'lg',
+ 'text-md': size === 'md',
+ 'text-sm': size === 'sm',
+ 'text-xs': size === 'xs',
'text-light': color === 'light',
'text-dark': color === 'dark',
diff --git a/src/components/TextWithTooltip/TextWithTooltip.tsx b/src/components/TextWithTooltip/TextWithTooltip.tsx
index b1975488..c03562ad 100644
--- a/src/components/TextWithTooltip/TextWithTooltip.tsx
+++ b/src/components/TextWithTooltip/TextWithTooltip.tsx
@@ -8,39 +8,40 @@ import Text, { TextProps } from '../Text/Text'
export type TextWithTooltipProps = {
className?: string
- text: {
- className?: string
- message: Intl.Message | string
- color?: TextProps['color']
- size?: TextProps['size']
- dataTestId?: string
- }
- icon?: {
- size: IconProps['size']
- color: IconProps['color']
- }
+ message: Intl.Message | string
+ size?: TextProps['size']
+ color?: TextProps['color']
+ iconColor?: IconProps['color']
tooltip?: Intl.Message | string
+ inModal?: boolean
+ dataTestId?: string
}
const TextWithTooltip: React.FC = (props) => {
- const { className, text, icon, tooltip } = props
+ const { className, message, size, color, iconColor, tooltip, inModal, dataTestId } = props
return (
-
+
{
Boolean(tooltip) && (
-
+
diff --git a/src/components/Token/Token.tsx b/src/components/Token/Token.tsx
index 4086c79b..e08c0aee 100644
--- a/src/components/Token/Token.tsx
+++ b/src/components/Token/Token.tsx
@@ -25,8 +25,9 @@ const Token: React.FC = (props) => {
name={`token/${token}`}
/>
diff --git a/src/components/TokenAmount/TokenAmount.tsx b/src/components/TokenAmount/TokenAmount.tsx
index 0e57a304..e47d33fd 100644
--- a/src/components/TokenAmount/TokenAmount.tsx
+++ b/src/components/TokenAmount/TokenAmount.tsx
@@ -32,15 +32,15 @@ export type TokenAmountProps = {
const params = {
sm: {
logoSize: 16,
- textSize: 't14m',
+ textSize: 'sm',
},
md: {
- logoSize: 24,
- textSize: 't18m',
+ logoSize: 18,
+ textSize: 'md',
},
xl: {
- logoSize: 24,
- textSize: 'h20',
+ logoSize: 20,
+ textSize: 'lg',
},
} as const
@@ -69,7 +69,7 @@ const TokenAmount: React.FC = (props) => {
size={logoSize}
/>
void
-}
-
-const Balance: React.FC = (props) => {
- const { title, loading, value, dataTestId, onClick } = props
-
- const { isDesktop } = device.useData()
- const formattedBalance = methods.formatTokenValue(value || 0n)
-
- return (
-
-
- {
- Boolean(value) && typeof onClick === 'function' && (
-
-
-
- )
- }
-
- )
-}
-
-export default React.memo(Balance)
diff --git a/src/components/TokenAmountInput/Balance/messages.ts b/src/components/TokenAmountInput/Balance/messages.ts
deleted file mode 100644
index 2e782442..00000000
--- a/src/components/TokenAmountInput/Balance/messages.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-export default {
- balance: {
- en: 'Balance: {balance}',
- ru: 'Баланс: {balance}',
- fr: 'Solde : {balance}',
- es: 'Saldo: {balance}',
- pt: 'Saldo: {balance}',
- de: 'Kontostand: {balance}',
- zh: '余额:{balance}',
- },
- setMaxValue: {
- en: 'Set max value',
- ru: 'Установить макс. значение',
- fr: 'Définir la valeur maximale',
- es: 'Establecer valor máximo',
- pt: 'Definir valor máximo',
- de: 'Maximalwert festlegen',
- zh: '设定最大值',
- },
-}
diff --git a/src/components/TokenAmountInput/Input/Input.tsx b/src/components/TokenAmountInput/Input/Input.tsx
deleted file mode 100644
index e79fd5ee..00000000
--- a/src/components/TokenAmountInput/Input/Input.tsx
+++ /dev/null
@@ -1,69 +0,0 @@
-import React, { RefObject, useCallback } from 'react'
-import { methods } from 'helpers'
-
-import AmountInput from '../../AmountInput/AmountInput'
-
-
-type InputProps = {
- className?: string
- value?: string
- error?: boolean
- disabled?: boolean
- dataTestId?: string
- isRequired?: boolean
- onChange: (value: string) => void
-}
-
-const formatValue = (value: string | undefined) => {
- if (value === undefined) {
- return ''
- }
-
- const parts = value.split('.')
- parts[0] = methods.addNumberSeparator(parts[0])
-
- return parts.join(".")
-}
-
-const Input: React.FC = (props) => {
- const {
- value,
- error,
- disabled,
- className,
- isRequired,
- dataTestId,
- onChange,
- } = props
-
- const handleChange = useCallback((value: string, inputRef: RefObject) => {
- const valueWithOutCommas = value.replace(/,/g, '')
- const cursorPositionBeforeChange = inputRef.current?.selectionStart || 0
- const newValue = formatValue(valueWithOutCommas)
- const diff = newValue.length - value.length
-
- // Set cursor position after the value change
- setTimeout(() => {
- inputRef.current?.setSelectionRange(cursorPositionBeforeChange + diff, cursorPositionBeforeChange + diff)
- }, 0)
-
- if (typeof onChange === 'function') {
- onChange(valueWithOutCommas)
- }
- }, [ onChange ])
-
- return (
-
- )
-}
-
-
-export default React.memo(Input)
diff --git a/src/components/TokenAmountInput/Token/Token.tsx b/src/components/TokenAmountInput/Token/Token.tsx
deleted file mode 100644
index e9c5a2e9..00000000
--- a/src/components/TokenAmountInput/Token/Token.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import React from 'react'
-import cx from 'classnames'
-import device from 'modules/device'
-
-import Logo from '../../Logo/Logo'
-import Text from '../../Text/Text'
-import type { TokenAmountProps } from '../../TokenAmount/TokenAmount'
-
-
-type TokenProps = {
- className?: string
- token: TokenAmountProps['token']
- dataTestId?: string
-}
-
-const Token: React.FC = (props) => {
- const { className, token, dataTestId } = props
-
- const { isDesktop } = device.useData()
-
- return (
-
-
-
-
- )
-}
-
-
-export default React.memo(Token)
diff --git a/src/components/TokenAmountInput/TokenAmountInput.tsx b/src/components/TokenAmountInput/TokenAmountInput.tsx
index ecaf953b..25723bce 100644
--- a/src/components/TokenAmountInput/TokenAmountInput.tsx
+++ b/src/components/TokenAmountInput/TokenAmountInput.tsx
@@ -4,7 +4,7 @@ import Token from '../Token/Token'
import TokenAmountInputView, { TokenAmountInputViewProps } from '../TokenAmountInputView/TokenAmountInputView'
-export type TokenAmountInputProps = Omit
+export type TokenAmountInputProps = Omit
const TokenAmountInput: React.FC = (props) => {
const {
@@ -12,7 +12,7 @@ const TokenAmountInput: React.FC = (props) => {
label,
field,
balance,
- loading,
+ disabled,
dataTestId,
bottomNode,
onChange,
@@ -26,7 +26,7 @@ const TokenAmountInput: React.FC = (props) => {
className={className}
label={label}
field={field}
- loading={loading}
+ disabled={disabled}
balance={balance}
dataTestId={dataTestId}
bottomNode={bottomNode}
diff --git a/src/components/TokenAmountInputView/Balance/Balance.tsx b/src/components/TokenAmountInputView/Balance/Balance.tsx
index 96d3fcbd..48b1ff1d 100644
--- a/src/components/TokenAmountInputView/Balance/Balance.tsx
+++ b/src/components/TokenAmountInputView/Balance/Balance.tsx
@@ -2,6 +2,7 @@ import React from 'react'
import { methods } from 'helpers'
import device from 'modules/device'
+import Bone from '../../Bone/Bone'
import Text from '../../Text/Text'
import ButtonBase from '../../ButtonBase/ButtonBase'
@@ -11,13 +12,14 @@ import messages from './messages'
export type BalanceProps = {
title?: Intl.Message
value?: bigint | string
- loading?: boolean
+ disabled?: boolean
dataTestId?: string
+ isFetching?: boolean
onClick?: () => void
}
const Balance: React.FC = (props) => {
- const { title, loading, value, dataTestId, onClick } = props
+ const { title, disabled, value, dataTestId, isFetching, onClick } = props
const { isDesktop } = device.useData()
const formattedBalance = methods.formatTokenValue(value || 0n)
@@ -27,33 +29,44 @@ const Balance: React.FC = (props) => {
className="flex items-end"
data-testid={dataTestId}
>
-
{
- Boolean(Number(value)) && typeof onClick === 'function' && (
-
+ isFetching ? (
+
+ ) : (
+ <>
-
+ {
+ Boolean(Number(value)) && typeof onClick === 'function' && (
+
+
+
+ )
+ }
+ >
)
}
diff --git a/src/components/TokenAmountInputView/Balance/messages.ts b/src/components/TokenAmountInputView/Balance/messages.ts
index 2e782442..ba26af58 100644
--- a/src/components/TokenAmountInputView/Balance/messages.ts
+++ b/src/components/TokenAmountInputView/Balance/messages.ts
@@ -5,7 +5,7 @@ export default {
fr: 'Solde : {balance}',
es: 'Saldo: {balance}',
pt: 'Saldo: {balance}',
- de: 'Kontostand: {balance}',
+ de: 'Guthaben: {balance}',
zh: '余额:{balance}',
},
setMaxValue: {
diff --git a/src/components/TokenAmountInputView/TokenAmountInputView.tsx b/src/components/TokenAmountInputView/TokenAmountInputView.tsx
index 08160d7d..67c8f09e 100644
--- a/src/components/TokenAmountInputView/TokenAmountInputView.tsx
+++ b/src/components/TokenAmountInputView/TokenAmountInputView.tsx
@@ -17,12 +17,14 @@ export type TokenAmountInputViewProps = {
className?: string
field: Forms.Field
label?: Intl.Message | string
- loading?: boolean
+ disabled?: boolean
balance: {
title?: Intl.Message
value?: bigint
token: Tokens
units?: number
+ hidden?: boolean
+ isFetching?: boolean
}
dataTestId?: string
tokenNode: ReactNode
@@ -36,7 +38,7 @@ const TokenAmountInputView: React.FC = (props) => {
className,
label,
field,
- loading,
+ disabled,
balance,
tokenNode,
dataTestId,
@@ -54,36 +56,43 @@ const TokenAmountInputView: React.FC = (props) => {
return (
{
Boolean(label) && (
)
}
-
+
{
- if (loading) {
+ if (disabled) {
return
}
@@ -100,29 +109,36 @@ const TokenAmountInputView: React.FC
= (props) => {
className={cx('flex items-end w-full', {
'justify-between' : isDesktop,
'justify-end' : !isDesktop,
+ 'opacity-50': disabled,
})}
>
{
isDesktop && (
)
}
{
- typeof onMaxButtonClick === 'function' && (
+ !balance.hidden && (
{
- setSpecialFormat(null)
- onMaxButtonClick()
- }}
+ onClick={(
+ typeof onMaxButtonClick === 'function'
+ ? () => {
+ setSpecialFormat(null)
+ onMaxButtonClick()
+ }
+ : undefined
+ )}
/>
)
}
@@ -131,9 +147,9 @@ const TokenAmountInputView: React.FC = (props) => {
{
error && (
)
diff --git a/src/components/TokenDropdown/TokenOptions/Option/Option.tsx b/src/components/TokenDropdown/TokenOptions/Option/Option.tsx
index b226b459..33b5c71c 100644
--- a/src/components/TokenDropdown/TokenOptions/Option/Option.tsx
+++ b/src/components/TokenDropdown/TokenOptions/Option/Option.tsx
@@ -49,9 +49,10 @@ const Option: React.FC = (props) => {
{
address && (
@@ -59,23 +60,24 @@ const Option: React.FC = (props) => {
className="opacity-50"
message={methods.shortenAddress(address)}
color="dark"
- size="t12"
+ size="xs"
/>
)
}
= (props) => {
amount={formattedBalance}
token={name as Tokens}
color="dark"
- size="t12"
+ size="xs"
/>
diff --git a/src/components/TokenDropdown/TokenOptions/TokenOptions.tsx b/src/components/TokenDropdown/TokenOptions/TokenOptions.tsx
index 47849d95..d83eafc9 100644
--- a/src/components/TokenDropdown/TokenOptions/TokenOptions.tsx
+++ b/src/components/TokenDropdown/TokenOptions/TokenOptions.tsx
@@ -64,7 +64,7 @@ const TokenOptions: React.FC
= (props) => {
{
isFetching ? (
= (props) => {
) : (
filteredTokens.length ? (
5
@@ -114,10 +114,10 @@ const TokenOptions: React.FC = (props) => {
) : (
)
)
diff --git a/src/components/TokenDropdown/TokenOptions/TokenSkeleton/TokenSkeleton.tsx b/src/components/TokenDropdown/TokenOptions/TokenSkeleton/TokenSkeleton.tsx
index 5e246465..1ca0f8a1 100644
--- a/src/components/TokenDropdown/TokenOptions/TokenSkeleton/TokenSkeleton.tsx
+++ b/src/components/TokenDropdown/TokenOptions/TokenSkeleton/TokenSkeleton.tsx
@@ -24,13 +24,13 @@ const TokenSkeleton: React.FC = (props) => {
@@ -39,13 +39,13 @@ const TokenSkeleton: React.FC
= (props) => {
diff --git a/src/components/Tooltip/Tooltip.tsx b/src/components/Tooltip/Tooltip.tsx
index 15434681..2ab7881c 100644
--- a/src/components/Tooltip/Tooltip.tsx
+++ b/src/components/Tooltip/Tooltip.tsx
@@ -61,7 +61,7 @@ const Tooltip: React.FC
= props => {
dataTestId={`${dataTestId}-content`}
message={content as Intl.Message}
color="white"
- size="t12"
+ size="xs"
html
/>
)
diff --git a/src/components/Transactions/TransactionView/TransactionView.tsx b/src/components/Transactions/TransactionView/TransactionView.tsx
index 0a5a2dd9..6eee30a3 100644
--- a/src/components/Transactions/TransactionView/TransactionView.tsx
+++ b/src/components/Transactions/TransactionView/TransactionView.tsx
@@ -91,9 +91,10 @@ const TransactionView: React.FC = (props) => {
{
@@ -106,9 +107,10 @@ const TransactionView: React.FC = (props) => {
dataTestId={dataTestId ? `${dataTestId}-cancel` : undefined}
>
>
@@ -119,7 +121,7 @@ const TransactionView: React.FC = (props) => {
className="mt-4 opacity-60"
message={descriptions[status] as Intl.Message}
color="dark"
- size="t14"
+ size="sm"
/>
diff --git a/src/components/Transactions/TransactionView/messages.ts b/src/components/Transactions/TransactionView/messages.ts
index 01ae4df1..2e03df25 100644
--- a/src/components/Transactions/TransactionView/messages.ts
+++ b/src/components/Transactions/TransactionView/messages.ts
@@ -23,7 +23,7 @@ export default {
fr: 'Échoué',
es: 'Fallido',
pt: 'Falhou',
- de: 'Gescheitert',
+ de: 'Fehlgeschlagen',
zh: '失败',
},
processing: {
diff --git a/src/components/Transactions/Transactions.module.scss b/src/components/Transactions/Transactions.module.scss
index 46301759..4e5a94c4 100644
--- a/src/components/Transactions/Transactions.module.scss
+++ b/src/components/Transactions/Transactions.module.scss
@@ -3,7 +3,7 @@
&::before {
position: absolute;
- top: 38px;
+ top: 42px;
left: 15px;
width: 2px;
height: calc(100% - 4px);
diff --git a/src/components/Transactions/util/index.ts b/src/components/Transactions/util/index.ts
index 405a66bd..7df6284b 100644
--- a/src/components/Transactions/util/index.ts
+++ b/src/components/Transactions/util/index.ts
@@ -1,4 +1,4 @@
+export { TransactionStatus } from './useLogic'
export { default as useLogic } from './useLogic'
export { default as useAction } from './useAction'
-export { TransactionStatus } from './useLogic'
export type { Transaction, ModifiedTransaction } from './useLogic'
diff --git a/src/components/Transactions/util/useAction.ts b/src/components/Transactions/util/useAction.ts
index 32392d98..e51e678b 100644
--- a/src/components/Transactions/util/useAction.ts
+++ b/src/components/Transactions/util/useAction.ts
@@ -9,7 +9,7 @@ type ActionInput = {
cancelErrors?: string[]
isConfirmOptional?: boolean
action: () => Promise
- confirm: (values: { hash: string }) => Promise
+ confirm: (values: { hash: string }) => Promise
setTransaction: SetTransaction
}
diff --git a/src/components/UserApy/BoostPercent/BoostPercent.tsx b/src/components/UserApy/BoostPercent/BoostPercent.tsx
index 8aed513c..45fa31d3 100644
--- a/src/components/UserApy/BoostPercent/BoostPercent.tsx
+++ b/src/components/UserApy/BoostPercent/BoostPercent.tsx
@@ -85,11 +85,11 @@ const BoostPercent: React.FC = (props) => {
)
}
)
diff --git a/src/components/UserApy/UserApy.tsx b/src/components/UserApy/UserApy.tsx
index 9a383219..28b72993 100644
--- a/src/components/UserApy/UserApy.tsx
+++ b/src/components/UserApy/UserApy.tsx
@@ -26,10 +26,11 @@ const UserApy: React.FC = (props) => {
if (!isBoosted) {
return (
)
}
diff --git a/src/layouts/AppLayout/BottomLoader/BottomLoader.tsx b/src/layouts/AppLayout/BottomLoader/BottomLoader.tsx
index 79ab5c2c..16ce733c 100644
--- a/src/layouts/AppLayout/BottomLoader/BottomLoader.tsx
+++ b/src/layouts/AppLayout/BottomLoader/BottomLoader.tsx
@@ -63,9 +63,9 @@ const BottomLoader: React.FC = (): React.ReactPortal | null => {
size={16}
/>
diff --git a/src/layouts/AppLayout/Header/Connect/Account/AccountMenu/Address/Address.tsx b/src/layouts/AppLayout/Header/Connect/Account/AccountMenu/Address/Address.tsx
index d894abb7..98b67242 100644
--- a/src/layouts/AppLayout/Header/Connect/Account/AccountMenu/Address/Address.tsx
+++ b/src/layouts/AppLayout/Header/Connect/Account/AccountMenu/Address/Address.tsx
@@ -37,10 +37,10 @@ const Address: React.FC = (props) => {
size={16}
/>
diff --git a/src/layouts/AppLayout/Header/Connect/Account/AccountMenu/Balances/Balance/Balance.tsx b/src/layouts/AppLayout/Header/Connect/Account/AccountMenu/Balances/Balance/Balance.tsx
index bcc054c4..53ecee39 100644
--- a/src/layouts/AppLayout/Header/Connect/Account/AccountMenu/Balances/Balance/Balance.tsx
+++ b/src/layouts/AppLayout/Header/Connect/Account/AccountMenu/Balances/Balance/Balance.tsx
@@ -72,9 +72,10 @@ const Balance: React.FC
= (props) => {
{
withAddTokenButton && (
@@ -105,11 +106,11 @@ const Balance: React.FC = (props) => {
dataTestId={`${token}-amount`}
/>
diff --git a/src/layouts/AppLayout/Header/Connect/Account/AccountMenu/Menu/MenuItem/MenuItem.tsx b/src/layouts/AppLayout/Header/Connect/Account/AccountMenu/Menu/MenuItem/MenuItem.tsx
index 43efe98a..a2b56423 100644
--- a/src/layouts/AppLayout/Header/Connect/Account/AccountMenu/Menu/MenuItem/MenuItem.tsx
+++ b/src/layouts/AppLayout/Header/Connect/Account/AccountMenu/Menu/MenuItem/MenuItem.tsx
@@ -18,9 +18,10 @@ const MenuItem: React.FC
= (props) => {
const itemNode = (
= (props) => {
>
)
diff --git a/src/layouts/AppLayout/Header/Settings/util/useAppConfig.ts b/src/layouts/AppLayout/Header/Settings/util/useAppConfig.ts
index 27956dc0..b4fc028f 100644
--- a/src/layouts/AppLayout/Header/Settings/util/useAppConfig.ts
+++ b/src/layouts/AppLayout/Header/Settings/util/useAppConfig.ts
@@ -58,19 +58,19 @@ const useAppConfig = () => {
const themeOptions = useMemo(() => [
{
- logo: 'theme/sun',
+ icon: 'theme/sun',
value: ThemeValue.Light,
title: messages.theme.light,
},
{
- logo: 'theme/moon',
+ icon: 'theme/moon',
value: ThemeValue.Dark,
title: messages.theme.dark,
},
{
value: ThemeValue.System,
title: messages.theme.system,
- logo: systemTheme === ThemeColor.Dark ? 'theme/moon' : 'theme/sun',
+ icon: systemTheme === ThemeColor.Dark ? 'theme/moon' : 'theme/sun',
},
], [ systemTheme ])
@@ -132,3 +132,4 @@ const useAppConfig = () => {
export default useAppConfig
+
diff --git a/src/layouts/modals/ConnectWalletModal/ConnectWalletModal.tsx b/src/layouts/modals/ConnectWalletModal/ConnectWalletModal.tsx
index 3694d58d..5f500929 100644
--- a/src/layouts/modals/ConnectWalletModal/ConnectWalletModal.tsx
+++ b/src/layouts/modals/ConnectWalletModal/ConnectWalletModal.tsx
@@ -60,8 +60,8 @@ export const [ ConnectWalletModal, openConnectWalletModal, closeConnectWalletMod
className="inline-block font-medium"
dataTestId="select-wallet-modal-no-wallet-button"
message={messages.noWallet}
- size="t14m"
color="inherit"
+ size="sm"
/>
diff --git a/src/layouts/modals/ConnectWalletModal/ConnectorsView/ConnectorButton/ConnectorButton.tsx b/src/layouts/modals/ConnectWalletModal/ConnectorsView/ConnectorButton/ConnectorButton.tsx
index 4452ff1a..a122fb1a 100644
--- a/src/layouts/modals/ConnectWalletModal/ConnectorsView/ConnectorButton/ConnectorButton.tsx
+++ b/src/layouts/modals/ConnectWalletModal/ConnectorsView/ConnectorButton/ConnectorButton.tsx
@@ -34,11 +34,11 @@ const ConnectorButton: React.FC = (props) => {
/>
)
@@ -66,11 +66,11 @@ const ConnectorButton: React.FC = (props) => {
}
)
diff --git a/src/layouts/modals/DistributorClaimsModal/DistributorClaimsModal.tsx b/src/layouts/modals/DistributorClaimsModal/DistributorClaimsModal.tsx
index 73810a7f..caa8aee3 100644
--- a/src/layouts/modals/DistributorClaimsModal/DistributorClaimsModal.tsx
+++ b/src/layouts/modals/DistributorClaimsModal/DistributorClaimsModal.tsx
@@ -127,8 +127,9 @@ export const [ DistributorClaimsModal, openDistributorClaimsModal ] = (
})}
>
@@ -138,11 +139,11 @@ export const [ DistributorClaimsModal, openDistributorClaimsModal ] = (
size="sm"
/>
diff --git a/src/layouts/modals/GuideModal/Slider/Content/Content.tsx b/src/layouts/modals/GuideModal/Slider/Content/Content.tsx
index 3cb4bcbb..00cfc77c 100644
--- a/src/layouts/modals/GuideModal/Slider/Content/Content.tsx
+++ b/src/layouts/modals/GuideModal/Slider/Content/Content.tsx
@@ -53,12 +53,12 @@ const Content: React.FC
= (props) => {
}}
>
diff --git a/src/layouts/modals/SwitchAccountModal/AccountItem/AccountItem.tsx b/src/layouts/modals/SwitchAccountModal/AccountItem/AccountItem.tsx
index 0486cb5f..0d13ab9a 100644
--- a/src/layouts/modals/SwitchAccountModal/AccountItem/AccountItem.tsx
+++ b/src/layouts/modals/SwitchAccountModal/AccountItem/AccountItem.tsx
@@ -31,9 +31,10 @@ const AccountItem: React.FC = (props) => {
onClick={handleClick}
>
{
Boolean(balance) ? (
@@ -45,11 +46,11 @@ const AccountItem: React.FC = (props) => {
size="md"
/>
>
) : (
diff --git a/src/layouts/modals/SwitchAccountModal/SwitchAccountModal.tsx b/src/layouts/modals/SwitchAccountModal/SwitchAccountModal.tsx
index b5a04fd0..f3ad0304 100644
--- a/src/layouts/modals/SwitchAccountModal/SwitchAccountModal.tsx
+++ b/src/layouts/modals/SwitchAccountModal/SwitchAccountModal.tsx
@@ -59,7 +59,7 @@ export const [ SwitchAccountModal, openSwitchAccountModal ] = (
className="opacity-60"
message={commonMessages.loading}
color="dark"
- size="t14"
+ size="sm"
/>
) : (
<>
@@ -68,7 +68,7 @@ export const [ SwitchAccountModal, openSwitchAccountModal ] = (
message={messages.note}
color="dark"
tag="span"
- size="t14"
+ size="sm"
/>
>
diff --git a/src/layouts/modals/TxCompletedModal/FailedToken/FailedToken.tsx b/src/layouts/modals/TxCompletedModal/FailedToken/FailedToken.tsx
index 8838344e..501d15d6 100644
--- a/src/layouts/modals/TxCompletedModal/FailedToken/FailedToken.tsx
+++ b/src/layouts/modals/TxCompletedModal/FailedToken/FailedToken.tsx
@@ -32,13 +32,13 @@ const FailedToken: React.FC = (props) => {
className={cx(className, 'flex items-center justify-between border-top border-dark/10 py-16')}
>
@@ -53,7 +53,7 @@ const FailedToken: React.FC = (props) => {
amount={value}
token={token}
color="dark"
- size="t12"
+ size="xs"
/>
diff --git a/src/layouts/modals/TxCompletedModal/Token/Token.tsx b/src/layouts/modals/TxCompletedModal/Token/Token.tsx
index a2139cbf..13241dbd 100644
--- a/src/layouts/modals/TxCompletedModal/Token/Token.tsx
+++ b/src/layouts/modals/TxCompletedModal/Token/Token.tsx
@@ -40,13 +40,13 @@ const Token: React.FC = (props) => {
className={cx(className, 'flex items-center justify-between border-top border-dark/10 py-16')}
>
@@ -61,7 +61,7 @@ const Token: React.FC = (props) => {
amount={value}
token={token}
color="dark"
- size="t12"
+ size="xs"
/>
diff --git a/src/modules/cache-storage/CacheStorage.spec.ts b/src/modules/cache-storage/CacheStorage.spec.ts
new file mode 100644
index 00000000..132149f9
--- /dev/null
+++ b/src/modules/cache-storage/CacheStorage.spec.ts
@@ -0,0 +1,26 @@
+import CacheStorage from './CacheStorage'
+
+
+describe('helpers/getter/CacheStorage', () => {
+ const cacheId = 'KEY'
+ const cache = CacheStorage.get(cacheId)
+
+ it('should set and get value by key', () => {
+ cache.setData('DATA')
+
+ expect(cache.getData()).toBe('DATA')
+ })
+
+ it('should reset value by key', () => {
+ cache.resetData()
+ expect(cache.getData()).toBe(null)
+ })
+
+ it('should reset value after timeout', async () => {
+ cache.setData('DATA', 1000)
+ expect(cache.getData()).toBe('DATA')
+
+ await new Promise((r) => setTimeout(r, 2000))
+ expect(cache.getData()).toBe(null)
+ })
+})
diff --git a/src/modules/cache-storage/useCacheListener.spec.ts b/src/modules/cache-storage/useCacheListener.spec.ts
new file mode 100644
index 00000000..77f46509
--- /dev/null
+++ b/src/modules/cache-storage/useCacheListener.spec.ts
@@ -0,0 +1,83 @@
+import { act, renderHook } from '@testing-library/react'
+import CacheStorage from './CacheStorage'
+
+import useCacheListener from './useCacheListener'
+
+
+describe('cache-storage/useCacheListener', () => {
+ const cacheId = 'KEY'
+ const cache = CacheStorage.get(cacheId)
+
+ it('should return correct initial "data"', () => {
+ cache.setData('DATA')
+ const { result } = renderHook(() => useCacheListener(cacheId))
+ const [ data ] = result.current
+
+ expect(data).toBe('DATA')
+ })
+
+ it('should return correct "data" after set', () => {
+ const { result } = renderHook(() => useCacheListener(cacheId))
+ const [ _, setData ] = result.current
+
+ act(() => {
+ setData('DATA2')
+ })
+
+ const [ state ] = result.current
+
+ expect(state).toBe('DATA2')
+ })
+
+ it('should return correct "data" after time is up', async () => {
+ const { result } = renderHook(() => useCacheListener(cacheId))
+ const [ _, setData ] = result.current
+
+ act(() => {
+ setData('DATA3', 1000)
+ })
+
+ await new Promise((r) => setTimeout(r, 2000))
+ const [ state ] = result.current
+
+ expect(state).toBe(null)
+ })
+
+ it('should return correct "data" if cache was canged from storage', async () => {
+ const { result } = renderHook(() => useCacheListener(cacheId))
+ const [ _, setData ] = result.current
+
+ act(() => {
+ setData('DATA4')
+ })
+
+ const [ firstRenderState ] = result.current
+ expect(firstRenderState).toBe('DATA4')
+
+ act(() => {
+ cache.setData('DATA5')
+ })
+
+ const [ secondRenderState ] = result.current
+ expect(secondRenderState).toBe('DATA5')
+ })
+
+ it('should return correct "data" if cache was deleted from storage', async () => {
+ const { result } = renderHook(() => useCacheListener(cacheId))
+ const [ _, setData ] = result.current
+
+ act(() => {
+ setData('DATA6')
+ })
+
+ const [ firstRenderState ] = result.current
+ expect(firstRenderState).toBe('DATA6')
+
+ act(() => {
+ cache.resetData()
+ })
+
+ const [ secondRenderState ] = result.current
+ expect(secondRenderState).toBe(null)
+ })
+})
diff --git a/src/modules/device/_SSR/getCookieDevice.ts b/src/modules/device/_SSR/getCookieDevice.ts
index c1d26b15..e3b48cb2 100644
--- a/src/modules/device/_SSR/getCookieDevice.ts
+++ b/src/modules/device/_SSR/getCookieDevice.ts
@@ -1,8 +1,9 @@
-import { cookies } from 'next/headers'
import * as constants from 'helpers/constants'
const getCookieDevice = async () => {
+ const { cookies } = await import('next/headers')
+
const cookiesState = await cookies()
const cookieDevice = cookiesState.get(constants.cookieNames.device)?.value
diff --git a/src/modules/device/_SSR/getUserAgentDevice.ts b/src/modules/device/_SSR/getUserAgentDevice.ts
index 64388f9f..13315fab 100644
--- a/src/modules/device/_SSR/getUserAgentDevice.ts
+++ b/src/modules/device/_SSR/getUserAgentDevice.ts
@@ -1,8 +1,9 @@
import { UAParser } from 'ua-parser-js'
-import { headers } from 'next/headers'
const getUserAgentDevice = async () => {
+ const { headers } = await import('next/headers')
+
const headersList = await headers()
const userAgent = headersList.get('user-agent') || ''
const serverUI = new UAParser(userAgent)
diff --git a/src/modules/device/_SSR/index.ts b/src/modules/device/_SSR/index.ts
index 069e3aa6..5323ed0a 100644
--- a/src/modules/device/_SSR/index.ts
+++ b/src/modules/device/_SSR/index.ts
@@ -2,7 +2,20 @@ import getCookieDevice from './getCookieDevice'
import getUserAgentDevice from './getUserAgentDevice'
+const isIpfs = process.env.NEXT_PUBLIC_IPFS === 'true'
+
+const defaultDevice: Device.Context = {
+ isTablet: false,
+ isMobile: false,
+ isDesktop: true,
+ isCalculated: false,
+}
+
const getServerDevice = async (): Promise => {
+ if (isIpfs) {
+ return defaultDevice
+ }
+
const [ cookieDevice, userAgentDevice ] = await Promise.all([
getCookieDevice(),
getUserAgentDevice(),
diff --git a/src/modules/forms/Field.spec.ts b/src/modules/forms/Field.spec.ts
new file mode 100644
index 00000000..648b9067
--- /dev/null
+++ b/src/modules/forms/Field.spec.ts
@@ -0,0 +1,153 @@
+import { parseEther } from 'ethers'
+
+import Field from './Field'
+import validators from './validators'
+
+
+const requiredError = JSON.stringify(validators.required(''))
+
+describe('forms/Field', () => {
+
+ describe('Check init params', () => {
+
+ it(`date mask`, () => {
+ const field = new Field({
+ valueType: 'string',
+ mask: 'date',
+ })
+
+ field.setValue('20210919')
+
+ expect(field.value).toEqual('2021-09-19')
+ })
+
+ it(`validators`, () => {
+ const field = new Field({ valueType: 'string', validators: [ validators.required ] })
+
+ field.validate('')
+
+ expect(JSON.stringify(field.error)).toEqual(requiredError)
+ })
+
+ it(`pattern`, () => {
+ const field = new Field({
+ valueType: 'string',
+ pattern: '^[0-9]+$',
+ })
+
+ field.setValue('asd')
+ expect(field.value).toEqual(undefined)
+
+ field.setValue('AGH')
+ expect(field.value).toEqual(undefined)
+
+ field.setValue('./{}]][..')
+ expect(field.value).toEqual(undefined)
+
+ field.setValue('123')
+ expect(field.value).toEqual('123')
+ })
+
+ it(`initial value`, () => {
+ const field = new Field({ valueType: 'string', initialValue: '333' })
+
+ expect(field.value).toEqual('333')
+ })
+ })
+
+ describe('Check input types', () => {
+
+ it(`date`, () => {
+ const field = new Field({
+ valueType: 'string',
+ type: 'date',
+ })
+
+ field.validate('')
+ expect(JSON.stringify(field.error)).toEqual(requiredError)
+
+ field.setValue('20210919')
+ expect(field.value).toEqual('2021-09-19')
+ })
+
+ it(`wallet address`, () => {
+ const field = new Field({
+ valueType: 'string',
+ type: 'address',
+ })
+
+ field.validate('')
+ expect(JSON.stringify(field.error)).toEqual(requiredError)
+
+ field.setValue('agfgdsasd')
+
+ const addressError = JSON.stringify(validators.ethOrEnsAddress(field.value))
+ expect(JSON.stringify(field.error)).toEqual(addressError)
+
+ field.setValue('0x4Fe7Cb62C98Ca5F71011d0DA0DAC680b832b858c')
+ expect(field.error).toEqual(null)
+
+ field.setValue('powerstake.eth')
+ expect(field.error).toEqual(null)
+ })
+
+ it(`token amount`, () => {
+ const field = new Field({
+ valueType: 'string',
+ type: 'tokenAmount',
+ })
+
+ field.validate('')
+ expect(JSON.stringify(field.error)).toEqual(requiredError)
+
+ field.setValue('0..0')
+ expect(field.value).toEqual(undefined)
+
+ field.setValue('0')
+ const zeroError = JSON.stringify(validators.greaterThanZero(field.value))
+ expect(JSON.stringify(field.error)).toEqual(zeroError)
+
+ field.setValue('0.00001')
+ expect(field.error).toEqual(null)
+ })
+
+ it(`bigint`, () => {
+ const field = new Field({ valueType: 'bigint' })
+
+ field.setValue('0')
+ expect(field.value).toEqual(undefined)
+
+ field.setValue(false)
+ expect(field.value).toEqual(undefined)
+
+ field.setValue(11)
+ expect(field.value).toEqual(undefined)
+
+ field.setValue(100n)
+ expect(field.value).toEqual(100n)
+ })
+
+ it(`token amount with balance ref`, () => {
+ const balanceRef = {
+ current: parseEther('1.01'),
+ }
+
+ const field = new Field({
+ type: 'tokenAmount',
+ valueType: 'string',
+ typeInput: { balanceRef },
+ })
+
+ field.validate('1.02')
+
+ const balanceError = JSON.stringify(
+ validators.sufficientBalance(balanceRef)('1.02')
+ )
+
+ expect(JSON.stringify(field.error)).toEqual(balanceError)
+
+ field.setValue('1')
+ expect(field.error).toEqual(null)
+ })
+ })
+})
diff --git a/src/modules/forms/Field.ts b/src/modules/forms/Field.ts
index 46dc538a..f45c03ac 100644
--- a/src/modules/forms/Field.ts
+++ b/src/modules/forms/Field.ts
@@ -7,12 +7,12 @@ import typesParams from './helpers/typesParams'
class Field implements Forms.Field {
+ #initialValue?: V
#value: V | undefined
#error: Forms.Field['error']
- readonly #mask?: 'date'
+ readonly #mask?: 'date' | 'time'
readonly #group?: string
- readonly #initialValue?: V
readonly #pattern?: string
readonly #isRequired: boolean
readonly #events: EventAggregator
@@ -147,6 +147,10 @@ class Field implements Forms.Field {
this.#events.dispatch('error', this)
}
+ public setInitialValue(value: V | undefined) {
+ this.#initialValue = value
+ }
+
public subscribe(event: Forms.Events, handler: Forms.EventHandler) {
this.#events.subscribe(event, handler)
diff --git a/src/modules/forms/index.ts b/src/modules/forms/index.ts
index da77a81b..db42638e 100644
--- a/src/modules/forms/index.ts
+++ b/src/modules/forms/index.ts
@@ -26,6 +26,8 @@ declare global {
setError(error: Error): void
+ setInitialValue(value: V): void
+
validateWithoutError(): boolean
setValue(value: V | undefined): void
@@ -62,7 +64,7 @@ declare global {
}
type FieldConfig = {
- mask?: 'date'
+ mask?: 'date' | 'time'
group?: string
initialValue?: V
pattern?: string
@@ -125,6 +127,7 @@ import useFormFilled from './useFormFilled'
import validators from './validators'
import useField from './useField'
import useForm from './useForm'
+import masks from './masks'
import Field from './Field'
@@ -139,5 +142,6 @@ export default {
validators,
useField,
useForm,
+ masks,
Field,
}
diff --git a/src/modules/forms/masks/createMask.spec.ts b/src/modules/forms/masks/createMask.spec.ts
new file mode 100644
index 00000000..008dd744
--- /dev/null
+++ b/src/modules/forms/masks/createMask.spec.ts
@@ -0,0 +1,49 @@
+import { createMask } from './index'
+
+
+describe('forms/masks', () => {
+
+ it(`Check phone mask`, () => {
+ const mask = createMask('+1 (XXX) XXX-XX-XX')
+
+ expect(mask('9')).toEqual('+1 (9')
+ expect(mask('92')).toEqual('+1 (92')
+ expect(mask('922')).toEqual('+1 (922')
+ expect(mask('9224')).toEqual('+1 (922) 4')
+ expect(mask('92244')).toEqual('+1 (922) 44')
+ expect(mask('922444')).toEqual('+1 (922) 444')
+ expect(mask('9224442')).toEqual('+1 (922) 444-2')
+ expect(mask('92244422')).toEqual('+1 (922) 444-22')
+ expect(mask('922444224')).toEqual('+1 (922) 444-22-4')
+ expect(mask('9224442244')).toEqual('+1 (922) 444-22-44')
+ expect(mask('922444224413131313')).toEqual('+1 (922) 444-22-44')
+ })
+
+ it(`Check date mask`, () => {
+ const mask = createMask('XXXX-XX-XX')
+
+ expect(mask('2')).toEqual('2')
+ expect(mask('20')).toEqual('20')
+ expect(mask('202')).toEqual('202')
+ expect(mask('2024')).toEqual('2024')
+ expect(mask('20241')).toEqual('2024-1')
+ expect(mask('202412')).toEqual('2024-12')
+ expect(mask('2024121')).toEqual('2024-12-1')
+ expect(mask('20241212')).toEqual('2024-12-12')
+ expect(mask('202412121313')).toEqual('2024-12-12')
+ })
+
+ it(`Check card mask`, () => {
+ const mask = createMask('XXXX XXXX XXXX XXXX')
+
+ expect(mask('1')).toEqual('1')
+ expect(mask('12')).toEqual('12')
+ expect(mask('123')).toEqual('123')
+ expect(mask('1234')).toEqual('1234')
+ expect(mask('12345')).toEqual('1234 5')
+ expect(mask('123456')).toEqual('1234 56')
+ expect(mask('1234567')).toEqual('1234 567')
+ expect(mask('12345678')).toEqual('1234 5678')
+ expect(mask('1234567890123456')).toEqual('1234 5678 9012 3456')
+ })
+})
diff --git a/src/modules/forms/masks/index.ts b/src/modules/forms/masks/index.ts
index a00b101a..889b968f 100644
--- a/src/modules/forms/masks/index.ts
+++ b/src/modules/forms/masks/index.ts
@@ -57,6 +57,46 @@ const createMaskWithModifiers: CreateMaskWithModifiers = ({ mask, preModify, pos
}
}
+// Mask result - HH:mm
+const initTimeMask = () => (raw: string): string => {
+ const digits = raw.replace(/\D/g, '').slice(0, 4)
+
+ if (!digits.length) {
+ return ''
+ }
+
+ if (digits.length === 1) {
+ const firstDigit = Number(digits[0])
+
+ if (firstDigit > 2) {
+ return `0${digits[0]}:`
+ }
+
+ return digits
+ }
+
+ let hour = digits.slice(0, 2)
+
+ if (Number(hour) > 23) {
+ hour = '23'
+ }
+
+ if (digits.length === 2) {
+ return hour
+ }
+
+ let minute = digits.slice(2, 4)
+
+ if (minute.length === 1 && Number(minute) > 5) {
+ minute = `0${minute}`
+ }
+ else if (minute.length === 2 && Number(minute) > 59) {
+ minute = '59'
+ }
+
+ return `${hour}:${minute}`
+}
+
// Mask result - YYYY-MM-DD
const initDateMask = (): Mask => {
const preModify = (value: string) => value.replace(/\D/g, '')
@@ -106,4 +146,5 @@ const initDateMask = (): Mask => {
export default {
date: initDateMask(),
+ time: initTimeMask(),
}
diff --git a/src/modules/forms/useFormValues.ts b/src/modules/forms/useFormValues.ts
index 78de48c8..97d6c683 100644
--- a/src/modules/forms/useFormValues.ts
+++ b/src/modules/forms/useFormValues.ts
@@ -1,3 +1,4 @@
+'use client'
import { useState, useCallback, useEffect, useMemo } from 'react'
diff --git a/src/modules/forms/validators/index.ts b/src/modules/forms/validators/index.ts
index e5112512..9c0d74db 100644
--- a/src/modules/forms/validators/index.ts
+++ b/src/modules/forms/validators/index.ts
@@ -73,7 +73,7 @@ const ethAddress = (value: Value) => {
}
const ethOrEnsAddress = (value: Value) => {
- if (typeof value === 'string' && !isAddress(value) && !ensAddressRegex.test(value)) {
+ if (typeof value === 'string' && value && !isAddress(value) && !ensAddressRegex.test(value)) {
return messages.ethOrEnsAddress
}
}
@@ -84,12 +84,6 @@ const email = (value: Value) => {
}
}
-const numberWithDot = (value: Value) => {
- if (!isValidNumberWithDot(value)) {
- return messages.invalidNumberWithDot
- }
-}
-
const httpsUrl = (value: Value) => {
if (typeof value !== 'string' || !value) {
return
@@ -107,6 +101,12 @@ const httpsUrl = (value: Value) => {
}
}
+const numberWithDot = (value: Value) => {
+ if (!isValidNumberWithDot(value)) {
+ return messages.invalidNumberWithDot
+ }
+}
+
const sufficientBalance = (balance: RefObject) => (value: Value) => {
if (value && !hasSufficientBalance(balance, value)) {
return messages.insufficientBalance
@@ -177,7 +177,7 @@ const number = (value: Value) => {
}
const validDate = (value: Value) => {
- if (typeof value === 'string' && !/^\d\d\d\d-\d\d-\d\d$/.test(value)) {
+ if (typeof value === 'string' && !/^\d{4}-\d{2}-\d{2}$/.test(value)) {
return messages.invalidDate
}
}
@@ -188,9 +188,15 @@ const selected = (value: Value) => {
}
}
-const length = (length: number) => (value: Value) => {
- if (typeof value === 'string' && value?.length > length) {
- return { ...messages.length, values: { length } }
+const maxLength = (maxLength: number) => (value: Value) => {
+ if (typeof value === 'string' && value?.length > maxLength) {
+ return { ...messages.maxLength, values: { maxLength } }
+ }
+}
+
+const minLength = (minLength: number) => (value: Value) => {
+ if (typeof value === 'string' && value.length > 0 && value.length < minLength) {
+ return { ...messages.minLength, values: { minLength } }
}
}
@@ -254,13 +260,14 @@ export default {
compareDate,
ethAddress,
validDate,
- selected,
+ maxLength,
+ minLength,
httpsUrl,
+ selected,
required,
exclude,
maxDate,
minDate,
- length,
number,
minFee,
email,
diff --git a/src/modules/forms/validators/messages.ts b/src/modules/forms/validators/messages.ts
index eafb77bd..7ec629f5 100644
--- a/src/modules/forms/validators/messages.ts
+++ b/src/modules/forms/validators/messages.ts
@@ -65,11 +65,11 @@ export default {
exclude: {
en: 'This value does not fit',
ru: 'Это значение не подходит',
- fr: 'Cette valeur ne convient pas',
- es: 'Este valor no encaja',
- pt: 'Este valor não se encaixa',
- de: 'Dieser Wert passt nicht',
- zh: '此值不适合',
+ fr: 'Cette valeur n\'est pas valide',
+ es: 'Este valor no es válido',
+ pt: 'Este valor não é válido',
+ de: 'Dieser Wert ist ungültig',
+ zh: '此值无效',
},
greaterThanZero: {
en: 'Must be greater than 0',
@@ -104,7 +104,7 @@ export default {
fr: 'Doit être inférieur à {maxValue}',
es: 'Debe ser menor que {maxValue}',
pt: 'Deve ser menor que {maxValue}',
- de: 'Muss weniger als {maxValue} sein',
+ de: 'Muss kleiner als {maxValue} sein',
zh: '必须小于{maxValue}',
},
number: {
@@ -125,14 +125,23 @@ export default {
de: 'Muss ausgewählt werden',
zh: '必须选择',
},
- length: {
- en: 'No more than {length} characters',
- ru: 'Не более {length} символов',
- fr: 'Pas plus de {length} caractères',
- es: 'No más de {length} caracteres',
- pt: 'Não mais do que {length} caracteres',
- de: 'Nicht mehr als {length} Zeichen',
- zh: '不超过{length}个字符',
+ maxLength: {
+ en: 'No more than {maxLength} characters',
+ ru: 'Не более {maxLength} символов',
+ fr: 'Pas plus de {maxLength} caractères',
+ es: 'No más de {maxLength} caracteres',
+ pt: 'Não mais do que {maxLength} caracteres',
+ de: 'Nicht mehr als {maxLength} Zeichen',
+ zh: '不超过{maxLength}个字符',
+ },
+ minLength: {
+ en: 'Must be at least {minLength} characters',
+ ru: 'Должно быть не менее {minLength} символов',
+ fr: 'Doit comporter au moins {minLength} caractères',
+ es: 'Debe tener al menos {minLength} caracteres',
+ pt: 'Deve ter pelo menos {minLength} caracteres',
+ de: 'Muss mindestens {minLength} Zeichen lang sein',
+ zh: '至少需要{minLength}个字符',
},
mustBeLess: {
en: 'Must be less than {valueToCompare}',
@@ -147,7 +156,7 @@ export default {
en: 'Must be more than {valueToCompare}',
ru: 'Должно быть больше {valueToCompare}',
fr: 'Doit être supérieur à {valueToCompare}',
- es: 'Debe ser más de {valueToCompare}',
+ es: 'Debe ser mayor que {valueToCompare}',
pt: 'Deve ser maior que {valueToCompare}',
de: 'Muss mehr als {valueToCompare} sein',
zh: '必须大于{valueToCompare}',
@@ -174,7 +183,7 @@ export default {
en: 'The amount is lower than the swap fee',
ru: 'Сумма меньше, чем комиссия за обмен',
fr: 'Le montant est inférieur aux frais d\'échange',
- es: 'La cantidad es inferior a la tarifa de intercambio',
+ es: 'El monto es inferior a la comisión de intercambio',
pt: 'O valor é inferior à taxa de troca',
de: 'Der Betrag ist niedriger als die Tauschgebühr',
zh: '金额低于兑换费用',
diff --git a/src/modules/forms/validators/validators.spec.ts b/src/modules/forms/validators/validators.spec.ts
new file mode 100644
index 00000000..10096c84
--- /dev/null
+++ b/src/modules/forms/validators/validators.spec.ts
@@ -0,0 +1,214 @@
+import { parseEther } from 'ethers'
+
+import validators from './index'
+import messages from './messages'
+
+
+describe('forms/validators', () => {
+ const {
+ min,
+ max,
+ email,
+ exclude,
+ httpsUrl,
+ required,
+ maxLength,
+ minLength,
+ validDate,
+ ethAddress,
+ numberWithDot,
+ greaterThanZero,
+ sufficientBalance,
+ } = validators
+
+ describe('required validator', () => {
+ it('should not return an error if the value exists', () => {
+ expect(required('string')).toBeFalsy()
+ expect(required(true)).toBeFalsy()
+ // @ts-ignore
+ expect(required(123)).toBeFalsy()
+ })
+
+ it('should return an error if the value does not exists', () => {
+ // @ts-ignore
+ expect(required(undefined)).toEqual(messages.required)
+ // @ts-ignore
+ expect(required(null)).toEqual(messages.required)
+ expect(required('')).toEqual(messages.required)
+ })
+ })
+
+ describe('email validator', () => {
+ it('should not return an error if the email is valid', () => {
+ expect(email('123@mail.com')).toBeFalsy()
+ expect(email('123@mail.co.com')).toBeFalsy()
+ expect(email('email@mail.com')).toBeFalsy()
+ expect(email('email.123@mail.com')).toBeFalsy()
+ })
+
+ it('should return an error if the email is not valid', () => {
+ expect(email('@mail.com')).toEqual(messages.email)
+ expect(email('email@mail')).toEqual(messages.email)
+ expect(email('@mail.co.com')).toEqual(messages.email)
+ expect(email('email.co@mail')).toEqual(messages.email)
+ expect(email('.email@mail.com')).toEqual(messages.email)
+ expect(email('email@mail.com.')).toEqual(messages.email)
+ expect(email('email@@mail.com')).toEqual(messages.email)
+ })
+ })
+
+ describe('ETH address validator', () => {
+ it('should not return an error if the address is valid', () => {
+ expect(ethAddress('0xEC01cB780202595Ce2Fb11225aABfAd201B54e0f')).toBeFalsy()
+ })
+
+ it('should return an error if the address is not valid', () => {
+ expect(ethAddress('xEC01cB780202595Ce2Fb11225aABfAd201B54e0f')).toEqual(messages.ethAddress)
+ expect(ethAddress('0xEC01cB780202595Ce2Fb11225aABfAd201B54e')).toEqual(messages.ethAddress)
+ })
+ })
+
+ describe('sufficientBalance validator', () => {
+ const balanceRef = { current: parseEther('32') }
+
+ it('should return an error if the value > balance', () => {
+ expect(sufficientBalance(balanceRef)('33')).toEqual(messages.insufficientBalance)
+ expect(sufficientBalance(balanceRef)('32.000000000000000001')).toEqual(messages.insufficientBalance)
+ })
+
+ it('should not return an error if the value is correct', () => {
+ expect(sufficientBalance(balanceRef)('32')).toBeFalsy()
+ expect(sufficientBalance(balanceRef)('31.000000000000000001')).toBeFalsy()
+ })
+ })
+
+ describe('numberWithDot validator', () => {
+ it('should return an error if the value is incorrect', () => {
+ expect(numberWithDot('AAA')).toEqual(messages.invalidNumberWithDot)
+ expect(numberWithDot('1..1')).toEqual(messages.invalidNumberWithDot)
+ expect(numberWithDot('..1')).toEqual(messages.invalidNumberWithDot)
+ expect(numberWithDot('1..')).toEqual(messages.invalidNumberWithDot)
+ })
+
+ it('should not return an error if the value is correct', () => {
+ expect(numberWithDot('1.55')).toBeFalsy()
+ expect(numberWithDot('155')).toBeFalsy()
+ })
+ })
+
+ describe('exclude validator', () => {
+ it('should return an error if the value is incorrect', () => {
+ const values = [ '1', '2' ]
+
+ expect(exclude(values)('1')).toEqual(messages.exclude)
+ expect(exclude(values)('2')).toEqual(messages.exclude)
+ })
+
+ it('should not return an error if the value is correct', () => {
+ expect(exclude([ '1', '2' ])('3')).toBeFalsy()
+ })
+ })
+
+ describe('httpsUrl validator', () => {
+ it('should not return an error if the URL is valid', () => {
+ expect(httpsUrl('https://rpc.example.com')).toBeFalsy()
+ expect(httpsUrl('https://eth-mainnet.g.alchemy.com/v2/abc123')).toBeFalsy()
+ expect(httpsUrl('https://example.com/path?query=1')).toBeFalsy()
+ })
+
+ it('should return an error if the URL is not valid', () => {
+ expect(httpsUrl('http://example.com')).toEqual(messages.httpsUrl)
+ expect(httpsUrl('ws://example.com')).toEqual(messages.httpsUrl)
+ expect(httpsUrl('wss://example.com')).toEqual(messages.httpsUrl)
+ expect(httpsUrl('ftp://example.com')).toEqual(messages.httpsUrl)
+ expect(httpsUrl('example.com')).toEqual(messages.httpsUrl)
+ expect(httpsUrl('https://')).toEqual(messages.httpsUrl)
+ expect(httpsUrl('not-a-url')).toEqual(messages.httpsUrl)
+ })
+ })
+
+ describe('maxLength validator', () => {
+ it('should not return an error for strings within the limit, empty or undefined', () => {
+ expect(maxLength(5)('abc')).toBeFalsy()
+ expect(maxLength(5)('abcde')).toBeFalsy()
+ expect(maxLength(5)('')).toBeFalsy()
+ expect(maxLength(5)(undefined)).toBeFalsy()
+ })
+
+ it('should return an error for strings over the limit', () => {
+ expect(maxLength(5)('abcdef')).toEqual({ ...messages.maxLength, values: { maxLength: 5 } })
+ })
+ })
+
+ describe('minLength validator', () => {
+ it('should not return an error for strings at or over the limit, or undefined', () => {
+ expect(minLength(5)('abcde')).toBeFalsy()
+ expect(minLength(5)('abcdef')).toBeFalsy()
+ expect(minLength(5)(undefined)).toBeFalsy()
+ })
+
+ it('should not return an error for an empty string (emptiness is the required validator job)', () => {
+ expect(minLength(5)('')).toBeFalsy()
+ })
+
+ it('should return an error for strings under the limit', () => {
+ expect(minLength(5)('abc')).toEqual({ ...messages.minLength, values: { minLength: 5 } })
+ })
+ })
+
+ describe('validDate validator', () => {
+ it('should not return an error for a valid date or undefined', () => {
+ expect(validDate('2024-01-15')).toBeFalsy()
+ expect(validDate(undefined)).toBeFalsy()
+ })
+
+ it('should return an error for an invalid date format', () => {
+ expect(validDate('2024-1-5')).toEqual(messages.invalidDate)
+ expect(validDate('notadate')).toEqual(messages.invalidDate)
+ })
+ })
+
+ describe('min validator', () => {
+ it('should not return an error for values at or above the minimum', () => {
+ expect(min(32)('32')).toBeFalsy()
+ expect(min(32)('100')).toBeFalsy()
+ })
+
+ it('should not return an error for NaN (e.g. "∞" unlimited capacity)', () => {
+ expect(min(32)('∞')).toBeFalsy()
+ })
+
+ it('should return an error for values below the minimum', () => {
+ expect(min(32)('10')).toEqual({ ...messages.min, values: { minValue: 32 } })
+ })
+ })
+
+ describe('max validator', () => {
+ it('should not return an error for values at or below the maximum', () => {
+ expect(max(100)('50')).toBeFalsy()
+ expect(max(100)('100')).toBeFalsy()
+ })
+
+ it('should not return an error for NaN (e.g. "∞")', () => {
+ expect(max(100)('∞')).toBeFalsy()
+ })
+
+ it('should return an error for values above the maximum', () => {
+ expect(max(100)('150')).toEqual({ ...messages.max, values: { maxValue: 100 } })
+ })
+ })
+
+ describe('greaterThanZero validator', () => {
+ it('should not return an error for positive values, NaN or empty', () => {
+ expect(greaterThanZero('5')).toBeFalsy()
+ expect(greaterThanZero('∞')).toBeFalsy()
+ expect(greaterThanZero(undefined)).toBeFalsy()
+ })
+
+ it('should return an error for zero or negative values', () => {
+ expect(greaterThanZero('0')).toEqual(messages.greaterThanZero)
+ expect(greaterThanZero('-1')).toEqual(messages.greaterThanZero)
+ })
+ })
+})
+
diff --git a/src/modules/intersection-observer/useEntry.ts b/src/modules/intersection-observer/useEntry.ts
index c9b1d852..85f7e228 100644
--- a/src/modules/intersection-observer/useEntry.ts
+++ b/src/modules/intersection-observer/useEntry.ts
@@ -1,6 +1,7 @@
'use client'
import { useState, useCallback, useEffect, RefCallback } from 'react'
-import { useDeepMemo } from 'hooks'
+
+import useDeepMemo from '../../hooks/controls/useDeepMemo'
export type Opts = Partial(opts: Opts = {}): Result => {
useEffect(() => {
if (!node || typeof IntersectionObserver === 'undefined') {
setEntry(null)
-
return
}
diff --git a/src/modules/intl/_SSR/languageMiddleware.ts b/src/modules/intl/_SSR/languageMiddleware.ts
index c8461158..625d7f4a 100644
--- a/src/modules/intl/_SSR/languageMiddleware.ts
+++ b/src/modules/intl/_SSR/languageMiddleware.ts
@@ -1,6 +1,6 @@
import { NextRequest } from 'next/server'
-import * as constants from 'helpers/constants'
import allLanguages from 'scripts/languages'
+import * as constants from 'helpers/constants'
const defaultLanguage = allLanguages[0]
diff --git a/src/modules/intl/formatMessage.spec.ts b/src/modules/intl/formatMessage.spec.ts
new file mode 100644
index 00000000..296fe83c
--- /dev/null
+++ b/src/modules/intl/formatMessage.spec.ts
@@ -0,0 +1,307 @@
+import _formatMessage from './formatMessage'
+
+
+const formatMessage = _formatMessage.bind(null, 'en')
+
+describe('module:intl/formatMessage', () => {
+
+ describe('common', () => {
+
+ it('with no variables', () => {
+ const message = { en: 'Hello world' }
+ const expected = 'Hello world'
+
+ expect(formatMessage(message)).toEqual(expected)
+ })
+
+ it('with html tags', () => {
+ const message = { en: 'Hello world' }
+ const expected = 'Hello world'
+
+ expect(formatMessage(message)).toEqual(expected)
+ })
+
+ it('with multiple lines', () => {
+ const message = {
+ en: `
+ Discover a new burger
+ This is delicious
+ `,
+ }
+ const expected = 'Discover a new burger This is delicious'
+
+ expect(formatMessage(message)).toEqual(expected)
+ })
+
+ })
+
+ // Simple variables ----------------------------------------------------- /
+
+ describe('with simple variables', () => {
+
+ it('with no value', () => {
+ const message = { en: 'Hello, {username}' }
+ const expected = 'Hello, {username}'
+
+ expect(formatMessage(message)).toEqual(expected)
+ })
+
+ it('with null value', () => {
+ const message = { en: 'Hello, {username}' }
+ const values = { username: null }
+ const expected = 'Hello, {username}'
+
+ // @ts-ignore
+ expect(formatMessage(message, values)).toEqual(expected)
+ })
+
+ it('with string value', () => {
+ const message = { en: 'Hello, {username}' }
+ const values = { username: 'John Doe' }
+ const expected = 'Hello, John Doe'
+
+ expect(formatMessage(message, values)).toEqual(expected)
+ })
+
+ it('with price value', () => {
+ const message = { en: 'Price: {price}' }
+ const values = { price: '$10.15' }
+ const expected = 'Price: $10.15'
+
+ expect(formatMessage(message, values)).toEqual(expected)
+ })
+
+ it('with multiple values', () => {
+ const message = { en: 'Discover a new {product} {period} for ${price}' }
+ const values = { product: 'burger', period: 'every month', price: 1.95 }
+ const expected = 'Discover a new burger every month for $1.95'
+
+ expect(formatMessage(message, values)).toEqual(expected)
+ })
+
+ })
+
+ // Plural variables ----------------------------------------------- /
+
+ describe('with plural variable', () => {
+
+ it('with values { count: 1 }', () => {
+ const messages = {
+ en: 'Get {count, plural, one {# product} other {# products}} for free.',
+ ru: 'Получить {count, plural, one {# продукт} other {# продуктов}} бесплатно.',
+ fr: 'Obtenez {count, plural, one {# produit} other {# produits}} gratuitement.',
+ de: 'Erhalten Sie das {count, plural, one {# Produkt} other {# Produkte}} kostenlos.',
+ es: 'Obtenga {count, plural, one {# producto} other {# productos}} gratis.',
+ pt: 'Receba {count, plural, one {# produto} other {# produtos}} gratuitamente.',
+ zh: '免费获得 {count, plural, one {# 产品} other {# 产品}}。',
+ }
+ const expectedMessages = {
+ en: 'Get 1 product for free.',
+ ru: 'Получить 1 продукт бесплатно.',
+ fr: 'Obtenez 1 produit gratuitement.',
+ de: 'Erhalten Sie das 1 Produkt kostenlos.',
+ es: 'Obtenga 1 producto gratis.',
+ pt: 'Receba 1 produto gratuitamente.',
+ zh: '免费获得 1 产品。',
+ }
+ const values = { count: 1 }
+
+ for (const [ key, value ] of Object.entries(messages)) {
+ const expectedMessage = expectedMessages[key as keyof typeof expectedMessages]
+
+ expect(_formatMessage(key, { [key]: value }, values)).toEqual(expectedMessage)
+ }
+ })
+
+ it('with values { count: 23 }', () => {
+ const messages = {
+ en: 'Get {count, plural, one {# product} other {products}} for free.',
+ ru: 'Получить {count, plural, one {# продукт} other {продукты}} бесплатно.',
+ fr: 'Obtenez {count, plural, one {# produit} other {produits}} gratuitement.',
+ de: 'Erhalten Sie das {count, plural, one {# Produkt} other {Produkte}} kostenlos.',
+ es: 'Obtenga {count, plural, one {# producto} other {productos}} gratis.',
+ pt: 'Receba {count, plural, one {# produto} other {produtos}} gratuitamente.',
+ zh: '免费获得{count, plural, one {# 产品} other {产品}}。',
+ }
+ const expectedMessages = {
+ en: 'Get products for free.',
+ ru: 'Получить продукты бесплатно.',
+ fr: 'Obtenez produits gratuitement.',
+ de: 'Erhalten Sie das Produkte kostenlos.',
+ es: 'Obtenga productos gratis.',
+ pt: 'Receba produtos gratuitamente.',
+ zh: '免费获得产品。',
+ }
+ const values = { count: 23 }
+
+ for (const [ key, value ] of Object.entries(messages)) {
+ const expectedMessage = expectedMessages[key as keyof typeof expectedMessages]
+
+ expect(_formatMessage(key, { [key]: value }, values)).toEqual(expectedMessage)
+ }
+ })
+
+ it('if # exists', () => {
+ const messages = {
+ en: 'Get {count, plural, one {# product} other {# products}} for free.',
+ ru: 'Получить {count, plural, one {# продукт} other {# продукта}} бесплатно.',
+ fr: 'Obtenez {count, plural, one {# produit} other {# produits}} gratuitement.',
+ de: 'Erhalten Sie das {count, plural, one {# Produkt} other {# Produkte}} kostenlos.',
+ es: 'Obtenga {count, plural, one {# producto} other {# productos}} gratis.',
+ pt: 'Receba {count, plural, one {# produto} other {# produtos}} gratuitamente.',
+ zh: '免费获得 {count, plural, one {# 产品} other {# 产品}}。',
+ }
+ const expectedMessages = {
+ en: 'Get 23 products for free.',
+ ru: 'Получить 23 продукта бесплатно.',
+ fr: 'Obtenez 23 produits gratuitement.',
+ de: 'Erhalten Sie das 23 Produkte kostenlos.',
+ es: 'Obtenga 23 productos gratis.',
+ pt: 'Receba 23 produtos gratuitamente.',
+ zh: '免费获得 23 产品。',
+ }
+ const values = { count: 23 }
+
+ for (const [ key, value ] of Object.entries(messages)) {
+ const expectedMessage = expectedMessages[key as keyof typeof expectedMessages]
+
+ expect(_formatMessage(key, { [key]: value }, values)).toEqual(expectedMessage)
+ }
+ })
+
+ it('with no values', () => {
+ const messages = {
+ en: 'Get {count, plural, one {# product} other {products}} for free.',
+ ru: 'Получить {count, plural, one {# продукт} other {продукты}} бесплатно.',
+ fr: 'Obtenez {count, plural, one {# produit} other {produits}} gratuitement.',
+ de: 'Erhalten Sie das {count, plural, one {# Produkt} other {Produkte}} kostenlos.',
+ es: 'Obtenga {count, plural, one {# producto} other {productos}} gratis.',
+ pt: 'Receba {count, plural, one {# produto} other {produtos}} gratuitamente.',
+ zh: '免费获得{count, plural, one {# 产品} other {产品}}。',
+ }
+ const expectedMessages = {
+ en: 'Get products for free.',
+ ru: 'Получить продукты бесплатно.',
+ fr: 'Obtenez produits gratuitement.',
+ de: 'Erhalten Sie das Produkte kostenlos.',
+ es: 'Obtenga productos gratis.',
+ pt: 'Receba produtos gratuitamente.',
+ zh: '免费获得产品。',
+ }
+
+ for (const [ key, value ] of Object.entries(messages)) {
+ const expectedMessage = expectedMessages[key as keyof typeof expectedMessages]
+
+ expect(_formatMessage(key, { [key]: value })).toEqual(expectedMessage)
+ }
+ })
+
+ it('with no values #2', () => {
+ const messages = {
+ en: 'Get {count, plural, one {# product} other {new products}} for free.',
+ ru: 'Получить {count, plural, one {# продукт} other {новые продукты}} бесплатно.',
+ fr: 'Obtenez {count, plural, one {# produit} other {nouveaux produits}} gratuitement.',
+ de: 'Erhalten Sie das {count, plural, one {# Produkt} other {Neue Produkte}} kostenlos.',
+ es: 'Obtenga {count, plural, one {# producto} other {nuevos productos}} gratis.',
+ pt: 'Receba {count, plural, one {# produto} other {novos produtos}} gratuitamente.',
+ zh: '免费获得{count, plural, one {# 产品} other {新产品}}。',
+ }
+ const expectedMessages = {
+ en: 'Get new products for free.',
+ ru: 'Получить новые продукты бесплатно.',
+ fr: 'Obtenez nouveaux produits gratuitement.',
+ de: 'Erhalten Sie das Neue Produkte kostenlos.',
+ es: 'Obtenga nuevos productos gratis.',
+ pt: 'Receba novos produtos gratuitamente.',
+ zh: '免费获得新产品。',
+ }
+
+ for (const [ key, value ] of Object.entries(messages)) {
+ const expectedMessage = expectedMessages[key as keyof typeof expectedMessages]
+
+ expect(_formatMessage(key, { [key]: value })).toEqual(expectedMessage)
+ }
+ })
+
+ it('with several values', () => {
+ const messages = {
+ en: 'Get {count, plural, one {# product} other {two products}} for free.',
+ ru: 'Получить {count, plural, one {# продукт} other {два продукта}} бесплатно.',
+ fr: 'Obtenez {count, plural, one {# produit} other {deux produits}} gratuitement.',
+ de: 'Erhalten Sie das {count, plural, one {# Produkt} other {zwei Produkte}} kostenlos.',
+ es: 'Obtenga {count, plural, one {# producto} other {dos productos}} gratis.',
+ pt: 'Receba {count, plural, one {# produto} other {dois produtos}} gratuitamente.',
+ zh: '免费获得{count, plural, one {# 产品} other {二}}产品。',
+ }
+ const expectedMessages = {
+ en: 'Get two products for free.',
+ ru: 'Получить два продукта бесплатно.',
+ fr: 'Obtenez deux produits gratuitement.',
+ de: 'Erhalten Sie das zwei Produkte kostenlos.',
+ es: 'Obtenga dos productos gratis.',
+ pt: 'Receba dois produtos gratuitamente.',
+ zh: '免费获得二产品。',
+ }
+ const values = { count: 2 }
+
+ for (const [ key, value ] of Object.entries(messages)) {
+ const expectedMessage = expectedMessages[key as keyof typeof expectedMessages]
+
+ expect(_formatMessage(key, { [key]: value }, values)).toEqual(expectedMessage)
+ }
+ })
+
+ })
+
+ // Select variables ----------------------------------------------- /
+
+ describe('with select variable', () => {
+
+ it('with values { currency: "ETH" }', () => {
+ const message = { en: 'Check a new {currency, select, ETH {ethereum} BTC {bitcoin}}' }
+ const values = { currency: 'ETH' }
+ const expected = 'Check a new ethereum'
+
+ expect(formatMessage(message, values)).toEqual(expected)
+ })
+
+ it('with no values', () => {
+ const message = { en: 'Check a new {currency, select, ETH {ethereum} BTC {bitcoin}}' }
+ const expected = 'Check a new ethereum'
+
+ expect(formatMessage(message)).toEqual(expected)
+ })
+
+ it('with empty values', () => {
+ const message = { en: 'Check a new {currency, select, ETH {ethereum} BTC {bitcoin}}' }
+ const expected = 'Check a new ethereum'
+
+ expect(formatMessage(message)).toEqual(expected)
+ })
+
+ })
+
+ // Mixed variables ---------------------------------- /
+
+ describe('with mixed variables', () => {
+
+ it('with same plural and simple variable names', () => {
+ const message = { en: 'Get a free {product}. One {count, plural, one {product} other {products}}' }
+ const values = { count: 1, product: 'burger' }
+ const expected = 'Get a free burger. One product'
+
+ expect(formatMessage(message, values)).toEqual(expected)
+ })
+
+ it('inline', () => {
+ // eslint-disable-next-line max-len
+ const message = { en: 'Discover a new {gender, select, female {cheeseburger} male {burger}} {period} for {price, plural, one {# dollar} other {# dollars}}' }
+ const values = { gender: 'male', period: 'every month', price: 1 }
+ const expected = 'Discover a new burger every month for 1 dollar'
+
+ expect(formatMessage(message, values)).toEqual(expected)
+ })
+
+ })
+
+})
diff --git a/src/modules/intl/useLocale.ts b/src/modules/intl/useLocale.ts
index f6bed50b..ce615565 100644
--- a/src/modules/intl/useLocale.ts
+++ b/src/modules/intl/useLocale.ts
@@ -1,9 +1,24 @@
'use client'
import { useCallback, useState } from 'react'
+
import * as constants from 'helpers/constants'
import cookie from 'helpers/cookie'
+const isIpfs = process.env.NEXT_PUBLIC_IPFS === 'true'
+
+const getInitialLocale = (locale?: Intl.LanguagesKeys): Intl.LanguagesKeys => {
+ if (isIpfs) {
+ const saved = cookie.get(constants.cookieNames.language) as Intl.LanguagesKeys
+
+ if (saved) {
+ return saved
+ }
+ }
+
+ return locale || 'en'
+}
+
export type Input = {
locale?: Intl.LanguagesKeys
locales: Intl.LanguagesKeys[]
@@ -23,7 +38,7 @@ const useLocale = (values: Input): Output => {
} = values
// The module can take control of any kind of router, or we can save locale in state (if control via a router is not suitable for us)
- const [ runtimeLocale, setRuntimeLocale ] = useState(locale || 'en')
+ const [ runtimeLocale, setRuntimeLocale ] = useState(() => getInitialLocale(locale))
const setLocale = useCallback((value: Intl.LanguagesKeys) => {
const isValid = locales.includes(value)
diff --git a/src/modules/intl/util/getVariableValues.spec.ts b/src/modules/intl/util/getVariableValues.spec.ts
new file mode 100644
index 00000000..68f69e4c
--- /dev/null
+++ b/src/modules/intl/util/getVariableValues.spec.ts
@@ -0,0 +1,12 @@
+import getVariableValues from './getVariableValues'
+
+
+describe('module:intl/getVariableValues', () => {
+
+ it('should transform variable string to values map', () => {
+ const message = '{network, select, mainnet {correct} goerli {test} kovan {error}}'
+ const expected = { mainnet: 'correct', goerli: 'test', kovan: 'error' }
+
+ expect(getVariableValues(message)).toStrictEqual(expected)
+ })
+})
diff --git a/src/modules/intl/util/replacePluralVariables.spec.ts b/src/modules/intl/util/replacePluralVariables.spec.ts
new file mode 100644
index 00000000..12833def
--- /dev/null
+++ b/src/modules/intl/util/replacePluralVariables.spec.ts
@@ -0,0 +1,250 @@
+import replacePluralVariables from './replacePluralVariables'
+
+
+describe('module:intl/replacePluralVariables', () => {
+
+ it('should work with non-existent locale', () => {
+ const messages = {
+ xyz: 'Get {count, plural, one {# product} other {# products}} for free.',
+ }
+ const expectedMessages = {
+ xyz: 'Get products for free.',
+ }
+
+ for (const [ key, value ] of Object.entries(messages)) {
+ const expectedMessage = expectedMessages[key as keyof typeof expectedMessages]
+
+ expect(replacePluralVariables(value, key)).toEqual(expectedMessage)
+ }
+ })
+
+ it('should return passed message without changes if wrong variable template used #1', () => {
+ const messages = {
+ en: 'Get {count, plural, one {# product}} for free.',
+ ru: 'Получить {count, plural, one {# продукт}} бесплатно.',
+ fr: 'Obtenez {count, plural, one {# produit}} gratuitement.',
+ de: 'Erhalten Sie das {count, plural, one {# Produkt}} kostenlos',
+ es: 'Obtenga {count, plural, one {# producto}} gratis',
+ pt: 'Receba {count, plural, one {# o produto}} gratuitamente.',
+ zh: '免费获取{count, plural, one {# 产品}}',
+ }
+ for (const [ key, value ] of Object.entries(messages)) {
+ expect(replacePluralVariables(value, key)).toEqual(value)
+ }
+ })
+
+ it('should return passed message without changes if wrong variable template used #2', () => {
+ const messages = {
+ en: 'Get {count, plural , one {# product} other {# products} for free.',
+ ru: 'Получить {count, plural , one {# продукт} other {# products} бесплатно.',
+ fr: 'Obtenez {count, plural , one {# produit} other {# produits}} gratuitement.',
+ de: 'Erhalten Sie das {count, plural , one {# Produkt} other {# Produkte}} kostenlos',
+ es: 'Obtenga {count, plural , one {# producto}} gratis',
+ pt: 'Receba {count, plural , one {# o produto}} gratuitamente.',
+ zh: '免费获取{count, plural , one {# o 产品}}',
+ }
+
+ for (const [ key, value ] of Object.entries(messages)) {
+ expect(replacePluralVariables(value, key)).toEqual(value)
+ }
+ })
+
+ it('should return passed message without changes if wrong variable template used #3', () => {
+ const messages = {
+ en: 'Get {count, plurall, one {# product} other {# products}} for free.',
+ ru: 'Получить {count, plurall, one {# продукт} other {# продукты}} бесплатно.',
+ fr: 'Obtenez {count, plurall, one {# produit} other {# produits}} gratuitement.',
+ de: 'Erhalten Sie das {count, plurall, one {# Produkt} other {# Produkte}} kostenlos',
+ es: 'Obtenga {count, plurall, one {# producto}} gratis',
+ pt: 'Receba {count, plurall, one {# o produto}} gratuitamente.',
+ zh: '免费获取{count, plurall, one {# 产品}}',
+ }
+
+ for (const [ key, value ] of Object.entries(messages)) {
+ expect(replacePluralVariables(value, key)).toEqual(value)
+ }
+ })
+
+ it('should return "other" variable value if values not passed', () => {
+ const messages = {
+ en: 'Get {count, plural, one {# product} other {# products}} for free.',
+ ru: 'Получить {count, plural, one {# продукт} other {# продукты}} бесплатно.',
+ fr: 'Obtenez {count, plural, one {# produit} other {# des produits}} gratuitement.',
+ de: 'Erhalten Sie das {count, plural, one {# Produkt} other {# Produkte}} kostenlos',
+ es: 'Obtenga {count, plural, one {# producto} other {# productos}} gratis',
+ pt: 'Receba {count, plural, one {# o produto} other {# produtos}} gratuitamente.',
+ zh: '免费获得{count, plural, one {# 产品} other {# 产品}}',
+ }
+ const expectedMessages = {
+ en: 'Get products for free.',
+ ru: 'Получить продукты бесплатно.',
+ fr: 'Obtenez des produits gratuitement.',
+ de: 'Erhalten Sie das Produkte kostenlos',
+ es: 'Obtenga productos gratis',
+ pt: 'Receba produtos gratuitamente.',
+ zh: '免费获得产品',
+ }
+
+ for (const [ key, value ] of Object.entries(messages)) {
+ const expectedMessage = expectedMessages[key as keyof typeof expectedMessages]
+
+ expect(replacePluralVariables(value, key)).toEqual(expectedMessage)
+ expect(replacePluralVariables(value, key, {})).toEqual(expectedMessage)
+ expect(replacePluralVariables(value, key, { count: null })).toEqual(expectedMessage)
+ expect(replacePluralVariables(value, key, { count: undefined })).toEqual(expectedMessage)
+ }
+ })
+
+ it('should return "one" variable', () => {
+ const messages = {
+ en: 'Get {count, plural, one {# product} other {# products}} for free.',
+ ru: 'Получить {count, plural, one {# продукт} other {# продуктов}} бесплатно.',
+ fr: 'Obtenez {count, plural, one {# produit} other {# produits}} gratuitement.',
+ de: 'Erhalten Sie das {count, plural, one {# Produkt} other {# Produkte}} kostenlos.',
+ es: 'Obtenga {count, plural, one {# producto} other {# productos}} gratis.',
+ pt: 'Receba {count, plural, one {# produto} other {# produtos}} gratuitamente.',
+ zh: '免费获得 {count, plural, one {# 产品} other {# 产品}}',
+ }
+ const expectedMessages = {
+ en: 'Get 1 product for free.',
+ ru: 'Получить 1 продукт бесплатно.',
+ fr: 'Obtenez 1 produit gratuitement.',
+ de: 'Erhalten Sie das 1 Produkt kostenlos.',
+ es: 'Obtenga 1 producto gratis.',
+ pt: 'Receba 1 produto gratuitamente.',
+ zh: '免费获得 1 产品',
+ }
+
+ for (const [ key, value ] of Object.entries(messages)) {
+ const expectedMessage = expectedMessages[key as keyof typeof expectedMessages]
+
+ expect(replacePluralVariables(value, key, { count: 1 })).toEqual(expectedMessage)
+ }
+ })
+
+ it('should return "other" variable', () => {
+ const messages = {
+ en: 'Get {count, plural, one {# product} other {# products}} for free.',
+ ru: 'Получить {count, plural, one {# продукт} other {# продукта}} бесплатно.',
+ fr: 'Obtenez {count, plural, one {# produit} other {# produits}} gratuitement.',
+ de: 'Erhalten Sie das {count, plural, one {# Produkt} other {# Produkte}} kostenlos',
+ es: 'Obtenga {count, plural, one {# producto} other {# productos}} gratis',
+ pt: 'Receba {count, plural, one {# produto} other {# produtos}} gratuitamente.',
+ zh: '免费获得 {count, plural, one {# 产品} other {# 产品}}。',
+ }
+ const expectedMessages = {
+ en: 'Get 2.5 products for free.',
+ ru: 'Получить 2.5 продукта бесплатно.',
+ fr: 'Obtenez 2.5 produits gratuitement.',
+ de: 'Erhalten Sie das 2.5 Produkte kostenlos',
+ es: 'Obtenga 2.5 productos gratis',
+ pt: 'Receba 2.5 produtos gratuitamente.',
+ zh: '免费获得 2.5 产品。',
+ }
+
+ for (const [ key, value ] of Object.entries(messages)) {
+ const expectedMessage = expectedMessages[key as keyof typeof expectedMessages]
+
+ expect(replacePluralVariables(value, key, { count: 2.5 })).toEqual(expectedMessage)
+ }
+ })
+
+ it('should replace multiple variables', () => {
+ const messages = {
+ en: `
+ Get {count, plural, one {# product} other {# products}} for free.
+ Get {amount, plural, one {one item} other {items}} for free.
+ `,
+ ru: `
+ Получить {count, plural, one {# продукт} other {# продукта}} бесплатно.
+ Получить {amount, plural, one {один элемент} other {# элемента}} бесплатно.
+ `,
+ fr: `
+ Obtenez {count, plural, one {# produit} other {# produits}} gratuitement.
+ Obtenez {amount, plural, one {un produit} other {# produits}} gratuitement.
+ `,
+ de: `
+ Erhalten Sie das {count, plural, one {# Produkt} other {# Produkte}} kostenlos.
+ Erhalten Sie das {amount, plural, one {ein Produkt} other {# Produkte}} kostenlos.
+ `,
+ es: `
+ Obtenga {count, plural, one {# producto} other {# productos}} gratis.
+ Obtenga {amount, plural, one {un producto} other {# productos}} gratis.
+ `,
+ pt: `
+ Receba {count, plural, one {# produto} other {# produtos}} gratuitamente.
+ Receba {amount, plural, one {um produto} other { produtos}} gratuitamente.
+ `,
+ zh: `
+ 免费获得 {count, plural, one {# 产品} other {# 产品}}。
+ 免费获得 {count, plural, one {一 产品} other { 产品}}。
+ `,
+ }
+
+ const expectedMessages = {
+ en: `
+ Get 2 products for free.
+ Get one item for free.
+ `,
+ ru: `
+ Получить 2 продукта бесплатно.
+ Получить один элемент бесплатно.
+ `,
+ fr: `
+ Obtenez 2 produits gratuitement.
+ Obtenez un produit gratuitement.
+ `,
+ de: `
+ Erhalten Sie das 2 Produkte kostenlos.
+ Erhalten Sie das ein Produkt kostenlos.
+ `,
+ es: `
+ Obtenga 2 productos gratis.
+ Obtenga un producto gratis.
+ `,
+ pt: `
+ Receba 2 produtos gratuitamente.
+ Receba um produto gratuitamente.
+ `,
+ zh: `
+ 免费获得 2 产品。
+ 免费获得 产品。
+ `,
+ }
+
+ for (const [ key, value ] of Object.entries(messages)) {
+ const expectedMessage = expectedMessages[key as keyof typeof expectedMessages]
+
+ expect(replacePluralVariables(value, key, { count: 2, amount: 1 })).toEqual(expectedMessage)
+ }
+ })
+
+ it('should return "few" variable or "others" if "few" does not exist', () => {
+ const messages = {
+ en: 'Get {count, plural, one {# product} few {few products} other {# products}} for free.',
+ ru: 'Получить {count, plural, one {# продукт} few {# продукта} many {# продуктов}} бесплатно.',
+ fr: 'Obtenez {count, plural, one {# produit} few {# plusieurs produits} other {# produits}} gratuitement.',
+ de: 'Erhalten Sie das {count, plural, one {# Produkt} few {# mehrere Produkte} other {# Produkte}} kostenlos.',
+ es: 'Obtenga {count, plural, one {# producto} few {# varios productos} other {# productos}} gratis',
+ pt: 'Receba {count, plural, one {# produto} few {# vários productos} other {# produtos}} gratuitamente.',
+ zh: '免费获得 {count, plural, one {# } few {# 产品} other {# 产品}}',
+ }
+
+ const expectedMessages = {
+ en: 'Get 2 products for free.',
+ ru: 'Получить 2 продукта бесплатно.',
+ fr: 'Obtenez 2 produits gratuitement.',
+ de: 'Erhalten Sie das 2 Produkte kostenlos.',
+ es: 'Obtenga 2 productos gratis',
+ pt: 'Receba 2 produtos gratuitamente.',
+ zh: '免费获得 2 产品',
+ }
+
+ for (const [ key, value ] of Object.entries(messages)) {
+ const expectedMessage = expectedMessages[key as keyof typeof expectedMessages]
+
+ expect(replacePluralVariables(value, key, { count: 2 })).toEqual(expectedMessage)
+ }
+ })
+
+})
diff --git a/src/modules/intl/util/replaceSelectVariables.spec.ts b/src/modules/intl/util/replaceSelectVariables.spec.ts
new file mode 100644
index 00000000..2d768062
--- /dev/null
+++ b/src/modules/intl/util/replaceSelectVariables.spec.ts
@@ -0,0 +1,71 @@
+import replaceSelectVariables from './replaceSelectVariables'
+
+
+describe('module:intl/replaceSelectVariables', () => {
+
+ it('should return passed message without changes if wrong variable template used #1', () => {
+ const message = '{network, select, mainnet {correct}}'
+
+ expect(replaceSelectVariables(message)).toEqual(message)
+ })
+
+ it('should return passed message without changes if wrong variable template used #2', () => {
+ const message = '{network, select , mainnet {correct} goerli {test} kovan {error}}'
+
+ expect(replaceSelectVariables(message)).toEqual(message)
+ })
+
+ it('should return passed message without changes if wrong variable template used #3', () => {
+ const message = '{network, selectt, mainnet {correct} goerli {test} kovan {error}}'
+
+ expect(replaceSelectVariables(message)).toEqual(message)
+ })
+
+ it('should return "other" variable value if values not passed', () => {
+ const message = '{network, select, mainnet {correct} goerli {test} other {fragrance}}'
+ const expected = 'fragrance'
+
+ expect(replaceSelectVariables(message)).toEqual(expected)
+ expect(replaceSelectVariables(message, {})).toEqual(expected)
+ expect(replaceSelectVariables(message, { count: null })).toEqual(expected)
+ expect(replaceSelectVariables(message, { count: undefined })).toEqual(expected)
+ })
+
+ it('should return "correct" (first) variable value if values not passed and "other" variable does not exist', () => {
+ const message = '{network, select, mainnet {correct} goerli {test} kovan {error}}'
+ const expected = 'correct'
+
+ expect(replaceSelectVariables(message)).toEqual(expected)
+ })
+
+ it('should replace variable with passed value', () => {
+ const message = '{network, select, mainnet {correct} goerli {test} kovan {error}}'
+ const values = { network: 'goerli' }
+ const expected = 'test'
+
+ expect(replaceSelectVariables(message, values)).toEqual(expected)
+ })
+
+ it('should return "other" variable', () => {
+ const message = '{network, select, mainnet {correct} goerli {test} kovan {error}}'
+ const values = { network: 'zero' }
+ const expected = 'correct'
+
+ expect(replaceSelectVariables(message, values)).toEqual(expected)
+ })
+
+ it('should replace multiple variables', () => {
+ const message = `
+ {network, select, mainnet {correct} goerli {test} kovan {error}}.
+ Count: {count, select, 1 {one ETH} other {ETHs}} for free.
+ `
+ const values = { network: 'mainnet', count: 1 }
+ const expected = `
+ correct.
+ Count: one ETH for free.
+ `
+
+ expect(replaceSelectVariables(message, values)).toEqual(expected)
+ })
+
+})
diff --git a/src/modules/intl/util/replaceSimpleVariables.spec.ts b/src/modules/intl/util/replaceSimpleVariables.spec.ts
new file mode 100644
index 00000000..1d484e47
--- /dev/null
+++ b/src/modules/intl/util/replaceSimpleVariables.spec.ts
@@ -0,0 +1,38 @@
+import replaceSimpleVariables from './replaceSimpleVariables'
+
+
+describe('module:intl/replaceSimpleVariables', () => {
+
+ it('should return passed message without changes', () => {
+ const message = 'Hello, {username}'
+ const expected = 'Hello, {username}'
+ const actual = replaceSimpleVariables(message)
+
+ expect(actual).toEqual(expected)
+ })
+
+ it('should replace one variable', () => {
+ const message = 'Hello, {username}'
+ const expected = 'Hello, John Doe'
+ const actual = replaceSimpleVariables(message, { username: 'John Doe' })
+
+ expect(actual).toEqual(expected)
+ })
+
+ it('should replace multiple variables', () => {
+ const message = 'Hello, {username}. Get a free {product}'
+ const expected = 'Hello, John Doe. Get a free cologne'
+ const actual = replaceSimpleVariables(message, { username: 'John Doe', product: 'cologne' })
+
+ expect(actual).toEqual(expected)
+ })
+
+ it('should replace multiple same variables', () => {
+ const message = 'Get a free {product}. This is nice {product}'
+ const expected = 'Get a free cologne. This is nice cologne'
+ const actual = replaceSimpleVariables(message, { product: 'cologne' })
+
+ expect(actual).toEqual(expected)
+ })
+
+})
diff --git a/src/modules/theme/_SSR/getServerTheme.ts b/src/modules/theme/_SSR/getServerTheme.ts
index 3147d1b0..97d2e32a 100644
--- a/src/modules/theme/_SSR/getServerTheme.ts
+++ b/src/modules/theme/_SSR/getServerTheme.ts
@@ -1,11 +1,18 @@
-import * as constants from 'helpers/constants'
-import { cookies } from 'next/headers'
import { ThemeColor } from '../enum'
+import * as constants from 'helpers/constants'
+
+const isIpfs = process.env.NEXT_PUBLIC_IPFS === 'true'
const availableThemes = [ ThemeColor.Light, ThemeColor.Dark ]
const getServerTheme = async () => {
+ if (isIpfs) {
+ return { value: undefined, isSystemTheme: true }
+ }
+
+ const { cookies } = await import('next/headers')
+
const cookiesStore = await cookies()
const theme = cookiesStore.get(constants.cookieNames.themeColor)?.value as ThemeColor
const isSystemTheme = cookiesStore.get(constants.cookieNames.isSystemTheme)?.value !== 'false'
diff --git a/src/modules/theme/ctx.ts b/src/modules/theme/ctx.ts
index bc51e489..1e4d8ec3 100644
--- a/src/modules/theme/ctx.ts
+++ b/src/modules/theme/ctx.ts
@@ -8,6 +8,8 @@ import cookie from 'helpers/cookie'
import { ThemeClasses, ThemeValue, ThemeColor } from './enum'
+const isIpfs = process.env.NEXT_PUBLIC_IPFS === 'true'
+
const initialContext: Theme.Context = {
themeValue: ThemeColor.Light,
cookieTheme: ThemeColor.Light,
@@ -26,11 +28,30 @@ const {
useData,
useInit,
} = initContext(initialContext, (serverTheme) => {
- const [ { cookieTheme, systemTheme, isSystemTheme }, setState ] = useObjectState({
- cookieTheme: serverTheme.value || ThemeColor.Light,
- isSystemTheme: serverTheme.isSystemTheme,
- systemTheme: serverTheme.isSystemTheme && serverTheme.value ? serverTheme.value : ThemeColor.Light,
- })
+ const getInitialTheme = (): Theme.State => {
+ if (isIpfs) {
+ const savedTheme = cookie.get(constants.cookieNames.themeColor) as ThemeColor
+ const savedIsSystem = cookie.get(constants.cookieNames.isSystemTheme)
+
+ if (savedTheme || savedIsSystem) {
+ const isSystem = savedIsSystem === 'true'
+
+ return {
+ isSystemTheme: isSystem,
+ cookieTheme: savedTheme || ThemeColor.Light,
+ systemTheme: isSystem ? (savedTheme || ThemeColor.Light) : ThemeColor.Light,
+ }
+ }
+ }
+
+ return {
+ isSystemTheme: serverTheme.isSystemTheme,
+ cookieTheme: serverTheme.value || ThemeColor.Light,
+ systemTheme: serverTheme.isSystemTheme && serverTheme.value ? serverTheme.value : ThemeColor.Light,
+ }
+ }
+
+ const [ { cookieTheme, systemTheme, isSystemTheme }, setState ] = useObjectState(getInitialTheme())
const setThemeClassName = useCallback((theme: ThemeColor) => {
const isDark = theme === ThemeColor.Dark
@@ -71,6 +92,9 @@ const {
darkThemeQuery.removeEventListener('change', handleSetTheme)
}
}
+ else {
+ setThemeClassName(cookieTheme)
+ }
}, [ isSystemTheme, getSystemTheme, setThemeClassName, setState ])
const setTheme = useCallback((theme: ThemeValue) => {
diff --git a/src/store/store/vault/base.ts b/src/store/store/vault/base.ts
index 9b5394bc..566d1292 100644
--- a/src/store/store/vault/base.ts
+++ b/src/store/store/vault/base.ts
@@ -11,6 +11,7 @@ type BaseData = Omit & {
isPostPectra: boolean
avgQueueDays: number
protocolFeePercent: string
+ subVaultsCurator: string | null
versions: Awaited>
}
@@ -24,7 +25,7 @@ export interface BaseState {
export const initialState: BaseState = {
data: {
- apy: 0,
+ apy: 0,
baseApy: 0,
imageUrl: '',
createdAt: 0,
@@ -43,13 +44,21 @@ export const initialState: BaseState = {
feeRecipient: '',
blocklistCount: 0,
whitelistCount: 0,
+ subVaultsCount: 0,
lastFeePercent: 0,
+ canHarvest: false,
queuedShares: '0',
+ exitingAssets: '0',
+ exitingTickets: '0',
depositDataRoot: '',
whitelistManager: '',
blocklistManager: '',
+ subVaultsCurator: '',
+ ejectingSubVault: '',
validatorsManager: '',
+ subVaultsRegistry: '',
depositDataManager: '',
+ pendingMetaSubVault: '',
protocolFeePercent: '0',
allocatorMaxBoostApy: 0,
lastFeeUpdateTimestamp: '0',
diff --git a/src/styles/tailwind/layers/base.css b/src/styles/tailwind/layers/base.css
index 9b27e637..751d4f00 100644
--- a/src/styles/tailwind/layers/base.css
+++ b/src/styles/tailwind/layers/base.css
@@ -137,12 +137,6 @@
resize: none;
}
- input::-webkit-contacts-auto-fill-button,
- select::-webkit-contacts-auto-fill-button,
- textarea::-webkit-contacts-auto-fill-button {
- visibility: hidden;
- }
-
input:-webkit-autofill,
select:-webkit-autofill,
textarea:-webkit-autofill,
@@ -157,8 +151,7 @@
color: transparent;
text-shadow: 0 0 0 #000;
-webkit-appearance: none;
- -moz-appearance: radio-container;
- appearance: normal;
+ appearance: auto;
}
select::-ms-expand {
diff --git a/src/styles/tailwind/layers/components.css b/src/styles/tailwind/layers/components.css
index 74a52274..77ba96c2 100644
--- a/src/styles/tailwind/layers/components.css
+++ b/src/styles/tailwind/layers/components.css
@@ -1,150 +1,61 @@
@layer components {
- /* Typography */
- .text-h100 {
- font-weight: 700;
- font-size: 100px;
- line-height: 110px;
- }
-
- .text-h90 {
- font-weight: 700;
- font-size: 90px;
- line-height: 100px;
- }
-
- .text-h60 {
+ .text-h1 {
font-weight: 700;
font-size: 60px;
line-height: 72px;
}
- .text-h48 {
+ .text-h2 {
font-weight: 700;
font-size: 48px;
- line-height: 56px;
+ line-height: 58px;
}
- .text-h40 {
+ .text-h3 {
font-weight: 700;
font-size: 40px;
line-height: 48px;
}
- .text-h32 {
- font-weight: 700;
- font-size: 32px;
- line-height: 44px;
- }
-
- .text-h24 {
- font-weight: 700;
- font-size: 24px;
- line-height: 32px;
- }
-
- .text-h22 {
- font-weight: 700;
- font-size: 22px;
- line-height: 26px;
- }
-
- .text-h20 {
+ .text-h4 {
font-weight: 700;
- font-size: 20px;
- line-height: 24px;
+ font-size: 30px;
+ line-height: 38px;
}
- .text-t40m {
+ .text-h5 {
font-weight: 500;
- font-size: 40px;
- line-height: 48px;
+ font-size: 28px;
+ line-height: 40px;
}
- .text-t20b {
- font-weight: 700;
- font-size: 20px;
- line-height: 32px;
- }
-
- .text-t18m {
+ .text-h6 {
font-weight: 500;
- font-size: 18px;
- line-height: 24px;
+ font-size: 24px;
+ line-height: 30px;
}
- .text-t18b {
- font-weight: 700;
+ .text-lg {
font-size: 18px;
- line-height: 24px;
- }
-
- .text-t16m {
- font-weight: 500;
- font-size: 16px;
- line-height: 22px;
+ line-height: 26px;
}
- .text-t16b {
- font-weight: 700;
+ .text-md {
font-size: 16px;
line-height: 22px;
}
- .text-t14m {
- font-weight: 500;
- font-size: 14px;
- line-height: 20px;
- }
-
- .text-t14b {
- font-weight: 700;
+ .text-sm {
font-size: 14px;
line-height: 20px;
}
- .text-t14 {
- font-weight: 400;
- font-size: 14px;
- line-height: 20px;
- }
-
- .text-t12m {
- font-weight: 500;
- font-size: 12px;
- line-height: 18px;
- }
-
- .text-t12b {
- font-weight: 700;
- font-size: 12px;
- line-height: 18px;
- }
-
- .text-t12 {
- font-weight: 400;
+ .text-xs {
font-size: 12px;
line-height: 18px;
}
- .text-n10m {
- font-weight: 500;
- font-size: 10px;
- line-height: 16px;
- }
-
- .text-n10b {
- font-weight: 700;
- font-size: 10px;
- line-height: 16px;
- }
-
- .text-n10 {
- font-weight: 400;
- font-size: 10px;
- line-height: 16px;
- }
-
/* Ellipsis */
.overflow-ellipsis {
overflow: hidden;
diff --git a/src/styles/tailwind/layers/utilities.css b/src/styles/tailwind/layers/utilities.css
index efeb85bc..ad487cbb 100644
--- a/src/styles/tailwind/layers/utilities.css
+++ b/src/styles/tailwind/layers/utilities.css
@@ -60,6 +60,14 @@
border-radius: var(--radius-16);
}
+ .rounded-20 {
+ border-radius: var(--radius-20);
+ }
+
+ .rounded-30 {
+ border-radius: var(--radius-30);
+ }
+
.rounded-24 {
border-radius: var(--radius-24);
}
@@ -114,6 +122,20 @@
text-decoration: underline;
}
+ .text-link {
+ color: rgb(var(--primary-rgb));
+ background-image: linear-gradient(currentColor, currentColor);
+ background-repeat: no-repeat;
+ background-position: 50% 100%;
+ background-size: 98% 1px;
+ cursor: pointer;
+ transition: background-size .35s ease;
+ }
+
+ .text-link:hover {
+ background-size: 0 1px;
+ }
+
.hide-empty:empty {
display: none;
}
@@ -126,4 +148,38 @@
.transparent {
color: rgba(255, 255, 255, 0);
}
+
+ .backdrop-none {
+ backdrop-filter: none !important;
+ }
+
+ .backdrop-20 {
+ backdrop-filter: blur(20px);
+ }
+
+ .backdrop-30 {
+ backdrop-filter: blur(30px);
+ }
+
+ .scrollbar-none {
+ scrollbar-width: none; /* Firefox */
+ }
+
+ .scrollbar-none::-webkit-scrollbar {
+ display: none;
+ }
+
+ .custom-scrollbar {
+ scrollbar-width: thin;
+ scrollbar-color: rgba(var(--secondary-rgb), .5) transparent;
+ }
+
+ .custom-scrollbar::-webkit-scrollbar-track {
+ width: 10px;
+ background-color: rgba(var(--secondary-rgb), .5) transparent;
+ }
+
+ .custom-scrollbar::-webkit-scrollbar-thumb {
+ background-color: transparent;
+ }
}
diff --git a/src/styles/tailwind/theme.css b/src/styles/tailwind/theme.css
index 9ffb03cb..9f642647 100644
--- a/src/styles/tailwind/theme.css
+++ b/src/styles/tailwind/theme.css
@@ -21,6 +21,8 @@
--radius-8: 8px;
--radius-12: 12px;
--radius-16: 16px;
+ --radius-20: 20px;
+ --radius-30: 30px;
--radius-24: 24px;
--radius-72: 72px;
@@ -33,6 +35,17 @@
--blur-2xl: 40px;
--blur-3xl: 64px;
+ /* Font Weight */
+ --font-weight-thin: 100;
+ --font-weight-extralight: 200;
+ --font-weight-light: 300;
+ --font-weight-normal: 400;
+ --font-weight-medium: 500;
+ --font-weight-semibold: 600;
+ --font-weight-bold: 700;
+ --font-weight-extrabold: 800;
+ --font-weight-black: 900;
+
/* Animation */
--animate-spin: spin 1s linear infinite;
diff --git a/src/views/ErrorView/ErrorView.tsx b/src/views/ErrorView/ErrorView.tsx
index e3c2704b..ae7e066d 100644
--- a/src/views/ErrorView/ErrorView.tsx
+++ b/src/views/ErrorView/ErrorView.tsx
@@ -26,13 +26,13 @@ const ErrorView: React.FC = ({ error }) => {
className="text-center"
message={pageMessages.title}
color="dark"
- size="h32"
+ size="h4"
/>
diff --git a/src/views/SwapView/common/ChartWithFilter/ChartWithFilter.tsx b/src/views/SwapView/common/ChartWithFilter/ChartWithFilter.tsx
index 2a41e0ac..7a4a3851 100644
--- a/src/views/SwapView/common/ChartWithFilter/ChartWithFilter.tsx
+++ b/src/views/SwapView/common/ChartWithFilter/ChartWithFilter.tsx
@@ -16,6 +16,7 @@ export type ChartWithFilterProps = {
tabsItems: TabsItems
vaultAddress: string
dataTestId?: string
+ showExtraRewards?: boolean
noItemsDescription?: Intl.Message
onExportButtonClick?: () => void
}
@@ -26,6 +27,7 @@ const ChartWithFilter: React.FC