Skip to content

Commit

Permalink
fix: configurable budget & renewal
Browse files Browse the repository at this point in the history
  • Loading branch information
reneaaron committed Jul 10, 2024
1 parent 1da2858 commit 66ab943
Show file tree
Hide file tree
Showing 14 changed files with 175 additions and 160 deletions.
9 changes: 4 additions & 5 deletions alby/alby_oauth_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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")
Expand All @@ -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(),
)
Expand Down
7 changes: 6 additions & 1 deletion alby/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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"`
Expand Down
32 changes: 32 additions & 0 deletions frontend/src/components/BudgetAmountSelect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { budgetOptions } from "src/types";

function BudgetAmountSelect({
value,
onChange,
}: {
value?: number;
onChange: (value: number) => void;
}) {
return (
<div className="grid grid-cols-6 grid-rows-2 md:grid-rows-1 md:grid-cols-6 gap-2 text-xs">
{Object.keys(budgetOptions).map((budget) => {
const amount = budgetOptions[budget];
return (
<div
key={budget}
onClick={() => 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}
<br />
{amount ? "sats" : "#reckless"}
</div>
);
})}
</div>
);
}

export default BudgetAmountSelect;
38 changes: 38 additions & 0 deletions frontend/src/components/BudgetRenewalSelect.tsx
Original file line number Diff line number Diff line change
@@ -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<BudgetRenewalProps> = ({
value,
onChange,
disabled,
}) => {
return (
<Select value={value} onValueChange={onChange} disabled={disabled}>
<SelectTrigger className="w-[150px]">
<SelectValue placeholder={"placeholder"} />
</SelectTrigger>
<SelectContent>
{validBudgetRenewals.map((renewalOption) => (
<SelectItem key={renewalOption} value={renewalOption}>
{renewalOption.charAt(0).toUpperCase() + renewalOption.slice(1)}
</SelectItem>
))}
</SelectContent>
</Select>
);
};

export default BudgetRenewalSelect;
63 changes: 9 additions & 54 deletions frontend/src/components/Permissions.tsx
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -164,55 +157,17 @@ const Permissions: React.FC<PermissionsProps> = ({
{!canEditPermissions ? (
permissions.budgetRenewal
) : (
<Select
<BudgetRenewalSelect
value={permissions.budgetRenewal}
onValueChange={handleBudgetRenewalChange}
onChange={handleBudgetRenewalChange}
disabled={!canEditPermissions}
>
<SelectTrigger className="w-[150px]">
<SelectValue
placeholder={permissions.budgetRenewal}
/>
</SelectTrigger>
<SelectContent>
{validBudgetRenewals.map((renewalOption) => (
<SelectItem
key={renewalOption}
value={renewalOption}
>
{renewalOption.charAt(0).toUpperCase() +
renewalOption.slice(1)}
</SelectItem>
))}
</SelectContent>
</Select>
/>
)}
</div>
<div
id="budget-allowance-limits"
className="grid grid-cols-6 grid-rows-2 md:grid-rows-1 md:grid-cols-6 gap-2 text-xs"
>
{Object.keys(budgetOptions).map((budget) => {
return (
// replace with something else and then remove dark prefixes
<div
key={budget}
onClick={() =>
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}
<br />
{budgetOptions[budget] ? "sats" : "#reckless"}
</div>
);
})}
</div>
<BudgetAmountSelect
value={permissions.maxAmount}
onChange={handleMaxAmountChange}
/>
</>
) : isNewConnection ? (
<>
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/SidebarHint.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,9 @@ function SidebarHint() {
return (
<SidebarHintCard
icon={Link2}
title="Link your Hub"
title="Link to your Alby Account"
description="Finish the setup by linking this Hub to your Alby Account."
buttonText="Link Hub"
buttonText="Link now"
buttonLink="/apps"
/>
);
Expand Down
98 changes: 29 additions & 69 deletions frontend/src/components/connections/AlbyConnectionCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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<BudgetRenewalType>("monthly");

return (
<Card>
Expand Down Expand Up @@ -91,68 +90,29 @@ function AlbyConnectionCard({ connection }: { connection?: App }) {
<DialogContent>
<DialogHeader>Link to Alby Account</DialogHeader>
<DialogDescription className="flex flex-col gap-4">
<p>
After you link your account, every app you access
through your Alby Account will handle payments via the
Hub.
</p>
After you link your account, every app you access through
your Alby Account will handle payments via the Hub.
<img src={albyButton} className="w-56 mx-auto" />
<p>
You can add a budget that will restrict how much can be
spent from the Hub with your Alby Account.
</p>
<div className="grid gap-1.5">
<Label>Budget renewal</Label>
<Select
value={"monthly"}
/*onValueChange={handleBudgetRenewalChange}
disabled={!canEditPermissions}*/
>
<SelectTrigger className="w-[150px]">
<SelectValue placeholder={"monthly"} />
</SelectTrigger>
<SelectContent>
{validBudgetRenewals.map((renewalOption) => (
<SelectItem
key={renewalOption}
value={renewalOption}
>
{renewalOption.charAt(0).toUpperCase() +
renewalOption.slice(1)}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div
id="budget-allowance-limits"
className="grid grid-cols-6 grid-rows-2 md:grid-rows-1 md:grid-cols-6 gap-2 text-xs"
>
{Object.keys(budgetOptions).map((budget) => {
return (
// replace with something else and then remove dark prefixes
<div
key={budget}
onClick={() =>
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}
<br />
{budgetOptions[budget] ? "sats" : "#reckless"}
</div>
);
})}
</div>
You can add a budget that will restrict how much can be
spent from the Hub with your Alby Account.
</DialogDescription>
<div className="grid gap-1.5">
<Label>Budget renewal</Label>
<BudgetRenewalSelect
value={budgetRenewal}
onChange={setBudgetRenewal}
/>
</div>
<BudgetAmountSelect
value={maxAmount}
onChange={setMaxAmount}
/>
<DialogFooter>
<LoadingButton onClick={linkAccount} loading={loading}>
<div>Link to Alby Account</div>
<LoadingButton
onClick={() => linkAccount(maxAmount, budgetRenewal)}
loading={loading}
>
Link to Alby Account
</LoadingButton>
</DialogFooter>
</DialogContent>
Expand Down
Loading

0 comments on commit 66ab943

Please sign in to comment.