fix: delete lightning address when deleting a sub-wallet (#1858)

* fix: delete lightning address when deleting a sub-wallet

* fix: pass app to useDeleteApp hook

* fix: move deletion to server

* fix: merge changes

* Update frontend/src/components/connections/DisconnectApp.tsx

Co-authored-by: Adithya Vardhan <imadithyavardhan@gmail.com>

* fix: create helper function

---------

Co-authored-by: Roland Bewick <roland.bewick@gmail.com>
Co-authored-by: Adithya Vardhan <imadithyavardhan@gmail.com>
This commit is contained in:
René Aaron 2025-11-06 12:11:12 +01:00 committed by GitHub
parent 377a3169c6
commit 0c7cf4fcf6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 88 additions and 36 deletions

View file

@ -320,6 +320,16 @@ func (api *api) UpdateApp(userApp *db.App, updateAppRequest *UpdateAppRequest) e
}
func (api *api) DeleteApp(userApp *db.App) error {
// Delete lightning address if one exists
if api.appsSvc.HasLightningAddress(userApp) {
err := api.DeleteLightningAddress(context.Background(), userApp.ID)
if err != nil {
logger.Logger.WithError(err).WithFields(logrus.Fields{
"app_id": userApp.ID,
}).Error("Failed to delete lightning address during app deletion")
}
}
return api.appsSvc.DeleteApp(userApp)
}

View file

@ -26,6 +26,7 @@ type AppsService interface {
GetAppByPubkey(pubkey string) *db.App
GetAppById(id uint) *db.App
SetAppMetadata(appId uint, metadata map[string]interface{}) error
HasLightningAddress(app *db.App) bool
}
type appsService struct {
@ -232,3 +233,18 @@ func (svc *appsService) SetAppMetadata(id uint, metadata map[string]interface{})
return nil
}
func (svc *appsService) HasLightningAddress(app *db.App) bool {
if app.Metadata == nil {
return false
}
var metadata map[string]interface{}
err := json.Unmarshal(app.Metadata, &metadata)
if err != nil {
return false
}
lud16, exists := metadata["lud16"]
return exists && lud16 != nil
}

View file

@ -25,7 +25,7 @@ export function DisconnectApp({
}) {
const navigate = useNavigate();
const { deleteApp, isDeleting } = useDeleteApp(() => {
const { deleteApp, isDeleting } = useDeleteApp(app, () => {
navigate(
app.metadata?.app_store_app_id !== SUBWALLET_APPSTORE_APP_ID
? "/apps?tab=connected-apps"
@ -33,6 +33,11 @@ export function DisconnectApp({
);
});
// Check if this is a sub-wallet with a lightning address
const isSubwallet =
app.metadata?.app_store_app_id === SUBWALLET_APPSTORE_APP_ID;
const hasLightningAddress = !!app.metadata?.lud16;
return (
<AlertDialog open>
<AlertDialogTrigger asChild>
@ -54,14 +59,17 @@ export function DisconnectApp({
remain in your wallet.
</>
)}
{isSubwallet && hasLightningAddress && (
<p className="font-medium mt-4">
This sub-wallet has a lightning address ({app.metadata?.lud16})
that will also be deleted.
</p>
)}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel onClick={onClose}>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={() => deleteApp(app.appPubkey)}
disabled={isDeleting}
>
<AlertDialogAction onClick={deleteApp} disabled={isDeleting}>
Confirm
</AlertDialogAction>
</AlertDialogFooter>

View file

@ -1,34 +1,35 @@
import React from "react";
import { toast } from "sonner";
import { App } from "src/types";
import { handleRequestError } from "src/utils/handleRequestError";
import { request } from "src/utils/request";
export function useDeleteApp(onSuccess?: (appPubkey: string) => void) {
export function useDeleteApp(app: App, onSuccess?: () => void) {
const [isDeleting, setDeleting] = React.useState(false);
const deleteApp = React.useCallback(
async (appPubkey: string) => {
setDeleting(true);
try {
await request(`/api/apps/${appPubkey}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
toast("Connection deleted");
if (onSuccess) {
onSuccess(appPubkey);
}
} catch (error) {
await handleRequestError("Failed to delete connection", error);
} finally {
setDeleting(false);
const deleteApp = React.useCallback(async () => {
setDeleting(true);
try {
// Delete the app/sub-wallet
await request(`/api/apps/${app.appPubkey}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
toast("Connection deleted");
if (onSuccess) {
onSuccess();
}
},
[onSuccess]
);
} catch (error) {
await handleRequestError("Failed to delete connection", error);
} finally {
setDeleting(false);
}
}, [onSuccess, app]);
return React.useMemo(
() => ({ deleteApp, isDeleting }),

View file

@ -24,7 +24,6 @@ export function AppsCleanup() {
const [skippedCount, setSkippedCount] = React.useState<number>(0);
const [deletedCount, setDeletedCount] = React.useState<number>(0);
const [appsToReview, setAppsToReview] = React.useState<App[]>();
const { deleteApp } = useDeleteApp();
React.useEffect(() => {
if (!unusedApps) {
return;
@ -86,17 +85,13 @@ export function AppsCleanup() {
<SkipForwardIcon />
Skip
</Button>
<Button
variant="destructive"
onClick={() => {
deleteApp(currentApp.appPubkey);
<DeleteAppButton
app={currentApp}
onDelete={() => {
setAppIndex(appIndex + 1);
setDeletedCount((current) => current + 1);
}}
>
<Trash2Icon />
Delete
</Button>
/>
</>
}
readonly
@ -133,3 +128,25 @@ export function AppsCleanup() {
</>
);
}
function DeleteAppButton({
app,
onDelete,
}: {
app: App;
onDelete: () => void;
}) {
const { deleteApp } = useDeleteApp(app);
return (
<Button
variant="destructive"
onClick={() => {
deleteApp();
onDelete();
}}
>
<Trash2Icon />
Delete
</Button>
);
}