diff --git a/packages/app/.eslintrc.cjs b/packages/app/.eslintrc.cjs index c3e0837b..3fc75e48 100644 --- a/packages/app/.eslintrc.cjs +++ b/packages/app/.eslintrc.cjs @@ -20,7 +20,16 @@ module.exports = { "simple-import-sort/imports": "error", "simple-import-sort/exports": "error", "@typescript-eslint/no-unused-vars": "error", + "max-lines": ["warn", { max: 300, skipBlankLines: true, skipComments: true }], }, + overrides: [ + { + files: ["*.tsx"], + rules: { + "max-lines": ["warn", { max: 200, skipBlankLines: true, skipComments: true }], + }, + }, + ], root: true, ignorePatterns: ["build/", "*.test.ts", "*.js"], env: { diff --git a/packages/app/src/Components/SendSats/SendSats.tsx b/packages/app/src/Components/SendSats/SendSats.tsx index 182c6b99..e2a3adbc 100644 --- a/packages/app/src/Components/SendSats/SendSats.tsx +++ b/packages/app/src/Components/SendSats/SendSats.tsx @@ -3,30 +3,18 @@ import "./SendSats.css"; import { LNURLSuccessAction } from "@snort/shared"; import { HexKey } from "@snort/system"; import React, { ReactNode, useEffect, useState } from "react"; -import { FormattedMessage, useIntl } from "react-intl"; -import AsyncButton from "@/Components/Button/AsyncButton"; import CloseButton from "@/Components/Button/CloseButton"; -import Copy from "@/Components/Copy/Copy"; -import Icon from "@/Components/Icons/Icon"; import Modal from "@/Components/Modal/Modal"; -import QrCode from "@/Components/QrCode"; -import ProfileImage from "@/Components/User/ProfileImage"; +import { SendSatsInput, SendSatsInputSelection } from "@/Components/SendSats/SendSatsInput"; +import { SendSatsInvoice } from "@/Components/SendSats/SendSatsInvoice"; +import { SendSatsTitle } from "@/Components/SendSats/SendSatsTitle"; +import { SuccessAction } from "@/Components/SendSats/SuccessAction"; +import { ZapType } from "@/Components/SendSats/ZapType"; import useEventPublisher from "@/Hooks/useEventPublisher"; -import useLogin from "@/Hooks/useLogin"; import { debounce } from "@/Utils"; -import { formatShort } from "@/Utils/Number"; import { Zapper, ZapTarget, ZapTargetResult } from "@/Utils/Zapper"; -import { LNWallet, useWallet } from "@/Wallet"; - -import messages from "../messages"; - -enum ZapType { - PublicZap = 1, - AnonZap = 2, - PrivateZap = 3, - NonZap = 4, -} +import { useWallet } from "@/Wallet"; export interface SendSatsProps { onClose?: () => void; @@ -97,91 +85,14 @@ export default function SendSats(props: SendSatsProps) { } }, [props.targets, props.show]); - function successAction() { - if (!success) return null; - return ( -
-

- - {success?.description ?? } -

- {success.url && ( -

- - {success.url} - -

- )} -
- ); - } - - function title() { - if (!props.targets) { - return ( - <> -

- {zapper?.canZap() ? ( - - ) : ( - - )} -

- - ); - } - if (props.targets.length === 1 && props.targets[0].name) { - const t = props.targets[0]; - const values = { - name: t.name, - }; - return ( - <> - {t.zap?.pubkey && } -

- {zapper?.canZap() ? ( - - ) : ( - - )} -

- - ); - } - if (props.targets.length > 1) { - const total = props.targets.reduce((acc, v) => (acc += v.weight), 0); - - return ( -
-

- {zapper?.canZap() ? ( - - ) : ( - - )} -

-
- {props.targets.map(v => ( - - ))} -
-
- ); - } - } - if (!(props.show ?? false)) return null; return (
-
{props.title || title()}
+
+ {props.title || } +
{zapper && !invoice && ( @@ -227,180 +138,8 @@ export default function SendSats(props: SendSatsProps) { }} /> )} - {successAction()} + {success && }
); } - -interface SendSatsInputSelection { - amount: number; - comment?: string; - type: ZapType; -} - -function SendSatsInput(props: { - zapper: Zapper; - onChange?: (v: SendSatsInputSelection) => void; - onNextStage: (v: SendSatsInputSelection) => Promise; -}) { - const { defaultZapAmount, readonly } = useLogin(s => ({ - defaultZapAmount: s.appData.item.preferences.defaultZapAmount, - readonly: s.readonly, - })); - const { formatMessage } = useIntl(); - const amounts: Record = { - [defaultZapAmount.toString()]: "", - "1000": "👍", - "5000": "💜", - "10000": "😍", - "20000": "🤩", - "50000": "🔥", - "100000": "🚀", - "1000000": "🤯", - }; - const [comment, setComment] = useState(); - const [amount, setAmount] = useState(defaultZapAmount); - const [customAmount, setCustomAmount] = useState(defaultZapAmount); - const [zapType, setZapType] = useState(readonly ? ZapType.AnonZap : ZapType.PublicZap); - - function getValue() { - return { - amount, - comment, - type: zapType, - } as SendSatsInputSelection; - } - - useEffect(() => { - if (props.onChange) { - props.onChange(getValue()); - } - }, [amount, comment, zapType]); - - function renderAmounts() { - const min = props.zapper.minAmount() / 1000; - const max = props.zapper.maxAmount() / 1000; - const filteredAmounts = Object.entries(amounts).filter(([k]) => Number(k) >= min && Number(k) <= max); - - return ( -
- {filteredAmounts.map(([k, v]) => ( - setAmount(Number(k))}> - {v}  - {k === "1000" ? "1K" : formatShort(Number(k))} - - ))} -
- ); - } - - function custom() { - const min = props.zapper.minAmount() / 1000; - const max = props.zapper.maxAmount() / 1000; - - return ( -
- setCustomAmount(parseInt(e.target.value))} - /> - -
- ); - } - - return ( -
-
-

- -

- {renderAmounts()} - {custom()} - {props.zapper.maxComment() > 0 && ( - setComment(e.target.value)} - /> - )} -
- - {(amount ?? 0) > 0 && ( - props.onNextStage(getValue())}> - - - - )} -
- ); -} - -function SendSatsZapTypeSelector({ zapType, setZapType }: { zapType: ZapType; setZapType: (t: ZapType) => void }) { - const { readonly } = useLogin(s => ({ readonly: s.readonly })); - const makeTab = (t: ZapType, n: React.ReactNode) => ( - - ); - return ( -
-

- -

-
- {!readonly && - makeTab(ZapType.PublicZap, )} - {/*makeTab(ZapType.PrivateZap, "Private")*/} - {makeTab(ZapType.AnonZap, )} - {makeTab( - ZapType.NonZap, - , - )} -
-
- ); -} - -function SendSatsInvoice(props: { - invoice: Array; - wallet?: LNWallet; - notice?: ReactNode; - onInvoicePaid: () => void; -}) { - return ( -
- {props.notice && {props.notice}} - {props.invoice.map(v => ( - <> - -
- - - - -
- - ))} -
- ); -} diff --git a/packages/app/src/Components/SendSats/SendSatsInput.tsx b/packages/app/src/Components/SendSats/SendSatsInput.tsx new file mode 100644 index 00000000..04cf89bd --- /dev/null +++ b/packages/app/src/Components/SendSats/SendSatsInput.tsx @@ -0,0 +1,131 @@ +import React, { useEffect, useState } from "react"; +import { FormattedMessage, useIntl } from "react-intl"; + +import AsyncButton from "@/Components/Button/AsyncButton"; +import Icon from "@/Components/Icons/Icon"; +import messages from "@/Components/messages"; +import { SendSatsZapTypeSelector } from "@/Components/SendSats/SendSatsZapTypeSelector"; +import { ZapType } from "@/Components/SendSats/ZapType"; +import useLogin from "@/Hooks/useLogin"; +import { formatShort } from "@/Utils/Number"; +import { Zapper } from "@/Utils/Zapper"; + +export interface SendSatsInputSelection { + amount: number; + comment?: string; + type: ZapType; +} + +export function SendSatsInput(props: { + zapper: Zapper; + onChange?: (v: SendSatsInputSelection) => void; + onNextStage: (v: SendSatsInputSelection) => Promise; +}) { + const { defaultZapAmount, readonly } = useLogin(s => ({ + defaultZapAmount: s.appData.item.preferences.defaultZapAmount, + readonly: s.readonly, + })); + const { formatMessage } = useIntl(); + const amounts: Record = { + [defaultZapAmount.toString()]: "", + "1000": "👍", + "5000": "💜", + "10000": "😍", + "20000": "🤩", + "50000": "🔥", + "100000": "🚀", + "1000000": "🤯", + }; + const [comment, setComment] = useState(); + const [amount, setAmount] = useState(defaultZapAmount); + const [customAmount, setCustomAmount] = useState(defaultZapAmount); + const [zapType, setZapType] = useState(readonly ? ZapType.AnonZap : ZapType.PublicZap); + + function getValue() { + return { + amount, + comment, + type: zapType, + } as SendSatsInputSelection; + } + + useEffect(() => { + if (props.onChange) { + props.onChange(getValue()); + } + }, [amount, comment, zapType]); + + function renderAmounts() { + const min = props.zapper.minAmount() / 1000; + const max = props.zapper.maxAmount() / 1000; + const filteredAmounts = Object.entries(amounts).filter(([k]) => Number(k) >= min && Number(k) <= max); + + return ( +
+ {filteredAmounts.map(([k, v]) => ( + setAmount(Number(k))}> + {v}  + {k === "1000" ? "1K" : formatShort(Number(k))} + + ))} +
+ ); + } + + function custom() { + const min = props.zapper.minAmount() / 1000; + const max = props.zapper.maxAmount() / 1000; + + return ( +
+ setCustomAmount(parseInt(e.target.value))} + /> + +
+ ); + } + + return ( +
+
+

+ +

+ {renderAmounts()} + {custom()} + {props.zapper.maxComment() > 0 && ( + setComment(e.target.value)} + /> + )} +
+ + {(amount ?? 0) > 0 && ( + props.onNextStage(getValue())}> + + + + )} +
+ ); +} diff --git a/packages/app/src/Components/SendSats/SendSatsInvoice.tsx b/packages/app/src/Components/SendSats/SendSatsInvoice.tsx new file mode 100644 index 00000000..e4fa33fe --- /dev/null +++ b/packages/app/src/Components/SendSats/SendSatsInvoice.tsx @@ -0,0 +1,33 @@ +import React, { ReactNode } from "react"; +import { FormattedMessage } from "react-intl"; + +import Copy from "@/Components/Copy/Copy"; +import QrCode from "@/Components/QrCode"; +import { ZapTargetResult } from "@/Utils/Zapper"; +import { LNWallet } from "@/Wallet"; + +export function SendSatsInvoice(props: { + invoice: Array; + wallet?: LNWallet; + notice?: ReactNode; + onInvoicePaid: () => void; +}) { + return ( +
+ {props.notice && {props.notice}} + {props.invoice.map(v => ( + <> + +
+ + + + +
+ + ))} +
+ ); +} diff --git a/packages/app/src/Components/SendSats/SendSatsTitle.tsx b/packages/app/src/Components/SendSats/SendSatsTitle.tsx new file mode 100644 index 00000000..a6f94cce --- /dev/null +++ b/packages/app/src/Components/SendSats/SendSatsTitle.tsx @@ -0,0 +1,75 @@ +import React from "react"; +import { FormattedMessage } from "react-intl"; + +import { SendSatsInputSelection } from "@/Components/SendSats/SendSatsInput"; +import ProfileImage from "@/Components/User/ProfileImage"; +import { formatShort } from "@/Utils/Number"; +import { Zapper, ZapTarget } from "@/Utils/Zapper"; + +export function SendSatsTitle({ + targets, + zapper, + amount, +}: { + targets?: Array; + zapper?: Zapper; + amount?: SendSatsInputSelection; +}) { + if (!targets) { + return ( + <> +

+ {zapper?.canZap() ? ( + + ) : ( + + )} +

+ + ); + } + if (targets.length === 1 && targets[0].name) { + const t = targets[0]; + const values = { + name: t.name, + }; + return ( + <> + {t.zap?.pubkey && } +

+ {zapper?.canZap() ? ( + + ) : ( + + )} +

+ + ); + } + if (targets.length > 1) { + const total = targets.reduce((acc, v) => (acc += v.weight), 0); + + return ( +
+

+ {zapper?.canZap() ? ( + + ) : ( + + )} +

+
+ {targets.map(v => ( + + ))} +
+
+ ); + } +} diff --git a/packages/app/src/Components/SendSats/SendSatsZapTypeSelector.tsx b/packages/app/src/Components/SendSats/SendSatsZapTypeSelector.tsx new file mode 100644 index 00000000..6a53d091 --- /dev/null +++ b/packages/app/src/Components/SendSats/SendSatsZapTypeSelector.tsx @@ -0,0 +1,37 @@ +import React from "react"; +import { FormattedMessage } from "react-intl"; + +import { ZapType } from "@/Components/SendSats/ZapType"; +import useLogin from "@/Hooks/useLogin"; + +export function SendSatsZapTypeSelector({ + zapType, + setZapType, +}: { + zapType: ZapType; + setZapType: (t: ZapType) => void; +}) { + const { readonly } = useLogin(s => ({ readonly: s.readonly })); + const makeTab = (t: ZapType, n: React.ReactNode) => ( + + ); + return ( +
+

+ +

+
+ {!readonly && + makeTab(ZapType.PublicZap, )} + {/*makeTab(ZapType.PrivateZap, "Private")*/} + {makeTab(ZapType.AnonZap, )} + {makeTab( + ZapType.NonZap, + , + )} +
+
+ ); +} diff --git a/packages/app/src/Components/SendSats/SuccessAction.tsx b/packages/app/src/Components/SendSats/SuccessAction.tsx new file mode 100644 index 00000000..dde49de8 --- /dev/null +++ b/packages/app/src/Components/SendSats/SuccessAction.tsx @@ -0,0 +1,23 @@ +import { LNURLSuccessAction } from "@snort/shared"; +import React from "react"; +import { FormattedMessage } from "react-intl"; + +import Icon from "@/Components/Icons/Icon"; + +export function SuccessAction({ success }: { success: LNURLSuccessAction }) { + return ( +
+

+ + {success?.description ?? } +

+ {success.url && ( +

+ + {success.url} + +

+ )} +
+ ); +} diff --git a/packages/app/src/Components/SendSats/ZapType.tsx b/packages/app/src/Components/SendSats/ZapType.tsx new file mode 100644 index 00000000..de048fc7 --- /dev/null +++ b/packages/app/src/Components/SendSats/ZapType.tsx @@ -0,0 +1,6 @@ +export enum ZapType { + PublicZap = 1, + AnonZap = 2, + PrivateZap = 3, + NonZap = 4, +} diff --git a/packages/app/src/Components/User/AnimalName.ts b/packages/app/src/Components/User/AnimalName.ts index 6b08e55d..db792d6a 100644 --- a/packages/app/src/Components/User/AnimalName.ts +++ b/packages/app/src/Components/User/AnimalName.ts @@ -1,3 +1,5 @@ +/* eslint-disable max-lines */ + import { sha256 } from "@noble/hashes/sha256"; const animals = [