feat: waitlist for bank accounts (#1354)

* feat: waitlist for virtual bank accounts

* fix: copy

* fix: apis

* fix: rename event

* feat: success state

* fix: copy

* fix: copy

* Update frontend/src/screens/features/BankAccount.tsx
This commit is contained in:
René Aaron 2025-06-04 15:11:33 +02:00 committed by GitHub
parent 3528017a24
commit 4ab91202b7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 125 additions and 0 deletions

View file

@ -1468,5 +1468,7 @@ func getEventWhitelist() []string {
"nwc_node_stop_failed",
"nwc_node_stopped",
"nwc_alby_account_connected",
"interest_virtual_bankaccount",
}
}

View file

@ -1296,6 +1296,12 @@ func (api *api) ExecuteCustomNodeCommand(ctx context.Context, command string) (i
return nodeResp.Response, nil
}
func (api *api) SendEvent(event string) {
api.svc.GetEventPublisher().Publish(&events.Event{
Event: event,
})
}
func (api *api) parseExpiresAt(expiresAtString string) (*time.Time, error) {
var expiresAt *time.Time
if expiresAtString != "" {

View file

@ -64,6 +64,7 @@ type API interface {
EnableAutoSwaps(ctx context.Context, autoSwapsRequest *EnableAutoSwapsRequest) error
GetCustomNodeCommands() (*CustomNodeCommandsResponse, error)
ExecuteCustomNodeCommand(ctx context.Context, command string) (interface{}, error)
SendEvent(event string)
}
type App struct {
@ -146,6 +147,10 @@ type BackupReminderRequest struct {
NextBackupReminder string `json:"nextBackupReminder"`
}
type SendEventRequest struct {
Event string `json:"event"`
}
type SetupRequest struct {
LNBackendType string `json:"backendType"`
UnlockPassword string `json:"unlockPassword"`

View file

@ -3,6 +3,7 @@ import {
ChevronsUpDown,
CircleHelp,
Cloud,
CreditCardIcon,
HomeIcon,
LayoutGridIcon,
LogOut,
@ -98,6 +99,11 @@ export function AppSidebar() {
url: "/apps",
icon: Plug2Icon,
},
{
title: "Bank Account",
url: "/bank-account",
icon: CreditCardIcon,
},
],
navSecondary: [
...(hasChannelManagement

View file

@ -4,6 +4,7 @@ export const localStorageKeys = {
channelOrder: "channelOrder",
authToken: "authToken",
supportAlbySidebarHintHiddenUntil: "supportAlbySidebarHintHiddenUntil",
interestVirtualBankAccount: "interestVirtualBankAccount",
};
export const ONCHAIN_DUST_SATS = 1000;

View file

@ -36,6 +36,7 @@ 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 BankAccount from "src/screens/features/BankAccount";
import { AlbyGo } from "src/screens/internal-apps/AlbyGo";
import { Bitrefill } from "src/screens/internal-apps/Bitrefill";
import { BuzzPay } from "src/screens/internal-apps/BuzzPay";
@ -410,6 +411,10 @@ const routes = [
path: "support-alby",
element: <SupportAlby />,
},
{
path: "bank-account",
element: <BankAccount />,
},
],
},
{

View file

@ -0,0 +1,67 @@
import { CheckCircle, CreditCardIcon } from "lucide-react";
import { useState } from "react";
import AppHeader from "src/components/AppHeader";
import { Button } from "src/components/ui/button";
import { localStorageKeys } from "src/constants";
import { request } from "src/utils/request";
function BankAccount() {
const [activated, setActivated] = useState(
!!localStorage.getItem(localStorageKeys.interestVirtualBankAccount)
);
async function activate() {
await request(`/api/event`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
event: "interest_virtual_bankaccount",
}),
});
localStorage.setItem(localStorageKeys.interestVirtualBankAccount, "true");
setActivated(true);
}
return (
<>
<AppHeader
title="Bank Account"
description="Receive money from your bank account, pay bills and send payments to bank accounts. Virtual credit cards are coming soon!"
/>
<div className="flex flex-1 items-center justify-center rounded-lg border border-dashed shadow-sm p-8">
<div className="flex flex-col items-center gap-1 text-center max-w-sm">
{!activated ? (
<>
<CreditCardIcon className="w-10 h-10 text-muted-foreground" />
<h3 className="mt-4 text-lg font-semibold">
Activate Your Virtual Bank Account
</h3>
<p className="text-sm text-muted-foreground">
Fund your Hub with fiat, settle traditional payments, and move
money to Lightning in seconds. Virtual credit card coming soon!
</p>
<Button onClick={activate} className="mt-4">
Request Early Access
</Button>
</>
) : (
<>
<CheckCircle className="w-10 h-10 text-muted-foreground" />
<h3 className="mt-4 text-lg font-semibold">
Thanks for your interest!
</h3>
<p className="text-sm text-muted-foreground">
We'll let you know as soon as this feature is available.
</p>
</>
)}
</div>
</div>
</>
);
}
export default BankAccount;

View file

@ -89,6 +89,8 @@ func (httpSvc *HttpService) RegisterSharedRoutes(e *echo.Echo) {
e.Use(middleware.Recover())
e.Use(middleware.RequestID())
e.POST("/api/event", httpSvc.eventHandler)
e.GET("/api/info", httpSvc.infoHandler)
e.POST("/api/setup", httpSvc.setupHandler)
e.POST("/api/restore", httpSvc.restoreBackupHandler)
@ -191,6 +193,19 @@ func (httpSvc *HttpService) infoHandler(c echo.Context) error {
return c.JSON(http.StatusOK, responseBody)
}
func (httpSvc *HttpService) eventHandler(c echo.Context) error {
var sendEventRequest api.SendEventRequest
if err := c.Bind(&sendEventRequest); err != nil {
return c.JSON(http.StatusBadRequest, ErrorResponse{
Message: fmt.Sprintf("Bad request: %s", err.Error()),
})
}
httpSvc.api.SendEvent(sendEventRequest.Event)
return c.NoContent(http.StatusOK)
}
func (httpSvc *HttpService) mnemonicHandler(c echo.Context) error {
var mnemonicRequest api.MnemonicRequest
if err := c.Bind(&mnemonicRequest); err != nil {

View file

@ -1021,6 +1021,24 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
}).WithError(err).Error("Failed to disable swaps")
return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
}
return WailsRequestRouterResponse{Body: nil, Error: ""}
}
case "/api/event":
switch method {
case "POST":
sendEventRequest := &api.SendEventRequest{}
err := json.Unmarshal([]byte(body), sendEventRequest)
if err != nil {
logger.Logger.WithFields(logrus.Fields{
"route": route,
"method": method,
"body": body,
}).WithError(err).Error("Failed to send event")
return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
}
app.api.SendEvent(sendEventRequest.Event)
return WailsRequestRouterResponse{Body: nil, Error: ""}
}
}