From 2c9de781bd964437035a094e37c4bdbdd0782478 Mon Sep 17 00:00:00 2001 From: keshav0479 Date: Mon, 23 Mar 2026 18:14:18 +0530 Subject: [PATCH 1/2] fix: sort Recently Used Apps by settled transactions --- api/api.go | 7 ++++ .../home/widgets/LatestUsedAppsWidget.tsx | 40 ++++++++----------- frontend/src/hooks/useApps.ts | 2 +- nip47/event_handler.go | 2 +- 4 files changed, 26 insertions(+), 25 deletions(-) diff --git a/api/api.go b/api/api.go index 47ca130bb..97d24ae76 100644 --- a/api/api.go +++ b/api/api.go @@ -533,6 +533,13 @@ func (api *api) ListApps(limit uint64, offset uint64, filters ListAppsFilters, o orderBy = "last_used_at IS NULL, " + orderBy } + if orderBy == "last_settled_transaction" { + query = query.Select("apps.*, MAX(transactions.settled_at) as last_transaction_at"). + Joins("LEFT JOIN transactions ON transactions.app_id = apps.id AND transactions.state = ?", constants.TRANSACTION_STATE_SETTLED). + Group("apps.id") + orderBy = "last_transaction_at IS NULL, last_transaction_at DESC, apps.last_used_at" + } + query = query.Order(orderBy + " DESC") if limit == 0 { diff --git a/frontend/src/components/home/widgets/LatestUsedAppsWidget.tsx b/frontend/src/components/home/widgets/LatestUsedAppsWidget.tsx index 079fea3db..f732ce116 100644 --- a/frontend/src/components/home/widgets/LatestUsedAppsWidget.tsx +++ b/frontend/src/components/home/widgets/LatestUsedAppsWidget.tsx @@ -13,7 +13,7 @@ import { ALBY_ACCOUNT_APP_NAME } from "src/constants"; import { useApps } from "src/hooks/useApps"; export function LatestUsedAppsWidget() { - const { data: appsData } = useApps(3, undefined, undefined, "last_used_at"); + const { data: appsData } = useApps(3, undefined, undefined, "last_settled_transaction"); const apps = appsData?.apps; const usedApps = apps?.filter((x) => x.lastUsedAt); @@ -32,28 +32,22 @@ export function LatestUsedAppsWidget() { - {usedApps - .sort( - (a, b) => - new Date(b.lastUsedAt ?? 0).getTime() - - new Date(a.lastUsedAt ?? 0).getTime() - ) - .map((app) => ( - -
- -

- {app.name === ALBY_ACCOUNT_APP_NAME - ? "Alby Account" - : app.name} -

-

- {app.lastUsedAt ? dayjs(app.lastUsedAt).fromNow() : "never"} -

- -
- - ))} + {usedApps.map((app) => ( + +
+ +

+ {app.name === ALBY_ACCOUNT_APP_NAME + ? "Alby Account" + : app.name} +

+

+ {app.lastUsedAt ? dayjs(app.lastUsedAt).fromNow() : "never"} +

+ +
+ + ))}
); diff --git a/frontend/src/hooks/useApps.ts b/frontend/src/hooks/useApps.ts index 4c551b749..f8cc05aad 100644 --- a/frontend/src/hooks/useApps.ts +++ b/frontend/src/hooks/useApps.ts @@ -15,7 +15,7 @@ export function useApps( unused?: boolean; subWallets?: boolean; }, - orderBy?: "last_used_at" | "created_at", + orderBy?: "last_used_at" | "created_at" | "last_settled_transaction", isEnabled = true ) { const offset = (page - 1) * limit; diff --git a/nip47/event_handler.go b/nip47/event_handler.go index c765d74da..948bc6f2b 100644 --- a/nip47/event_handler.go +++ b/nip47/event_handler.go @@ -85,7 +85,7 @@ func (svc *nip47Service) HandleEvent(ctx context.Context, pool nostrmodels.Simpl err = svc.db.Model(&app).Update("last_used_at", &now).Error if err != nil { logger.Logger.WithFields(logrus.Fields{ - "it": app.ID, + "app_id": app.ID, }).WithError(err).Error("Failed to update app last used time") } From 1585dd1f5027d11e25c659e8d86398968e01ed57 Mon Sep 17 00:00:00 2001 From: keshav0479 Date: Wed, 25 Mar 2026 04:42:27 +0530 Subject: [PATCH 2/2] fix: expose lastSettledAt in API response and widget --- api/api.go | 26 ++++++++++++++++++- api/models.go | 1 + .../home/widgets/LatestUsedAppsWidget.tsx | 17 +++++++----- frontend/src/types.ts | 1 + 4 files changed, 38 insertions(+), 7 deletions(-) diff --git a/api/api.go b/api/api.go index 97d24ae76..a47cb08f3 100644 --- a/api/api.go +++ b/api/api.go @@ -533,7 +533,8 @@ func (api *api) ListApps(limit uint64, offset uint64, filters ListAppsFilters, o orderBy = "last_used_at IS NULL, " + orderBy } - if orderBy == "last_settled_transaction" { + sortBySettled := orderBy == "last_settled_transaction" + if sortBySettled { query = query.Select("apps.*, MAX(transactions.settled_at) as last_transaction_at"). Joins("LEFT JOIN transactions ON transactions.app_id = apps.id AND transactions.state = ?", constants.TRANSACTION_STATE_SETTLED). Group("apps.id") @@ -588,6 +589,28 @@ func (api *api) ListApps(limit uint64, offset uint64, filters ListAppsFilters, o permissionsMap[perm.AppId] = append(permissionsMap[perm.AppId], perm) } + // fetch last settled transaction time per app if needed + lastSettledMap := make(map[uint]*time.Time) + if sortBySettled { + type settledResult struct { + AppID uint `gorm:"column:app_id"` + LastTransactionAt *time.Time `gorm:"column:last_transaction_at"` + } + var results []settledResult + err = api.db.Model(&db.Transaction{}). + Select("app_id, MAX(settled_at) as last_transaction_at"). + Where("app_id IN ? AND state = ?", appIds, constants.TRANSACTION_STATE_SETTLED). + Group("app_id"). + Find(&results).Error + if err != nil { + logger.Logger.WithError(err).Error("Failed to fetch last settled transaction times") + return nil, err + } + for _, r := range results { + lastSettledMap[r.AppID] = r.LastTransactionAt + } + } + apiApps := []App{} for _, dbApp := range dbApps { walletPubkey := api.keys.GetNostrPublicKey() @@ -607,6 +630,7 @@ func (api *api) ListApps(limit uint64, offset uint64, filters ListAppsFilters, o WalletPubkey: walletPubkey, UniqueWalletPubkey: uniqueWalletPubkey, LastUsedAt: dbApp.LastUsedAt, + LastSettledAt: lastSettledMap[dbApp.ID], } if dbApp.Isolated { diff --git a/api/models.go b/api/models.go index b4c77accf..74bdfc0d6 100644 --- a/api/models.go +++ b/api/models.go @@ -93,6 +93,7 @@ type App struct { CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` LastUsedAt *time.Time `json:"lastUsedAt"` + LastSettledAt *time.Time `json:"lastSettledAt"` ExpiresAt *time.Time `json:"expiresAt"` Scopes []string `json:"scopes"` MaxAmountSat uint64 `json:"maxAmount"` diff --git a/frontend/src/components/home/widgets/LatestUsedAppsWidget.tsx b/frontend/src/components/home/widgets/LatestUsedAppsWidget.tsx index f732ce116..1f395ecd3 100644 --- a/frontend/src/components/home/widgets/LatestUsedAppsWidget.tsx +++ b/frontend/src/components/home/widgets/LatestUsedAppsWidget.tsx @@ -13,9 +13,14 @@ import { ALBY_ACCOUNT_APP_NAME } from "src/constants"; import { useApps } from "src/hooks/useApps"; export function LatestUsedAppsWidget() { - const { data: appsData } = useApps(3, undefined, undefined, "last_settled_transaction"); + const { data: appsData } = useApps( + 3, + undefined, + undefined, + "last_settled_transaction" + ); const apps = appsData?.apps; - const usedApps = apps?.filter((x) => x.lastUsedAt); + const usedApps = apps?.filter((x) => x.lastSettledAt); if (!usedApps?.length) { return null; @@ -37,12 +42,12 @@ export function LatestUsedAppsWidget() {

- {app.name === ALBY_ACCOUNT_APP_NAME - ? "Alby Account" - : app.name} + {app.name === ALBY_ACCOUNT_APP_NAME ? "Alby Account" : app.name}

- {app.lastUsedAt ? dayjs(app.lastUsedAt).fromNow() : "never"} + {app.lastSettledAt + ? dayjs(app.lastSettledAt).fromNow() + : "never"}

diff --git a/frontend/src/types.ts b/frontend/src/types.ts index cdf4e8a65..535938ba4 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -120,6 +120,7 @@ export interface App { createdAt: string; updatedAt: string; lastUsedAt?: string; + lastSettledAt?: string; expiresAt?: string; isolated: boolean; balance: number;