Merged in feat/LOY-391-my-points-transactions-table-design (pull request #3415)

Feat/LOY-391 my points transactions table design

* feat(LOY-391): Added new design to point transaction table

* fix(LOY-391): rebase fix

* fix(LOY-391): fix

* fix(LOY-391): fix

* fix(LOY-391): fixed sticky header etc.

* feat(LOY-391): added focus on the newest loaded item in the list

* fix(LOY-391): cleaned up

* fix(LOY-391): style fix

* fix(LOY-391): fixed PR-comments, types, removed the old files for earn and burn table

* fix(LOY-391): fixed PR-comments

* feat(LOY-391): added useCallback so scrolling is avoided when clicking see all on expiring points


Approved-by: Anton Gunnarsson
Approved-by: Matilda Landström
This commit is contained in:
Emma Zettervall
2026-01-20 08:41:09 +00:00
parent 8b56fa84e7
commit 5af64ef896
23 changed files with 473 additions and 347 deletions

View File

@@ -0,0 +1,4 @@
// 'BALFWD' are transactions from Opera migration that happended in May 2021
export const BALFWD = "BALFWD"
export const NON_TRANSACTIONAL = "non-transactional"

View File

@@ -31,6 +31,7 @@
"./constants/routes/*": "./constants/routes/*.ts",
"./constants/sessionKeys": "./constants/sessionKeys.ts",
"./constants/signatureHotels": "./constants/signatureHotels.ts",
"./constants/transactionType": "./constants/transactionType.ts",
"./dataCache": "./dataCache/index.ts",
"./dt": "./dt/dt.ts",
"./dt/utils/hasOverlappingDates": "./dt/utils/hasOverlappingDates.ts",

View File

@@ -7,6 +7,7 @@ export enum StickyElementNameEnum {
HOTEL_TAB_NAVIGATION = "HOTEL_TAB_NAVIGATION",
HOTEL_STATIC_MAP = "HOTEL_STATIC_MAP",
DESTINATION_SIDEBAR = "DESTINATION_SIDEBAR",
TRANSACTION_LIST_HEADER = "TRANSACTION_LIST_HEADER",
}
export interface StickyElement {
@@ -39,6 +40,7 @@ const priorityMap: Record<StickyElementNameEnum, number> = {
[StickyElementNameEnum.MEETING_PACKAGE_WIDGET]: 3,
[StickyElementNameEnum.DESTINATION_SIDEBAR]: 3,
[StickyElementNameEnum.TRANSACTION_LIST_HEADER]: 3,
}
const useStickyPositionStore = create<StickyStore>((set, get) => ({

View File

@@ -20,11 +20,11 @@ export const staysInput = z
export const friendTransactionsInput = z
.object({
limit: z.number().int().positive(),
page: z.number().int().positive(),
cursor: z.number().optional(),
limit: z.number().min(0).default(10),
lang: z.nativeEnum(Lang).optional(),
})
.default({ limit: 5, page: 1 })
.default({})
// Mutation
export const addCreditCardInput = z.object({

View File

@@ -1,3 +1,4 @@
import { BALFWD } from "@scandic-hotels/common/constants/transactionType"
import { createCounter } from "@scandic-hotels/common/telemetry"
import { safeTry } from "@scandic-hotels/common/utils/safeTry"
@@ -239,22 +240,23 @@ export const userQueryRouter = router({
friendTransactions: languageProtectedProcedure
.input(friendTransactionsInput)
.query(async ({ ctx, input }) => {
const { limit, page, lang } = input
const { limit, cursor, lang } = input
const language = lang ?? ctx.lang
const page = cursor ? Number(cursor) : 1
const friendTransactionsCounter = createCounter(
"trpc.user.transactions.friendTransactions"
"trpc.user.transactions.friendTransactions2"
)
const metricsFriendTransactions = friendTransactionsCounter.init({
limit,
page,
lang,
cursor,
language,
})
metricsFriendTransactions.start()
const language = lang ?? ctx.lang
const apiResponse = await api.get(
api.endpoints.v1.Profile.Transaction.friendTransactions,
{
@@ -282,15 +284,13 @@ export const userQueryRouter = router({
const updatedData = await updateStaysBookingUrl(
verifiedData.data.data,
ctx.session,
ctx.lang
language
)
const pageData = updatedData
const allTransactions = updatedData
.filter((t) => t.type !== Transactions.rewardType.expired)
.sort((a, b) => {
// 'BALFWD' are transactions from Opera migration that happended in May 2021
if (a.attributes.confirmationNumber === "BALFWD") return 1
if (b.attributes.confirmationNumber === "BALFWD") return -1
if (a.attributes.confirmationNumber === BALFWD) return 1
if (b.attributes.confirmationNumber === BALFWD) return -1
const dateA = new Date(
a.attributes.checkinDate
@@ -306,35 +306,20 @@ export const userQueryRouter = router({
return dateA > dateB ? -1 : 1
})
const slicedData = pageData.slice(limit * (page - 1), limit * page)
const result = {
data: {
transactions: slicedData.map(({ type, attributes }) => {
return {
type,
awardPoints: attributes.awardPoints,
checkinDate: attributes.checkinDate,
checkoutDate: attributes.checkoutDate,
confirmationNumber: attributes.confirmationNumber,
nights: attributes.nights,
pointsCalculated: attributes.pointsCalculated,
hotelId: attributes.hotelOperaId,
transactionDate: attributes.transactionDate,
bookingUrl: attributes.bookingUrl,
hotelName: attributes.hotelInformation?.name,
city: attributes.hotelInformation?.city,
}
}),
},
meta: {
totalPages: Math.ceil(pageData.length / limit),
},
}
const startIndex = limit * (page - 1)
const endIndex = limit * page
const paginatedTransactions = allTransactions.slice(
startIndex,
endIndex
)
const hasMore = endIndex < allTransactions.length
metricsFriendTransactions.success()
return result
return {
data: paginatedTransactions,
nextCursor: hasMore ? Number(page + 1) : undefined,
}
}),
}),