From 66ab943ace1a18c55a56efee4d16c432ab4699b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Aaron?= Date: Tue, 9 Jul 2024 20:46:18 +0200 Subject: [PATCH] fix: configurable budget & renewal --- alby/alby_oauth_service.go | 9 +- alby/models.go | 7 +- .../src/components/BudgetAmountSelect.tsx | 32 ++++++ .../src/components/BudgetRenewalSelect.tsx | 38 +++++++ frontend/src/components/Permissions.tsx | 63 ++---------- frontend/src/components/SidebarHint.tsx | 4 +- .../connections/AlbyConnectionCard.tsx | 98 ++++++------------- .../src/components/connections/AppCard.tsx | 43 ++++---- .../connections/AppCardConnectionInfo.tsx | 5 +- frontend/src/hooks/useLinkAccount.ts | 7 +- .../screens/wallet/OnboardingChecklist.tsx | 2 +- frontend/yarn.lock | 6 +- http/alby_http_service.go | 9 +- wails/wails_handlers.go | 12 ++- 14 files changed, 175 insertions(+), 160 deletions(-) create mode 100644 frontend/src/components/BudgetAmountSelect.tsx create mode 100644 frontend/src/components/BudgetRenewalSelect.tsx diff --git a/alby/alby_oauth_service.go b/alby/alby_oauth_service.go index b9534915..488da6a7 100644 --- a/alby/alby_oauth_service.go +++ b/alby/alby_oauth_service.go @@ -21,7 +21,6 @@ import ( "github.com/getAlby/hub/events" "github.com/getAlby/hub/lnclient" "github.com/getAlby/hub/logger" - nip47 "github.com/getAlby/hub/nip47/models" "github.com/getAlby/hub/service/keys" ) @@ -262,7 +261,7 @@ func (svc *albyOAuthService) DrainSharedWallet(ctx context.Context, lnClient lnc amountSat := int64(math.Floor( balanceSat- // Alby shared node balance in sats - (balanceSat*(8/1000))- // Alby service fee (0.8%) + (balanceSat*(8.0/1000.0))- // Alby service fee (0.8%) (balanceSat*0.01))) - // Maximum potential routing fees (1%) 10 // Alby fee reserve (10 sats) @@ -379,7 +378,7 @@ func (svc *albyOAuthService) GetAuthUrl() string { return svc.oauthConf.AuthCodeURL("unused") } -func (svc *albyOAuthService) LinkAccount(ctx context.Context, lnClient lnclient.LNClient) error { +func (svc *albyOAuthService) LinkAccount(ctx context.Context, lnClient lnclient.LNClient, budget uint64, renewal string) error { connectionPubkey, err := svc.createAlbyAccountNWCNode(ctx) if err != nil { logger.Logger.WithError(err).Error("Failed to create alby account nwc node") @@ -389,8 +388,8 @@ func (svc *albyOAuthService) LinkAccount(ctx context.Context, lnClient lnclient. app, _, err := db.NewDBService(svc.db, svc.eventPublisher).CreateApp( "getalby.com", connectionPubkey, - 1_000_000, - nip47.BUDGET_RENEWAL_MONTHLY, + budget, + renewal, nil, lnClient.GetSupportedNIP47Methods(), ) diff --git a/alby/models.go b/alby/models.go index 625f8b4e..ea76217a 100644 --- a/alby/models.go +++ b/alby/models.go @@ -13,7 +13,7 @@ type AlbyOAuthService interface { GetAuthUrl() string GetUserIdentifier() (string, error) IsConnected(ctx context.Context) bool - LinkAccount(ctx context.Context, lnClient lnclient.LNClient) error + LinkAccount(ctx context.Context, lnClient lnclient.LNClient, budget uint64, renewal string) error CallbackHandler(ctx context.Context, code string) error GetBalance(ctx context.Context) (*AlbyBalance, error) GetMe(ctx context.Context) (*AlbyMe, error) @@ -29,6 +29,11 @@ type AlbyPayRequest struct { Invoice string `json:"invoice"` } +type AlbyLinkAccountRequest struct { + Budget int `json:"budget"` + Renewal string `json:"renewal"` +} + type AlbyMe struct { Identifier string `json:"identifier"` NPub string `json:"nostr_pubkey"` diff --git a/frontend/src/components/BudgetAmountSelect.tsx b/frontend/src/components/BudgetAmountSelect.tsx new file mode 100644 index 00000000..4f598175 --- /dev/null +++ b/frontend/src/components/BudgetAmountSelect.tsx @@ -0,0 +1,32 @@ +import { budgetOptions } from "src/types"; + +function BudgetAmountSelect({ + value, + onChange, +}: { + value?: number; + onChange: (value: number) => void; +}) { + return ( +
+ {Object.keys(budgetOptions).map((budget) => { + const amount = budgetOptions[budget]; + return ( +
onChange(amount)} + className={`col-span-2 md:col-span-1 cursor-pointer rounded border-2 ${ + value === amount ? "border-primary" : "border-muted" + } text-center py-4`} + > + {budget} +
+ {amount ? "sats" : "#reckless"} +
+ ); + })} +
+ ); +} + +export default BudgetAmountSelect; diff --git a/frontend/src/components/BudgetRenewalSelect.tsx b/frontend/src/components/BudgetRenewalSelect.tsx new file mode 100644 index 00000000..b058fd38 --- /dev/null +++ b/frontend/src/components/BudgetRenewalSelect.tsx @@ -0,0 +1,38 @@ +import React from "react"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "src/components/ui/select"; +import { BudgetRenewalType, validBudgetRenewals } from "src/types"; + +interface BudgetRenewalProps { + value: BudgetRenewalType; + onChange: (value: BudgetRenewalType) => void; + disabled?: boolean; +} + +const BudgetRenewalSelect: React.FC = ({ + value, + onChange, + disabled, +}) => { + return ( + + ); +}; + +export default BudgetRenewalSelect; diff --git a/frontend/src/components/Permissions.tsx b/frontend/src/components/Permissions.tsx index d061ed62..2dbbf905 100644 --- a/frontend/src/components/Permissions.tsx +++ b/frontend/src/components/Permissions.tsx @@ -1,26 +1,19 @@ import { PlusCircle } from "lucide-react"; import React, { useEffect, useState } from "react"; +import BudgetAmountSelect from "src/components/BudgetAmountSelect"; +import BudgetRenewalSelect from "src/components/BudgetRenewalSelect"; import { Button } from "src/components/ui/button"; import { Checkbox } from "src/components/ui/checkbox"; import { Label } from "src/components/ui/label"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "src/components/ui/select"; import { useCapabilities } from "src/hooks/useCapabilities"; import { cn } from "src/lib/utils"; import { AppPermissions, BudgetRenewalType, Scope, - budgetOptions, expiryOptions, iconMap, scopeDescriptions, - validBudgetRenewals, } from "src/types"; interface PermissionsProps { @@ -164,55 +157,17 @@ const Permissions: React.FC = ({ {!canEditPermissions ? ( permissions.budgetRenewal ) : ( - + /> )} -
- {Object.keys(budgetOptions).map((budget) => { - return ( - // replace with something else and then remove dark prefixes -
- handleMaxAmountChange(budgetOptions[budget]) - } - className={`col-span-2 md:col-span-1 cursor-pointer rounded border-2 ${ - permissions.maxAmount == budgetOptions[budget] - ? "border-primary" - : "border-muted" - } text-center py-4 dark:text-white`} - > - {budget} -
- {budgetOptions[budget] ? "sats" : "#reckless"} -
- ); - })} -
+ ) : isNewConnection ? ( <> diff --git a/frontend/src/components/SidebarHint.tsx b/frontend/src/components/SidebarHint.tsx index 0ec49db5..67bf68f7 100644 --- a/frontend/src/components/SidebarHint.tsx +++ b/frontend/src/components/SidebarHint.tsx @@ -87,9 +87,9 @@ function SidebarHint() { return ( ); diff --git a/frontend/src/components/connections/AlbyConnectionCard.tsx b/frontend/src/components/connections/AlbyConnectionCard.tsx index c8882402..ab3e39a8 100644 --- a/frontend/src/components/connections/AlbyConnectionCard.tsx +++ b/frontend/src/components/connections/AlbyConnectionCard.tsx @@ -6,9 +6,11 @@ import { Link2Icon, ZapIcon, } from "lucide-react"; -import albyButton from "public/images/illustrations/login-with-alby.png"; import { useState } from "react"; import { Link } from "react-router-dom"; + +import BudgetAmountSelect from "src/components/BudgetAmountSelect"; +import BudgetRenewalSelect from "src/components/BudgetRenewalSelect"; import ExternalLink from "src/components/ExternalLink"; import Loading from "src/components/Loading"; import UserAvatar from "src/components/UserAvatar"; @@ -32,23 +34,20 @@ import { } from "src/components/ui/dialog"; import { Label } from "src/components/ui/label"; import { LoadingButton } from "src/components/ui/loading-button"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "src/components/ui/select"; import { Separator } from "src/components/ui/separator"; import { useAlbyMe } from "src/hooks/useAlbyMe"; import { LinkStatus, useLinkAccount } from "src/hooks/useLinkAccount"; -import { App, budgetOptions, validBudgetRenewals } from "src/types"; +import { App, BudgetRenewalType } from "src/types"; +import albyButton from "/images/illustrations/login-with-alby.png"; function AlbyConnectionCard({ connection }: { connection?: App }) { const { data: albyMe } = useAlbyMe(); const { loading, linkStatus, loadingLinkStatus, linkAccount } = useLinkAccount(); - const [maxAmount, setMaxAmount] = useState(); + + const [maxAmount, setMaxAmount] = useState(1_000_000); + const [budgetRenewal, setBudgetRenewal] = + useState("monthly"); return ( @@ -91,68 +90,29 @@ function AlbyConnectionCard({ connection }: { connection?: App }) { Link to Alby Account -

- After you link your account, every app you access - through your Alby Account will handle payments via the - Hub. -

+ After you link your account, every app you access through + your Alby Account will handle payments via the Hub. -

- You can add a budget that will restrict how much can be - spent from the Hub with your Alby Account. -

-
- - -
-
- {Object.keys(budgetOptions).map((budget) => { - return ( - // replace with something else and then remove dark prefixes -
- setMaxAmount(budgetOptions[budget]) - } - className={`col-span-2 md:col-span-1 cursor-pointer rounded border-2 ${ - maxAmount == budgetOptions[budget] - ? "border-primary" - : "border-muted" - } text-center py-4`} - > - {budget} -
- {budgetOptions[budget] ? "sats" : "#reckless"} -
- ); - })} -
+ You can add a budget that will restrict how much can be + spent from the Hub with your Alby Account.
+
+ + +
+ - -
Link to Alby Account
+ linkAccount(maxAmount, budgetRenewal)} + loading={loading} + > + Link to Alby Account
diff --git a/frontend/src/components/connections/AppCard.tsx b/frontend/src/components/connections/AppCard.tsx index 3d817c23..edb3864c 100644 --- a/frontend/src/components/connections/AppCard.tsx +++ b/frontend/src/components/connections/AppCard.tsx @@ -1,6 +1,6 @@ import dayjs from "dayjs"; import relativeTime from "dayjs/plugin/relativeTime"; -import { Link } from "react-router-dom"; +import { useNavigate } from "react-router-dom"; import AppAvatar from "src/components/AppAvatar"; import { AppCardConnectionInfo } from "src/components/connections/AppCardConnectionInfo"; import { AppCardNotice } from "src/components/connections/AppCardNotice"; @@ -20,26 +20,27 @@ type Props = { }; export default function AppCard({ app }: Props) { + const navigate = useNavigate(); + return ( - <> - - - - - -
- -
- {app.name} -
-
-
-
- - - -
- - + navigate(`/apps/${app.nostrPubkey}`)} + > + + + +
+ +
+ {app.name} +
+
+
+
+ + + +
); } diff --git a/frontend/src/components/connections/AppCardConnectionInfo.tsx b/frontend/src/components/connections/AppCardConnectionInfo.tsx index ba2d50df..8e3d6ee7 100644 --- a/frontend/src/components/connections/AppCardConnectionInfo.tsx +++ b/frontend/src/components/connections/AppCardConnectionInfo.tsx @@ -93,7 +93,10 @@ export function AppCardConnectionInfo({ <>Last used: {dayjs(connection.lastEventAt).fromNow()} )} - + e.stopPropagation()} + >