diff --git a/src/components/auth/AuthContext.tsx b/src/components/auth/AuthContext.tsx index b77325b37..d3c0405c9 100644 --- a/src/components/auth/AuthContext.tsx +++ b/src/components/auth/AuthContext.tsx @@ -1,6 +1,7 @@ import { u128 } from '@polkadot/types' import { CodecMap } from '@polkadot/types/codec' import { isStr } from '@subsocial/utils' +import BigNumber from 'bignumber.js' import BN from 'bn.js' import { useRouter } from 'next/router' import React, { createContext, useContext, useEffect, useState } from 'react' @@ -55,7 +56,12 @@ export type AuthContextProps = { setEmail: React.Dispatch> } -const energyStub: EnergyState = { status: 'normal', transactionsCount: 0, coefficient: 1 } +const energyStub: EnergyState = { + energyBalance: new BigNumber(0), + status: 'normal', + transactionsCount: 0, + coefficient: 1, +} const contextStub: AuthContextProps = { state: { diff --git a/src/components/energy/Energy.module.sass b/src/components/energy/Energy.module.sass index 0974791a3..cd2e5133b 100644 --- a/src/components/energy/Energy.module.sass +++ b/src/components/energy/Energy.module.sass @@ -1,75 +1,247 @@ @import 'src/styles/subsocial-vars.scss' -.DropdownOverlay - background-color: #fff - width: 250px - box-shadow: $shadow +.EnergyStationLayout + display: flex + gap: $space_normal -.DropdownOverlayContent - padding: $space_normal $space_big 0 $space_big +.LeftSideLayout + display: flex + flex-direction: column + gap: $space_normal -.OverlayCollapse - padding-bottom: $space_normal + width: 70% - \:global .ant-collapse-header - padding: 0 !important - padding-left: 3rem !important +.RightSideLayout + display: flex + flex-direction: column + gap: $space_normal - \:global .ant-collapse-arrow - left: $space_big !important - font-size: $font_normal !important + width: 30% - \:global .ant-collapse-content-box - padding: $space_small $space_big 0 $space_big !important +.TitleSection + display: flex + flex-direction: column + gap: $space_small -.QuestionCircle - color: $color_primary + .Title + font-size: $font_big + line-height: 26px + font-weight: 700 + color: $color_font_normal -.OnlyProgress - cursor: pointer + .Description + font-size: $font_small + line-height: 22px + color: #64748B -.CallsValue - font-size: 19px -.CallsLowValue - color: $color_gold -.CallsZeroValue - color: $color_error +.EnergyStatsSection + display: flex + gap: $space_normal + align-items: center -.Warning - background-color: #FFFBE6 - padding: $space_normal $space_big $space_small $space_big +.StatsCard + display: flex + gap: $space_tiny + flex-direction: column + width: 100% + border-radius: 20px + padding: $space_normal + background: white -.Error - background-color: #F9D8D8 + .StatsTitle + color: #64748B + font-size: $font_small + font-weight: 400 + line-height: 22px -.EnergyIndicator - display: flex + .StatsValue + display: flex + gap: $space_mini + align-items: center -.Title - font-size: 44px + svg + color: rgba(100, 116, 139, 0.8) + font-size: 20px -.Intro - text-align: center - margin-bottom: 32px + span + color: #0F172A + font-size: $space_large + font-weight: 600 + line-height: 32px + +.EnergyFormCard + border-radius: 20px + + background-color: #fff + box-shadow: 0px 4px 10.9px 0px #edf2f6a1 -.InfoSection - border: 1px solid $color_light_border - border-radius: $border_radius_normal padding: $space_normal - margin-top: $space_normal + display: flex + flex-direction: column + gap: $space_normal + +.EnergyFormTitle + font-size: $font_large + font-weight: 700 + line-height: 26px + color: $color_font_normal + +.EnergyForm + display: flex + flex-direction: column + gap: $space_normal + + :global(.ant-form-item) + margin-bottom: 0 .AmountFormInput + :global(.ant-form-item-label) + padding: 0 + label width: 100% + margin-bottom: 0 + line-height: 26px + +.InfoSection + display: flex + gap: $space_small + align-items: center + + .Divider + color: #64748B + + .InfoSectionItem + width: 100% + background: #F8FAFC + border-radius: 20px + padding: $space_normal + + display: flex + flex-direction: column + gap: $space_tiny + + .ItemTitle + font-size: $font_small + font-weight: 400 + line-height: 22px + color: #64748B + + .ItemValue + font-size: $font_large + font-weight: 600 + line-height: 32px + color: #0F172A + +.EnergyFormButton + height: 46px !important + + &:disabled + background-color: rgba(235, 47, 149, 0.5) !important + color: white + + &:hover + color: white + +.FAQWrapper + background: white + border-radius: 20px + padding: $space_normal + + display: flex + flex-direction: column + gap: $space_normal + + .FAQTitleSection + display: flex + align-items: center + justify-content: space-between + gap: $space_tiny + + .Title + font-size: $font_large + font-weight: 700 + line-height: 26px + color: $color_font_normal + + :global(.ant-collapse) + display: flex + flex-direction: column + gap: $space_normal + + :global(.ant-collapse-content-box) + padding: $space_small 0 0 $space_large !important + + p + color: #64748B + margin-bottom: 0 + + :global(.ant-collapse-header) + padding: 0 !important + padding-left: $space_large !important + + color: $color_font_normal !important + font-size: $font_small !important + line-height: 22px !important + b + font-weight: 500 !important + + :global(.ant-collapse-arrow) + left: 0 !important + top: 11px !important + +.QuestionWrapper + display: flex + flex-direction: column + gap: $space_normal + + background: white + border-radius: 20px + padding: $space_normal + margin-bottom: $space_normal -.Faq - \:global .ant-card-body - padding: 12px + a + width: fit-content + display: flex + align-items: center + justify-content: center + + .QuestionTitle + font-size: $font_large + font-weight: 700 + line-height: 26px + color: $color_font_normal -.FontSmall - font-size: $font_small +@media (max-width: 875px) + .EnergyStationLayout + flex-direction: column -.EnergyIcon - path - stroke-width: 36px + .LeftSideLayout + width: 100% + + .RightSideLayout + + width: 100% + + .QuestionWrapper + align-items: center + +@media (max-width: 560px) + .InfoSection + flex-direction: column + + .EnergyStatsSection + flex-direction: column + +.FormAlert + border-radius: 12px + +.RecipientInput + :global(.ant-form-item-label) + padding: 0 + + label + width: 100% + margin-bottom: 0 + line-height: 26px diff --git a/src/components/energy/EnergyForm.tsx b/src/components/energy/EnergyForm.tsx index 8046f5735..f20b17dfb 100644 --- a/src/components/energy/EnergyForm.tsx +++ b/src/components/energy/EnergyForm.tsx @@ -5,21 +5,10 @@ import { isDefined, nonEmptyStr, } from '@subsocial/utils' -import { - Alert, - Button, - Card, - CardProps, - Checkbox, - Col, - Divider, - Form, - Input, - Row, - Tooltip, -} from 'antd' +import { Alert, Button, CardProps, Checkbox, Col, Form, Input, Row, Tooltip } from 'antd' import { FormInstance } from 'antd/es/form/Form' import BigNumber from 'bignumber.js' +import clsx from 'clsx' import React, { useEffect, useState } from 'react' import { useMyAccount } from 'src/stores/my-account' import { useAuth } from '../auth/AuthContext' @@ -27,7 +16,8 @@ import { FormatBalance } from '../common/balances' import { useSubstrate } from '../substrate' import { AccountInputField } from '../utils/forms/AccountInputField' import { createFieldNameFn } from '../utils/forms/utils' -import { MutedDiv, MutedSpan } from '../utils/MutedText' +import { MutedSpan } from '../utils/MutedText' +import TokenBalance from '../utils/TokenBalance' import TxButton from '../utils/TxButton' import styles from './Energy.module.sass' import { EnergySuccessModal } from './SuccessModal' @@ -150,39 +140,37 @@ const EnergyInfoSection = ({ amount }: EnergyInfoSectionProps) => { const txsCount = amount ? calculateTransactionCount(amount, coefficient, tokenDecimal) : 0 return (
- - -
- - Transactions with SUB{' '} - - - - -
~ {(txsCount / coefficient).toFixed(0)}
-
- - - - - Transactions with energy{' '} - - - - -
~ {txsCount.toFixed(0)}
- -
+
+
+ Transactions with SUB{' '} + + + +
+
+ ~ {} +
+
+
VS
+
+
+ Transactions with energy{' '} + + + +
+
~ {}
+
) } @@ -198,7 +186,7 @@ export interface EnergyFormProps extends CardProps { setIsDisabled: (disabled: boolean) => void } } -const EnergyForm = ({ forSelfOnly, subscribeValues, ...props }: EnergyFormProps) => { +const EnergyForm = ({ forSelfOnly, subscribeValues, className }: EnergyFormProps) => { const [form] = Form.useForm() const [amount, setAmount] = useState() const [amountWithoutValidation, setAmountWithoutValidation] = useState() @@ -245,8 +233,9 @@ const EnergyForm = ({ forSelfOnly, subscribeValues, ...props }: EnergyFormProps) }, [disableSubscriber, amount]) return ( - -
+
+
Get more energy!
+ - +
)} {!isAnotherRecipient && hasProxy && ( )} @@ -274,8 +263,8 @@ const EnergyForm = ({ forSelfOnly, subscribeValues, ...props }: EnergyFormProps) name={fieldName('recipient')} onBlur={subscribeAddress} size='large' - className='mt-3' label='Address of the recipient' + className={styles.RecipientInput} /> )} @@ -283,8 +272,8 @@ const EnergyForm = ({ forSelfOnly, subscribeValues, ...props }: EnergyFormProps) {!subscribeValues?.noButton && ( setSuccess(true)} + className={styles.EnergyFormButton} /> )} setSuccess(false)} /> -
+ ) } diff --git a/src/components/energy/EnergyPage.tsx b/src/components/energy/EnergyPage.tsx index c1d8db5aa..b800ecfce 100644 --- a/src/components/energy/EnergyPage.tsx +++ b/src/components/energy/EnergyPage.tsx @@ -1,9 +1,10 @@ -import clsx from 'clsx' +import { useMyAddress } from '../auth/MyAccountsContext' import { HeadMetaProps, PageContent } from '../main/PageWrapper' -import { MutedDiv } from '../utils/MutedText' import styles from './Energy.module.sass' import EnergyForm from './EnergyForm' +import EnergyStats from './EnergyStats' import { FaqSection } from './Faq' +import QuestionSection from './QuestionSection' const desc = 'Energy allows you to use Subsocial. You can create energy here by burning SUB, and see the approximate number of transactions it will allow you to complete.' @@ -15,20 +16,22 @@ const meta: HeadMetaProps = { } export const EnergyPage = () => { + const myAddress = useMyAddress() + return ( - -
-
Energy Station
+ +
+
+
Energy Station
+
{desc}
+
+ {myAddress && } + +
+
+ +
-
- {desc} -
- -
) } diff --git a/src/components/energy/EnergyStats.tsx b/src/components/energy/EnergyStats.tsx new file mode 100644 index 000000000..e8ca2c114 --- /dev/null +++ b/src/components/energy/EnergyStats.tsx @@ -0,0 +1,75 @@ +import { QuestionCircleOutlined } from '@ant-design/icons' +import { convertToBalanceWithDecimal } from '@subsocial/utils' +import { Tooltip } from 'antd' +import BN from 'bignumber.js' +import { TiFlashOutline } from 'react-icons/ti' +import { useAuth } from '../auth/AuthContext' +import { useSubstrate } from '../substrate' +import TokenBalance from '../utils/TokenBalance' +import styles from './Energy.module.sass' + +const EnergyStats = () => { + const { + energy: { transactionsCount, energyBalance }, + } = useAuth() + const { tokenDecimal } = useSubstrate() + + const energyBalanceWithDecimal = + energyBalance && tokenDecimal + ? convertToBalanceWithDecimal(energyBalance.toString(), tokenDecimal) + : new BN(0) + + const energyLeft = + const transactionsLeft = + + const data = [ + { + title: 'Energy left', + value: energyLeft, + withEnergyIcon: true, + tooltipText: + 'The amount of energy you have left. Please generate more energy before this reaches 0.', + }, + { + title: 'Transactions left', + value: transactionsLeft, + withEnergyIcon: false, + tooltipText: + 'How many transactions you can do with your remaining energy. Please generate more energy before this reaches 0.', + }, + ] + + return ( +
+ {data.map((props, i) => ( + + ))} +
+ ) +} + +type StatsCardProps = { + title: string + value: React.ReactNode + withEnergyIcon: boolean + tooltipText: string +} + +const StatsCard = ({ title, value, withEnergyIcon, tooltipText }: StatsCardProps) => { + return ( +
+
+ {title}{' '} + + + +
+
+ {withEnergyIcon && } + {value} +
+
+ ) +} + +export default EnergyStats diff --git a/src/components/energy/Faq.tsx b/src/components/energy/Faq.tsx index 31cc61821..a6237a84f 100644 --- a/src/components/energy/Faq.tsx +++ b/src/components/energy/Faq.tsx @@ -1,18 +1,17 @@ import { Collapse } from 'antd' -import { LocalIcon } from 'src/components/utils' -import { CardWithTitle } from '../utils/cards/WithTitle' +import ExternalLink from '../spaces/helpers/ExternalLink' import styles from './Energy.module.sass' const { Panel } = Collapse export const FaqSection = () => { return ( - } - > - +
+
+
FAQ
+ +
+ What is energy?} key='1'>

Energy can be used to perform actions on the network, instead of SUB tokens. Energy can @@ -39,6 +38,6 @@ export const FaqSection = () => {

- +
) } diff --git a/src/components/energy/QuestionSection.tsx b/src/components/energy/QuestionSection.tsx new file mode 100644 index 000000000..2b174ae17 --- /dev/null +++ b/src/components/energy/QuestionSection.tsx @@ -0,0 +1,16 @@ +import { Button } from 'antd' +import styles from './Energy.module.sass' + +const QuestionSection = () => { + return ( +
+
Still have questions?
+ + +
+ ) +} + +export default QuestionSection diff --git a/src/components/energy/index.tsx b/src/components/energy/index.tsx deleted file mode 100644 index 990160edb..000000000 --- a/src/components/energy/index.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import { Collapse, Divider, Dropdown } from 'antd' -import clsx from 'clsx' -import { IoFlashOutline } from 'react-icons/io5' -import { useAuth } from '../auth/AuthContext' -import { ButtonLink } from '../utils/CustomLinks' -import { MutedDiv } from '../utils/MutedText' -import { BareProps } from '../utils/types' -import styles from './Energy.module.sass' -import { EnergyStatus } from './utils' - -export type CommonEnergyProps = { - status: EnergyStatus -} - -const energyColors: { [key in EnergyStatus]: string } = { - low: '#E89C29', - normal: '#94A3B8', - zero: '#F5222D', -} - -export const EnergyIndicator = ({ status }: CommonEnergyProps & BareProps) => ( -
- -
-) - -const { Panel } = Collapse - -const DropdownOverlay = () => { - const { - energy: { transactionsCount, coefficient, status }, - } = useAuth() - - return ( -
-
-
- Transactions left: -
- - {transactionsCount === 0 - ? transactionsCount.toString() - : `~ ${transactionsCount.toFixed(0)}`} - -
-
-
- Energy coefficient: -
x{coefficient}
-
- - Get more energy - -
- - - - - - - Energy lets you use Subsocial to make posts, follow your friends, like their content, - etc. NRG can be created by burning SUB tokens, and enables you to perform {coefficient}x - more actions than if you used SUB. - - - -
- ) -} - -const EnergyDropdown = () => { - const { - energy: { status }, - } = useAuth() - - return ( - <> - } - placement={'bottomCenter'} - trigger={['hover', 'click']} - > -
- -
-
- - ) -} - -export default EnergyDropdown diff --git a/src/components/energy/utils.ts b/src/components/energy/utils.ts index 65027d10d..43f1e053f 100644 --- a/src/components/energy/utils.ts +++ b/src/components/energy/utils.ts @@ -8,6 +8,7 @@ export type EnergyState = { status: EnergyStatus transactionsCount: number coefficient: number + energyBalance: BN } export const getEnergyCoef = async (api: ApiPromise) => { @@ -51,5 +52,6 @@ export const calculateEnergyState = ( coefficient, transactionsCount, status, + energyBalance, } } diff --git a/src/components/utils/TokenBalance.tsx b/src/components/utils/TokenBalance.tsx new file mode 100644 index 000000000..2dee11dc6 --- /dev/null +++ b/src/components/utils/TokenBalance.tsx @@ -0,0 +1,52 @@ +type TokenBalanceProps = { + value: string + defaultMaximumFractionDigits?: number + symbol?: string + startFromSymbol?: boolean +} + +type FormatBalance = { + value: string + defaultMaximumFractionDigits?: number +} + +export const formatBalance = ({ value, defaultMaximumFractionDigits = 2 }: FormatBalance) => { + const [intPart, decimalPart] = value.split('.') + + let maximumFractionDigits = defaultMaximumFractionDigits + + if (intPart === '0' && Number(decimalPart) > 0) { + const firstNonZeroIndex = decimalPart.split('').findIndex(char => char !== '0') + 1 + + if (firstNonZeroIndex > 5) { + maximumFractionDigits = defaultMaximumFractionDigits + } else { + maximumFractionDigits = firstNonZeroIndex + } + } + + return Number(value).toLocaleString('en-US', { + minimumFractionDigits: 0, + maximumFractionDigits, + }) +} + +const TokenBalance = ({ + value, + defaultMaximumFractionDigits, + startFromSymbol: startFromSybmol, + symbol, +}: TokenBalanceProps) => { + const formattedValue = formatBalance({ + value: value, + defaultMaximumFractionDigits, + }) + + const formattedValueWithSymbol = startFromSybmol + ? `${symbol}${formattedValue}` + : `${formattedValue} ${symbol}` + + return <>{symbol ? formattedValueWithSymbol : formattedValue} +} + +export default TokenBalance diff --git a/src/components/utils/cards/WithTitle.tsx b/src/components/utils/cards/WithTitle.tsx index 63c99a240..afd7b0612 100644 --- a/src/components/utils/cards/WithTitle.tsx +++ b/src/components/utils/cards/WithTitle.tsx @@ -1,15 +1,23 @@ import { Card } from 'antd' +import clsx from 'clsx' import React, { FC } from 'react' export type CardWithTitleProps = { title: React.ReactNode icon: React.ReactNode cardClassName?: string + className?: string } -export const CardWithTitle: FC = ({ children, title, icon, cardClassName }) => { +export const CardWithTitle: FC = ({ + children, + title, + icon, + cardClassName, + className, +}) => { return ( -
+

{icon} {title} diff --git a/src/stores/analytics.ts b/src/stores/analytics.ts index 9d7f82306..46d75ad29 100644 --- a/src/stores/analytics.ts +++ b/src/stores/analytics.ts @@ -1,5 +1,5 @@ import { createInstance } from '@amplitude/analytics-browser' -import { type BaseEvent, type BrowserClient } from '@amplitude/analytics-types' +import { BaseEvent, BrowserClient } from '@amplitude/analytics-types' import Router from 'next/router' import { createUserId } from 'src/components/utils/OffchainUtils' import { ampId } from 'src/config/env' diff --git a/src/styles/subsocial-vars.scss b/src/styles/subsocial-vars.scss index f579b90ba..09bdcb9c3 100644 --- a/src/styles/subsocial-vars.scss +++ b/src/styles/subsocial-vars.scss @@ -1,12 +1,12 @@ /*-------------Font Size-----------*/ -$font_tiny: 0.75rem; -$font_small: 0.875rem; -$font_normal: 1rem; -$font_semilarge: 1.125rem; -$font_large: 1.25rem; -$font_big: 1.5rem; -$font_huge: 2rem; +$font_tiny: 0.75rem; // 12px +$font_small: 0.875rem; // 14px +$font_normal: 1rem; // 16px +$font_semilarge: 1.125rem; // 18px +$font_large: 1.25rem; // 20px +$font_big: 1.5rem; // 24px +$font_huge: 2rem; // 32px /*-------------Font Weight-----------*/ @@ -17,16 +17,16 @@ $font_weight_bold: 700; /*----------Space Size-------------*/ -$space_mini: 0.25rem; -$space_semitiny: 0.375rem; -$space_tiny: 0.5rem; -$space_semismall: 0.625rem; -$space_small: 0.75rem; -$space_normal: 1rem; -$space_semilarge: 1.125rem; -$space_large: 1.25rem; -$space_big: 1.5rem; -$space_huge: 2rem; +$space_mini: 0.25rem; // 4px +$space_semitiny: 0.375rem; // 6px +$space_tiny: 0.5rem; // 8px +$space_semismall: 0.625rem; // 10px +$space_small: 0.75rem; // 12px +$space_normal: 1rem; // 16px +$space_semilarge: 1.125rem; // 18px +$space_large: 1.25rem; // 20px +$space_big: 1.5rem; // 24px +$space_huge: 2rem; // 32px /*-------------Colors----------*/