Skip to content

Commit

Permalink
feat: add buzzpay internal app (#580)
Browse files Browse the repository at this point in the history
* feat: add uncle jim internal app

* fix: wrong number in instructions

* chore: simplify alby jim internal app copy

* Feat: app metadata (#570)

* chore: wg.Add() before go (#559)

* chore: code cleanup (#558)

* Update README.md

Add command to make the install script executable in the instructions.

* feat: add migration card in wallet screen (#555)

* feat: add migration card in wallet screen

* chore: transfer funds if channel exists

* chore: typo

* chore: improve copy

* chore: add transfer funds button

* fix: remove duplicate icon

---------

Co-authored-by: Roland Bewick <roland.bewick@gmail.com>

* chore: run extra workflows on PR (#563)

* chore: run extra workflows on PR

* fix: pull request workflows

* build(deps): bump github.com/nbd-wtf/go-nostr from 0.34.5 to 0.34.10 (#551)

Bumps [github.com/nbd-wtf/go-nostr](https://github.com/nbd-wtf/go-nostr) from 0.34.5 to 0.34.10.
- [Commits](nbd-wtf/go-nostr@v0.34.5...v0.34.10)

---
updated-dependencies:
- dependency-name: github.com/nbd-wtf/go-nostr
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps): bump gopkg.in/DataDog/dd-trace-go.v1 from 1.66.0 to 1.67.0 (#552)

Bumps gopkg.in/DataDog/dd-trace-go.v1 from 1.66.0 to 1.67.0.

---
updated-dependencies:
- dependency-name: gopkg.in/DataDog/dd-trace-go.v1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* fix: do not render 0 on withdraw onchain funds page when reserve balance is 0 (#549)

* chore: update fly default kill timeout to ensure app gracefully shuts down (#547)

* feat: app metadata

* fix(wails): parse max length in log output endpoint (#568)

* fix: alby account image and name on transaction list

* fix: vertically center transaction item content

* fix: use slashed zero on wallet balance

* chore: align app image with app name in transaction list

* fix: ensure auth with correct user if refresh token expires (#572)

* fix: do not allow editing name of Alby Account connection

* fix: change "Create Wallet" to "Create Subaccount"

* fix: subaccount copy

* chore: update friends and family app icon

* fix: update app store images (#574)

* fix: update app store images

* chore: update paper scissors hodl and zapplanner icons

* chore: update lume and habla news icons

* chore: resize and compress zapplanner and rps logos

---------

Co-authored-by: Roland Bewick <roland.bewick@gmail.com>

* feat: add new auto channel flow that works with existing channels (#556)

* feat: add new auto channel flow that works with existing channels

* chore: remove "first" from auto channel copy

* feat: new transaction list item design

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Matjaž Lipuš <matjazl@gmail.com>
Co-authored-by: BtcPins <pardus79@gmail.com>
Co-authored-by: Michael Bumann <hello@michaelbumann.com>
Co-authored-by: Adithya Vardhan <imadithyavardhan@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* feat: add buzzpay internal app

* chore: remove connection secret from metadata

* fix: make buzzpay isolated

* chore: update buzzpay app UI and instructions

* chore: extract app creation into shared method

* fix: isolated app permission display

* chore: update copy

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Matjaž Lipuš <matjazl@gmail.com>
Co-authored-by: BtcPins <pardus79@gmail.com>
Co-authored-by: Michael Bumann <hello@michaelbumann.com>
Co-authored-by: Adithya Vardhan <imadithyavardhan@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
  • Loading branch information
6 people authored Sep 3, 2024
1 parent 7032bba commit 3266e64
Show file tree
Hide file tree
Showing 13 changed files with 446 additions and 90 deletions.
15 changes: 15 additions & 0 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"time"

"github.com/sirupsen/logrus"
"gorm.io/datatypes"
"gorm.io/gorm"

"github.com/getAlby/hub/alby"
Expand Down Expand Up @@ -146,6 +147,20 @@ func (api *api) UpdateApp(userApp *db.App, updateAppRequest *UpdateAppRequest) e
}
}

if updateAppRequest.Metadata != nil {
var metadataBytes []byte
var err error
metadataBytes, err = json.Marshal(updateAppRequest.Metadata)
if err != nil {
logger.Logger.WithError(err).Error("Failed to serialize metadata")
return err
}
err = tx.Model(&db.App{}).Where("id", userApp.ID).Update("metadata", datatypes.JSON(metadataBytes)).Error
if err != nil {
return err
}
}

// Update existing permissions with new budget and expiry
err := tx.Model(&db.AppPermission{}).Where("app_id", userApp.ID).Updates(map[string]interface{}{
"ExpiresAt": expiresAt,
Expand Down
1 change: 1 addition & 0 deletions api/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ type UpdateAppRequest struct {
BudgetRenewal string `json:"budgetRenewal"`
ExpiresAt string `json:"expiresAt"`
Scopes []string `json:"scopes"`
Metadata Metadata `json:"metadata,omitempty"`
}

type CreateAppRequest struct {
Expand Down
Binary file added frontend/src/assets/suggested-apps/buzzpay.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 8 additions & 7 deletions frontend/src/components/Permissions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,14 @@ const Permissions: React.FC<PermissionsProps> = ({

return (
<div className="max-w-lg">
{permissions.isolated && (
<p className="mb-4">
This app is isolated from the rest of your wallet. This means it will
have an isolated balance and only has access to its own transaction
history. It will not be able to sign messages on your node's behalf.
</p>
)}

{!readOnly && !scopesReadOnly ? (
<Scopes
capabilities={capabilities}
Expand All @@ -93,13 +101,6 @@ const Permissions: React.FC<PermissionsProps> = ({
onScopesChanged={onScopesChanged}
isNewConnection={isNewConnection}
/>
) : permissions.isolated ? (
<p>
This app will be isolated from the rest of your wallet. This means it
will have an isolated balance and only has access to its own
transaction history. It will not be able to read your node info,
transactions, or sign messages.
</p>
) : (
<>
<p className="text-sm font-medium mb-2">Scopes</p>
Expand Down
8 changes: 8 additions & 0 deletions frontend/src/components/SuggestedAppData.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import alby from "src/assets/suggested-apps/alby.png";
import amethyst from "src/assets/suggested-apps/amethyst.png";
import buzzpay from "src/assets/suggested-apps/buzzpay.png";
import damus from "src/assets/suggested-apps/damus.png";
import hablanews from "src/assets/suggested-apps/habla-news.png";
import kiwi from "src/assets/suggested-apps/kiwi.png";
Expand Down Expand Up @@ -38,6 +39,13 @@ export const suggestedApps: SuggestedApp[] = [
internal: true,
logo: uncleJim,
},
{
id: "buzzpay",
title: "BuzzPay PoS",
description: "Receive-only PoS you can safely share with your employees",
internal: true,
logo: buzzpay,
},
{
id: "alby-extension",
title: "Alby Extension",
Expand Down
19 changes: 19 additions & 0 deletions frontend/src/requests/createApp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { CreateAppRequest, CreateAppResponse } from "src/types";
import { request } from "src/utils/request";

export async function createApp(
createAppRequest: CreateAppRequest
): Promise<CreateAppResponse> {
const createAppResponse = await request<CreateAppResponse>("/api/apps", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(createAppRequest),
});

if (!createAppResponse) {
throw new Error("no create app response received");
}
return createAppResponse;
}
9 changes: 7 additions & 2 deletions frontend/src/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ import { OpeningAutoChannel } from "src/screens/channels/auto/OpeningAutoChannel
import { FirstChannel } from "src/screens/channels/first/FirstChannel";
import { OpenedFirstChannel } from "src/screens/channels/first/OpenedFirstChannel";
import { OpeningFirstChannel } from "src/screens/channels/first/OpeningFirstChannel";
import { UncleJimApp } from "src/screens/internal-apps/UncleJimApp";
import { BuzzPay } from "src/screens/internal-apps/BuzzPay";
import { UncleJim } from "src/screens/internal-apps/UncleJim";
import { Success } from "src/screens/onboarding/Success";
import BuyBitcoin from "src/screens/onchain/BuyBitcoin";
import DepositBitcoin from "src/screens/onchain/DepositBitcoin";
Expand Down Expand Up @@ -210,7 +211,11 @@ const routes = [
children: [
{
path: "uncle-jim",
element: <UncleJimApp />,
element: <UncleJim />,
},
{
path: "buzzpay",
element: <BuzzPay />,
},
],
},
Expand Down
15 changes: 2 additions & 13 deletions frontend/src/screens/apps/NewApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
AppPermissions,
BudgetRenewalType,
CreateAppRequest,
CreateAppResponse,
Nip47NotificationType,
Nip47RequestMethod,
Scope,
Expand All @@ -23,8 +22,8 @@ import { Separator } from "src/components/ui/separator";
import { useToast } from "src/components/ui/use-toast";
import { useApps } from "src/hooks/useApps";
import { useCapabilities } from "src/hooks/useCapabilities";
import { createApp } from "src/requests/createApp";
import { handleRequestError } from "src/utils/handleRequestError";
import { request } from "src/utils/request"; // build the project for this to appear
import Permissions from "../../components/Permissions";
import { suggestedApps } from "../../components/SuggestedAppData";

Expand Down Expand Up @@ -204,17 +203,7 @@ const NewAppInternal = ({ capabilities }: NewAppInternalProps) => {
},
};

const createAppResponse = await request<CreateAppResponse>("/api/apps", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(createAppRequest),
});

if (!createAppResponse) {
throw new Error("no create app response received");
}
const createAppResponse = await createApp(createAppRequest);

if (createAppResponse.returnTo) {
// open connection URI directly in an app
Expand Down
101 changes: 49 additions & 52 deletions frontend/src/screens/apps/ShowApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { useDeleteApp } from "src/hooks/useDeleteApp";
import {
App,
AppPermissions,
BudgetRenewalType,
UpdateAppRequest,
WalletCapabilities,
} from "src/types";
Expand Down Expand Up @@ -95,7 +94,7 @@ function AppInternal({ app, refetchApp, capabilities }: AppInternalProps) {
const [permissions, setPermissions] = React.useState<AppPermissions>({
scopes: app.scopes,
maxAmount: app.maxAmount,
budgetRenewal: app.budgetRenewal as BudgetRenewalType,
budgetRenewal: app.budgetRenewal,
expiresAt: app.expiresAt ? new Date(app.expiresAt) : undefined,
isolated: app.isolated,
});
Expand Down Expand Up @@ -259,59 +258,57 @@ function AppInternal({ app, refetchApp, capabilities }: AppInternalProps) {
</CardContent>
</Card>

{!app.isolated && (
<Card>
<CardHeader>
<CardTitle>
<div className="flex flex-row justify-between items-center">
Permissions
<div className="flex flex-row gap-2">
{isEditingPermissions && (
<div className="flex justify-center items-center gap-2">
<Button
type="button"
variant="outline"
onClick={() => {
window.location.reload();
}}
>
Cancel
</Button>
<Card>
<CardHeader>
<CardTitle>
<div className="flex flex-row justify-between items-center">
Permissions
<div className="flex flex-row gap-2">
{isEditingPermissions && (
<div className="flex justify-center items-center gap-2">
<Button
type="button"
variant="outline"
onClick={() => {
window.location.reload();
}}
>
Cancel
</Button>

<Button type="button" onClick={handleSave}>
Save
</Button>
</div>
)}
<Button type="button" onClick={handleSave}>
Save
</Button>
</div>
)}

{!isEditingPermissions && (
<>
<Button
variant="outline"
onClick={() =>
setIsEditingPermissions(!isEditingPermissions)
}
>
Edit
</Button>
</>
)}
</div>
{!app.isolated && !isEditingPermissions && (
<>
<Button
variant="outline"
onClick={() =>
setIsEditingPermissions(!isEditingPermissions)
}
>
Edit
</Button>
</>
)}
</div>
</CardTitle>
</CardHeader>
<CardContent>
<Permissions
capabilities={capabilities}
permissions={permissions}
setPermissions={setPermissions}
readOnly={!isEditingPermissions}
isNewConnection={false}
budgetUsage={app.budgetUsage}
/>
</CardContent>
</Card>
)}
</div>
</CardTitle>
</CardHeader>
<CardContent>
<Permissions
capabilities={capabilities}
permissions={permissions}
setPermissions={setPermissions}
readOnly={!isEditingPermissions}
isNewConnection={false}
budgetUsage={app.budgetUsage}
/>
</CardContent>
</Card>
</div>
</div>
</>
Expand Down
88 changes: 88 additions & 0 deletions frontend/src/screens/internal-apps/BuzzPay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import React from "react";
import AppHeader from "src/components/AppHeader";
import AppCard from "src/components/connections/AppCard";
import Loading from "src/components/Loading";
import { ExternalLinkButton } from "src/components/ui/button";
import { LoadingButton } from "src/components/ui/loading-button";
import { useToast } from "src/components/ui/use-toast";
import { useApps } from "src/hooks/useApps";
import { createApp } from "src/requests/createApp";
import { handleRequestError } from "src/utils/handleRequestError";

export function BuzzPay() {
const { data: apps, mutate: reloadApps } = useApps();
const [creatingApp, setCreatingApp] = React.useState(false);
const [connectionSecret, setConnectionSecret] = React.useState("");
const { toast } = useToast();

if (!apps) {
return <Loading />;
}
const app = apps.find((app) => app.metadata?.app_store_app_id === "buzzpay");

function handleCreateApp() {
setCreatingApp(true);
(async () => {
try {
const name = "BuzzPay";
if (apps?.some((existingApp) => existingApp.name === name)) {
throw new Error("A connection with the same name already exists.");
}

const createAppResponse = await createApp({
name,
scopes: ["get_info", "lookup_invoice", "make_invoice"],
isolated: true,
metadata: {
app_store_app_id: "buzzpay",
},
});

setConnectionSecret(createAppResponse.pairingUri);

await reloadApps();

toast({ title: "BuzzPay app created" });
} catch (error) {
handleRequestError(toast, "Failed to create app", error);
}
setCreatingApp(false);
})();
}

return (
<div className="grid gap-5">
<AppHeader
title="BuzzPay"
description="Receive-only PoS you can safely share with your employees"
/>
{app && (
<div className="max-w-lg flex flex-col gap-5">
<p className="text-muted-foreground">
Simply click the button below to access your PoS which you can
instantly receive payments, manage your items, and share your PoS
with your employees.
</p>
<ExternalLinkButton
to={`https://pos.albylabs.com${connectionSecret && `/#/wallet/${encodeURIComponent(connectionSecret)}/new`}`}
>
Go to BuzzPay PoS
</ExternalLinkButton>
<AppCard app={app} />
</div>
)}
{!app && (
<div className="max-w-lg flex flex-col gap-5">
<p className="text-muted-foreground">
By creating a new buzzpay app, a read-only wallet connection will be
created and you will receive a link to a PoS you can share with your
employees, on any device.
</p>
<LoadingButton loading={creatingApp} onClick={handleCreateApp}>
Create BuzzPay App
</LoadingButton>
</div>
)}
</div>
);
}
Loading

0 comments on commit 3266e64

Please sign in to comment.