Add trust center updates list and detail pages

Build the public Updates pages in the compliance portal: a
cursor-paginated list of sent mailing-list updates and a detail view
for a single update, replacing the previous stub page.

Add a MailingListUpdate case to the trust API node resolver, guarded so
only SENT updates belonging to the current trust center's mailing list
are exposed, so the detail page can load an update by URL.

Add a Prev/Next Pagination primitive to the v2 UI kit. Page numbers are
omitted because cursor pagination cannot derive an ordinal page index;
each arrow only shows when its page exists while keeping its slot
reserved so a visible arrow never shifts position.

Relocate the shared MailingListUpdateListItem to its own component
folder and wrap each row in a link to the detail page, so both the home
recent-updates section and the list navigate to detail.

Signed-off-by: Émile Ré <emile@probo.com>
This commit is contained in:
Émile Ré
2026-07-09 17:45:13 -04:00
parent 99fa2bdde8
commit a238f78768
25 changed files with 742 additions and 36 deletions

View File

@@ -13,6 +13,7 @@ import (
"go.gearno.de/kit/log"
"go.probo.inc/probo/pkg/coredata"
"go.probo.inc/probo/pkg/gid"
"go.probo.inc/probo/pkg/mailman"
"go.probo.inc/probo/pkg/server/api/authn"
"go.probo.inc/probo/pkg/server/api/compliancepage"
"go.probo.inc/probo/pkg/server/api/trust/v1/schema"
@@ -199,6 +200,35 @@ func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error
return types.NewTrustCenterFile(trustCenterFile), nil
case coredata.MailingListUpdateEntityType:
update, err := r.mailman.GetMailingListUpdate(ctx, id)
if err != nil {
if errors.Is(err, mailman.ErrMailingListUpdateNotFound) || errors.Is(err, coredata.ErrResourceNotFound) {
return nil, gqlutils.NotFoundf(ctx, "node %q not found", id)
}
r.logger.ErrorCtx(ctx, "cannot get mailing list update", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
if update.Status != coredata.MailingListUpdateStatusSent {
return nil, gqlutils.NotFoundf(ctx, "node %q not found", id)
}
trustCenter, err := trustService.TrustCenters.Get(ctx, scope, compliancePage.ID)
if err != nil {
r.logger.ErrorCtx(ctx, "cannot get trust center", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
if trustCenter.MailingListID == nil || *trustCenter.MailingListID != update.MailingListID {
return nil, gqlutils.NotFoundf(ctx, "node %q not found", id)
}
return types.NewMailingListUpdate(update), nil
default:
return nil, gqlutils.NotFoundf(ctx, "node %q not found", id)
}