diff --git a/frontend/src/assets/icons/check.svg b/frontend/src/assets/icons/check.svg deleted file mode 100644 index abf43e11..00000000 --- a/frontend/src/assets/icons/check.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/src/assets/icons/edit.svg b/frontend/src/assets/icons/edit.svg deleted file mode 100644 index 70335684..00000000 --- a/frontend/src/assets/icons/edit.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/assets/icons/invoice.svg b/frontend/src/assets/icons/invoice.svg deleted file mode 100644 index a3230637..00000000 --- a/frontend/src/assets/icons/invoice.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/assets/icons/lightning.svg b/frontend/src/assets/icons/lightning.svg deleted file mode 100644 index 3b26738e..00000000 --- a/frontend/src/assets/icons/lightning.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/assets/icons/search.svg b/frontend/src/assets/icons/search.svg deleted file mode 100644 index 3815c805..00000000 --- a/frontend/src/assets/icons/search.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - \ No newline at end of file diff --git a/frontend/src/assets/icons/transactions.svg b/frontend/src/assets/icons/transactions.svg deleted file mode 100644 index b72fbfc9..00000000 --- a/frontend/src/assets/icons/transactions.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/frontend/src/assets/icons/wallet.svg b/frontend/src/assets/icons/wallet.svg deleted file mode 100644 index 0dd2ce00..00000000 --- a/frontend/src/assets/icons/wallet.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - \ No newline at end of file diff --git a/frontend/src/components/icons/CheckIcon.tsx b/frontend/src/components/icons/CheckIcon.tsx new file mode 100644 index 00000000..325f3f15 --- /dev/null +++ b/frontend/src/components/icons/CheckIcon.tsx @@ -0,0 +1,21 @@ +import { SVGAttributes } from "react"; + +export function CheckIcon(props: SVGAttributes) { + return ( + + + + ); +} diff --git a/frontend/src/components/icons/EditIcon.tsx b/frontend/src/components/icons/EditIcon.tsx new file mode 100644 index 00000000..f145c663 --- /dev/null +++ b/frontend/src/components/icons/EditIcon.tsx @@ -0,0 +1,16 @@ +import { SVGAttributes } from "react"; + +export function EditIcon(props: SVGAttributes) { + return ( + + + + ); +} diff --git a/frontend/src/components/icons/InvoiceIcon.tsx b/frontend/src/components/icons/InvoiceIcon.tsx new file mode 100644 index 00000000..f7904575 --- /dev/null +++ b/frontend/src/components/icons/InvoiceIcon.tsx @@ -0,0 +1,27 @@ +import { SVGAttributes } from "react"; + +export function InvoiceIcon(props: SVGAttributes) { + return ( + + + + ); +} +; diff --git a/frontend/src/components/icons/LightningIcon.tsx b/frontend/src/components/icons/LightningIcon.tsx new file mode 100644 index 00000000..3e79434b --- /dev/null +++ b/frontend/src/components/icons/LightningIcon.tsx @@ -0,0 +1,16 @@ +import { SVGAttributes } from "react"; + +export function LightningIcon(props: SVGAttributes) { + return ( + + + + ); +} diff --git a/frontend/src/components/icons/SearchIcon.tsx b/frontend/src/components/icons/SearchIcon.tsx new file mode 100644 index 00000000..0712504f --- /dev/null +++ b/frontend/src/components/icons/SearchIcon.tsx @@ -0,0 +1,25 @@ +import { SVGAttributes } from "react"; + +export function SearchIcon(props: SVGAttributes) { + return ( + + + + + ); +} diff --git a/frontend/src/components/icons/TransactionsIcon.tsx b/frontend/src/components/icons/TransactionsIcon.tsx new file mode 100644 index 00000000..3658dd92 --- /dev/null +++ b/frontend/src/components/icons/TransactionsIcon.tsx @@ -0,0 +1,33 @@ +import { SVGAttributes } from "react"; + +export function TransactionsIcon(props: SVGAttributes) { + return ( + + + + + + + + + ); +} diff --git a/frontend/src/components/icons/WalletIcon.tsx b/frontend/src/components/icons/WalletIcon.tsx new file mode 100644 index 00000000..8224f804 --- /dev/null +++ b/frontend/src/components/icons/WalletIcon.tsx @@ -0,0 +1,25 @@ +import { SVGAttributes } from "react"; + +export function WalletIcon(props: SVGAttributes) { + return ( + + + + + ); +} diff --git a/frontend/src/screens/apps/CreateApp.tsx b/frontend/src/screens/apps/CreateApp.tsx deleted file mode 100644 index 3b65cb94..00000000 --- a/frontend/src/screens/apps/CreateApp.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { useState } from "react"; - -const CreateApp = () => { - const pairingUri = "YOUR_PAIRING_URI"; // Replace with actual data or props - const [copied, setCopied] = useState(false); - - // useEffect(() => { - // // This would replace the script to create QR Code - // QrCreator.render( - // { - // fill: window.matchMedia('(prefers-color-scheme: dark)').matches ? "#FFF" : "#000", - // text: pairingUri, - // size: 250, - // }, - // document.getElementById("connect-qrcode") - // ); - // }, [pairingUri]); - - const copyToClipboard = async () => { - if (navigator.clipboard && window.isSecureContext) { - await navigator.clipboard.writeText(pairingUri); - } else { - // Fallback for older browsers - const textArea = document.createElement("textarea"); - textArea.value = pairingUri; - textArea.style.position = "absolute"; - textArea.style.opacity = "0"; - document.body.appendChild(textArea); - textArea.select(); - await document.execCommand("copy"); - textArea.remove(); - } - setCopied(true); - setTimeout(() => setCopied(false), 2000); - }; - - return ( -
-

- 🚀 Almost there! -

-
- Complete the last step of the setup by pasting or scanning your - connection's pairing secret in the desired app to finalise the - connection. -
- -
- - {/* SVGs are preserved */} - {/* ... SVG content */} - Open in supported app - -
- Only connect with apps you trust! -
- -
- Manually pair app ↓ -
- - - {/* ... Remaining JSX conversion for QR code and other elements */} -
-
- ); -}; - -export default CreateApp; diff --git a/frontend/src/screens/apps/NewApp.tsx b/frontend/src/screens/apps/NewApp.tsx index 8b982d1a..e07ee611 100644 --- a/frontend/src/screens/apps/NewApp.tsx +++ b/frontend/src/screens/apps/NewApp.tsx @@ -1,19 +1,33 @@ import { useState } from "react"; import { useLocation, useNavigate } from "react-router-dom"; +import toast from "react-hot-toast"; import { BudgetRenewalType, CreateAppResponse, + IconMap, + NIP_47_GET_BALANCE_METHOD, + NIP_47_GET_INFO_METHOD, + NIP_47_LIST_TRANSACTIONS_METHOD, + NIP_47_LOOKUP_INVOICE_METHOD, + NIP_47_MAKE_INVOICE_METHOD, + NIP_47_PAY_INVOICE_METHOD, RequestMethodType, nip47MethodDescriptions, - nip47MethodIcons, validBudgetRenewals, } from "../../types"; -import toast from "react-hot-toast"; import { handleFetchError, validateFetchResponse } from "../../utils/fetch"; import { useCSRF } from "../../hooks/useCSRF"; +import { WalletIcon } from "../../components/icons/WalletIcon"; +import { LightningIcon } from "../../components/icons/LightningIcon"; +import { InvoiceIcon } from "../../components/icons/InvoiceIcon"; +import { SearchIcon } from "../../components/icons/SearchIcon"; +import { TransactionsIcon } from "../../components/icons/TransactionsIcon"; +import { EditIcon } from "../../components/icons/EditIcon"; +import { useUser } from "../../hooks/useUser"; const NewApp = () => { const { data: csrf } = useCSRF(); + const { data: user } = useUser(); const navigate = useNavigate(); const location = useLocation(); @@ -41,7 +55,9 @@ const NewApp = () => { ); const maxAmountParam = queryParams.get("max_amount") ?? ""; - const [maxAmount, setMaxAmount] = useState(parseInt(maxAmountParam)); + const [maxAmount, setMaxAmount] = useState( + parseInt(maxAmountParam || "100000") + ); const parseExpiresParam = (expiresParam: string): string => { if (/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/.test(expiresParam)) { @@ -139,6 +155,15 @@ const NewApp = () => { } }; + const iconMap: IconMap = { + [NIP_47_GET_BALANCE_METHOD]: WalletIcon, + [NIP_47_GET_INFO_METHOD]: WalletIcon, + [NIP_47_LIST_TRANSACTIONS_METHOD]: TransactionsIcon, + [NIP_47_LOOKUP_INVOICE_METHOD]: SearchIcon, + [NIP_47_MAKE_INVOICE_METHOD]: InvoiceIcon, + [NIP_47_PAY_INVOICE_METHOD]: LightningIcon, + }; + return (

@@ -206,155 +231,157 @@ const NewApp = () => {

Authorize the app to:

{!reqMethodsParam && ( - setEdit(true)} - className="dark:invert opacity-80 inline cursor-pointer w-6" - src="/public/images/edit.svg" + className="text-gray-800 dark:text-gray-300 cursor-pointer w-6" /> )}
    - {Object.keys(nip47MethodDescriptions).map((rm, index) => ( -
  • -
    - - - -
    - {rm == "pay_invoice" && ( -
    - {!maxAmountParam ? ( - <> -

    - Monthly budget -

    -
    -
    setMaxAmount(10000)} - className={`col-span-2 md:col-span-1 cursor-pointer rounded border-2 ${ - maxAmount == 10000 - ? "border-purple-700 dark:border-purple-300 text-purple-700 bg-purple-100 dark:bg-purple-900" - : "border-gray-200 dark:border-gray-400" - } text-center py-4 dark:text-white`} - > - 10k -
    - sats -
    -
    setMaxAmount(25000)} - className={`col-span-2 md:col-span-1 cursor-pointer rounded border-2 ${ - maxAmount == 25000 - ? "border-purple-700 dark:border-purple-300 text-purple-700 bg-purple-100 dark:bg-purple-900" - : "border-gray-200 dark:border-gray-400" - } text-center py-4 dark:text-white`} - > - 25k -
    - sats -
    -
    setMaxAmount(50000)} - className={`col-span-2 md:col-span-1 cursor-pointer rounded border-2 ${ - maxAmount == 50000 - ? "border-purple-700 dark:border-purple-300 text-purple-700 bg-purple-100 dark:bg-purple-900" - : "border-gray-200 dark:border-gray-400" - } text-center py-4 dark:text-white`} - > - 50k -
    - sats -
    -
    setMaxAmount(100000)} - className={`col-span-2 md:col-span-1 cursor-pointer rounded border-2 ${ - maxAmount == 100000 - ? "border-purple-700 dark:border-purple-300 text-purple-700 bg-purple-100 dark:bg-purple-900" - : "border-gray-200 dark:border-gray-400" - } text-center py-4 dark:text-white`} - > - 100k -
    - sats -
    -
    setMaxAmount(1000000)} - className={`col-span-2 md:col-span-1 cursor-pointer rounded border-2 ${ - maxAmount == 1000000 - ? "border-purple-700 dark:border-purple-300 text-purple-700 bg-purple-100 dark:bg-purple-900" - : "border-gray-200 dark:border-gray-400" - } text-center py-4 dark:text-white`} - > - 1M -
    - sats -
    + {Object.keys(nip47MethodDescriptions).map((rm, index) => { + const RequestMethodIcon = iconMap[rm as RequestMethodType]; + return ( +
  • +
    + {RequestMethodIcon && ( + + )} + + +
    + {rm == "pay_invoice" && ( +
    + {!maxAmountParam ? ( + <> +

    + Monthly budget +

    setMaxAmount(0)} - className={`col-span-2 md:col-span-1 cursor-pointer rounded border-2 ${ - maxAmount == 0 - ? "border-purple-700 dark:border-purple-300 text-purple-700 bg-purple-100 dark:bg-purple-900" - : "border-gray-200 dark:border-gray-400" - } text-center py-4 dark:text-white`} + id="budget-allowance-limits" + className="grid grid-cols-6 grid-rows-2 md:grid-rows-1 md:grid-cols-6 gap-2 text-xs text-gray-800 dark:text-neutral-200" > - Unlimited -
    - #reckless +
    setMaxAmount(10000)} + className={`col-span-2 md:col-span-1 cursor-pointer rounded border-2 ${ + maxAmount == 10000 + ? "border-purple-700 dark:border-purple-300 text-purple-700 bg-purple-100 dark:bg-purple-900" + : "border-gray-200 dark:border-gray-400" + } text-center py-4 dark:text-white`} + > + 10k +
    + sats +
    +
    setMaxAmount(25000)} + className={`col-span-2 md:col-span-1 cursor-pointer rounded border-2 ${ + maxAmount == 25000 + ? "border-purple-700 dark:border-purple-300 text-purple-700 bg-purple-100 dark:bg-purple-900" + : "border-gray-200 dark:border-gray-400" + } text-center py-4 dark:text-white`} + > + 25k +
    + sats +
    +
    setMaxAmount(50000)} + className={`col-span-2 md:col-span-1 cursor-pointer rounded border-2 ${ + maxAmount == 50000 + ? "border-purple-700 dark:border-purple-300 text-purple-700 bg-purple-100 dark:bg-purple-900" + : "border-gray-200 dark:border-gray-400" + } text-center py-4 dark:text-white`} + > + 50k +
    + sats +
    +
    setMaxAmount(100000)} + className={`col-span-2 md:col-span-1 cursor-pointer rounded border-2 ${ + maxAmount == 100000 + ? "border-purple-700 dark:border-purple-300 text-purple-700 bg-purple-100 dark:bg-purple-900" + : "border-gray-200 dark:border-gray-400" + } text-center py-4 dark:text-white`} + > + 100k +
    + sats +
    +
    setMaxAmount(1000000)} + className={`col-span-2 md:col-span-1 cursor-pointer rounded border-2 ${ + maxAmount == 1000000 + ? "border-purple-700 dark:border-purple-300 text-purple-700 bg-purple-100 dark:bg-purple-900" + : "border-gray-200 dark:border-gray-400" + } text-center py-4 dark:text-white`} + > + 1M +
    + sats +
    +
    setMaxAmount(0)} + className={`col-span-2 md:col-span-1 cursor-pointer rounded border-2 ${ + maxAmount == 0 + ? "border-purple-700 dark:border-purple-300 text-purple-700 bg-purple-100 dark:bg-purple-900" + : "border-gray-200 dark:border-gray-400" + } text-center py-4 dark:text-white`} + > + Unlimited +
    + #reckless +
    -
    - - ) : ( - <> -

    - {budgetRenewal}{" "} - budget: - {maxAmount} sats -

    - - )} -
- )} - - ))} + + ) : ( + <> +

+ + {budgetRenewal} + {" "} + budget: {maxAmount} sats +

+ + )} +

+ )} + + ); + })} @@ -433,14 +460,29 @@ const NewApp = () => { )} -
-
+ + + Cancel + + )} + diff --git a/frontend/src/screens/apps/ShowApp.tsx b/frontend/src/screens/apps/ShowApp.tsx index fffcb364..66de9f0d 100644 --- a/frontend/src/screens/apps/ShowApp.tsx +++ b/frontend/src/screens/apps/ShowApp.tsx @@ -5,6 +5,7 @@ import { useApp } from "../../hooks/useApp"; import { handleFetchError, validateFetchResponse } from "../../utils/fetch"; import toast from "../../components/Toast"; import { useCSRF } from "../../hooks/useCSRF"; +import { RequestMethodType, nip47MethodDescriptions } from "../../types"; function ShowApp() { const { data: info } = useInfo(); @@ -92,7 +93,7 @@ function ShowApp() { {app.requestMethods.map((method, index) => (
  • ✓ - {method} + {nip47MethodDescriptions[method as RequestMethodType]}
  • ))} diff --git a/frontend/src/types.ts b/frontend/src/types.ts index a03bb80d..49e8608c 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -17,6 +17,12 @@ export type RequestMethodType = export type BudgetRenewalType = "daily" | "weekly" | "monthly" | "yearly" | ""; +export type IconMap = { + [key in RequestMethodType]: ( + props: React.SVGAttributes + ) => JSX.Element; +}; + export const validBudgetRenewals: BudgetRenewalType[] = [ "daily", "weekly", @@ -28,19 +34,10 @@ export const validBudgetRenewals: BudgetRenewalType[] = [ export const nip47MethodDescriptions: Record = { [NIP_47_GET_BALANCE_METHOD]: "Read your balance", [NIP_47_GET_INFO_METHOD]: "Read your node info", - [NIP_47_PAY_INVOICE_METHOD]: "Send payments", - [NIP_47_MAKE_INVOICE_METHOD]: "Create invoices", - [NIP_47_LOOKUP_INVOICE_METHOD]: "Lookup status of invoices", [NIP_47_LIST_TRANSACTIONS_METHOD]: "Read incoming transaction history", -}; - -export const nip47MethodIcons: Record = { - [NIP_47_GET_BALANCE_METHOD]: "wallet", - [NIP_47_GET_INFO_METHOD]: "wallet", - [NIP_47_PAY_INVOICE_METHOD]: "lightning", - [NIP_47_MAKE_INVOICE_METHOD]: "invoice", - [NIP_47_LOOKUP_INVOICE_METHOD]: "search", - [NIP_47_LIST_TRANSACTIONS_METHOD]: "transactions", + [NIP_47_LOOKUP_INVOICE_METHOD]: "Lookup status of invoices", + [NIP_47_MAKE_INVOICE_METHOD]: "Create invoices", + [NIP_47_PAY_INVOICE_METHOD]: "Send payments", }; export interface User {