@stackedapp/utils 1.17.2 → 1.17.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/conditions.js +109 -16
  2. package/package.json +3 -3
@@ -16,6 +16,7 @@ const calculatePercent = (amount, goal, isMet) => {
16
16
  }
17
17
  return isMet ? 100 : 0;
18
18
  };
19
+ const formatList = (items) => items.length === 2 ? items.join(' and ') : `${items.slice(0, -1).join(', ')}, and ${items.at(-1)}`;
19
20
  const calculateDynamicConditionPercent = (dynamicObj, cond) => {
20
21
  if (!dynamicObj)
21
22
  return 0;
@@ -451,7 +452,7 @@ additionalData, }) => {
451
452
  }
452
453
  }
453
454
  // Validate link count conditions
454
- if (conditions?.links && 'entityLinks' in playerSnap) {
455
+ if (conditions?.links) {
455
456
  for (const [linkType, constraint] of Object.entries(conditions.links)) {
456
457
  // linkType should always exist. and be default is none was specified
457
458
  const linkCount = playerSnap.entityLinks?.filter((link) => (link.kind || exports.DEFAULT_ENTITY_KIND) === linkType).length || 0;
@@ -553,19 +554,30 @@ additionalData, }) => {
553
554
  return { isValid: false, isComplete: false, percentCompleted: 0 };
554
555
  }
555
556
  }
556
- if (conditions?.identifiers?.platforms?.length && 'identifiers' in playerSnap) {
557
+ if (conditions?.identifiers?.platforms?.length) {
557
558
  const playerPlatforms = new Set(playerSnap.identifiers?.map((i) => i.platform.toLowerCase()) || []);
558
559
  const isAndBehaviour = conditions.identifiers.behaviour === 'AND';
559
560
  const platformsToCheck = conditions.identifiers.platforms;
560
561
  let isMet;
561
562
  let displayText;
563
+ const platformsMap = {
564
+ sms: 'SMS',
565
+ email: 'Email',
566
+ apple: 'Apple',
567
+ google: 'Google',
568
+ tiktok: 'TikTok',
569
+ };
570
+ const metAndPlatforms = platformsToCheck.filter((platform) => playerPlatforms.has(platform.toLowerCase()));
562
571
  if (isAndBehaviour) {
563
- isMet = platformsToCheck.every((platform) => playerPlatforms.has(platform.toLowerCase()));
564
- displayText = `Link all: ${platformsToCheck.join(', ')}`;
572
+ isMet = metAndPlatforms.length === platformsToCheck.length;
573
+ displayText =
574
+ platformsToCheck.length > 1
575
+ ? `Link your ${formatList(platformsToCheck.map((p) => platformsMap[p.toLowerCase()] ?? p))} accounts`
576
+ : `Link your ${platformsMap[platformsToCheck[0].toLowerCase()] ?? platformsToCheck[0]} account`;
565
577
  }
566
578
  else {
567
579
  isMet = platformsToCheck.some((platform) => playerPlatforms.has(platform.toLowerCase()));
568
- displayText = `Link any: ${platformsToCheck.join(', ')}`;
580
+ displayText = `Link any of your ${formatList(platformsToCheck.map((p) => platformsMap[p.toLowerCase()] ?? p))} accounts`;
569
581
  }
570
582
  if (addDetails) {
571
583
  conditionData.push({
@@ -573,7 +585,11 @@ additionalData, }) => {
573
585
  kind: 'identifiers',
574
586
  trackerAmount: isMet ? 1 : 0,
575
587
  trackerGoal: 1,
576
- percentCompleted: isMet ? 100 : 0, // Binary
588
+ percentCompleted: isAndBehaviour
589
+ ? calculatePercent(metAndPlatforms.length, platformsToCheck.length, isMet)
590
+ : isMet
591
+ ? 100
592
+ : 0,
577
593
  text: displayText,
578
594
  });
579
595
  if (!isMet)
@@ -1155,24 +1171,101 @@ const meetsCompletionConditions = ({ completionConditions, completionTrackers, p
1155
1171
  // Evaluate contractInteractions completion trackers
1156
1172
  if (conditions?.contractInteractions) {
1157
1173
  for (const [conditionId, condition] of Object.entries(conditions.contractInteractions)) {
1158
- const baseAmount = condition.minAmount || 0;
1159
- const trackerGoal = baseAmount * claimMultiplier;
1160
- const trackerAmount = completionTrackers?.contractInteractions?.[conditionId] || 0;
1161
- const isDisqualify = trackerAmount < trackerGoal;
1162
- if (shouldScale && baseAmount > 0) {
1163
- updateMax(Math.floor(trackerAmount / baseAmount));
1174
+ // Tracker now stores { amount: number, count: number }
1175
+ // Default to { amount: 0, count: 0 } if missing
1176
+ const rawTracker = completionTrackers?.contractInteractions?.[conditionId];
1177
+ const trackerData = rawTracker || { amount: 0, count: 0 };
1178
+ const trackerAmount = trackerData.amount || 0;
1179
+ const trackerCount = trackerData.count || 0;
1180
+ // Goals
1181
+ const minAmount = condition.minAmount ? condition.minAmount * claimMultiplier : 0;
1182
+ const maxAmount = condition.maxAmount ? condition.maxAmount * claimMultiplier : undefined;
1183
+ const minCount = condition.minCount ? condition.minCount * claimMultiplier : 0;
1184
+ const maxCount = condition.maxCount ? condition.maxCount * claimMultiplier : undefined;
1185
+ let isDisqualify = false;
1186
+ // Check Amounts
1187
+ if (shouldScale) {
1188
+ // If we have scaling (claimMultiplier), we handle max claims calculation differently
1189
+ // For now, assume standard validation
1190
+ }
1191
+ if (minAmount > 0 && trackerAmount < minAmount)
1192
+ isDisqualify = true;
1193
+ if (maxAmount !== undefined && trackerAmount > maxAmount)
1194
+ isDisqualify = true;
1195
+ // Check Counts
1196
+ if (minCount > 0 && trackerCount < minCount)
1197
+ isDisqualify = true;
1198
+ if (maxCount !== undefined && trackerCount > maxCount)
1199
+ isDisqualify = true;
1200
+ if (shouldScale) {
1201
+ // Update max claims based on whatever allows the FEWEST claims
1202
+ // e.g. if I have enough Amount for 3 claims but enough Count for 1 claim, I can only make 1 claim.
1203
+ const possibleClaimsByAmount = condition.minAmount
1204
+ ? Math.floor(trackerAmount / condition.minAmount)
1205
+ : Number.MAX_SAFE_INTEGER;
1206
+ const possibleClaimsByCount = condition.minCount
1207
+ ? Math.floor(trackerCount / condition.minCount)
1208
+ : Number.MAX_SAFE_INTEGER;
1209
+ const possibleClaims = Math.min(possibleClaimsByAmount, possibleClaimsByCount);
1210
+ // Only update if we have at least ANY requirement (Amount or Count)
1211
+ // If both minAmount and minCount are 0/undefined, this logic might be weird, but usually one is set.
1212
+ if (condition.minAmount || condition.minCount) {
1213
+ updateMax(possibleClaims);
1214
+ }
1164
1215
  }
1165
1216
  if (addDetails) {
1217
+ // Determine primary metric for progress display
1218
+ // If minAmount is set, use amount. Else use count.
1219
+ let current = trackerAmount;
1220
+ let goal = minAmount;
1221
+ let percent = 0;
1222
+ // If both are set, display percent based on the average of the two
1223
+ const hasMin = minAmount > 0 || minCount > 0;
1224
+ const hasMax = (maxAmount !== undefined && maxAmount > 0) || (maxCount !== undefined && maxCount > 0);
1225
+ if (!hasMin && hasMax) {
1226
+ // If only max is set: > 0 progress = 100%, else 0%
1227
+ const maxAmountSet = maxAmount !== undefined && maxAmount > 0;
1228
+ const maxCountSet = maxCount !== undefined && maxCount > 0;
1229
+ let pcts = [];
1230
+ if (maxAmountSet) {
1231
+ pcts.push(trackerAmount > 0 ? 100 : 0);
1232
+ }
1233
+ if (maxCountSet) {
1234
+ pcts.push(trackerCount > 0 ? 100 : 0);
1235
+ }
1236
+ if (pcts.length > 0) {
1237
+ percent = pcts.reduce((a, b) => a + b, 0) / pcts.length;
1238
+ }
1239
+ else {
1240
+ percent = 0;
1241
+ }
1242
+ }
1243
+ else if (minAmount > 0 && minCount > 0) {
1244
+ const pctAmount = calculatePercent(trackerAmount, minAmount, true); // clamp to 100
1245
+ const pctCount = calculatePercent(trackerCount, minCount, true);
1246
+ percent = (pctAmount + pctCount) / 2;
1247
+ }
1248
+ else if (minCount > 0) {
1249
+ current = trackerCount;
1250
+ goal = minCount;
1251
+ percent = calculatePercent(current, goal, !isDisqualify);
1252
+ }
1253
+ else {
1254
+ percent = calculatePercent(trackerAmount, minAmount || 0, !isDisqualify);
1255
+ }
1166
1256
  const displayText = (0, template_1.renderTemplate)(condition.template, {
1167
- current: trackerAmount,
1168
- amount: trackerGoal,
1257
+ currentAmount: trackerAmount || 0,
1258
+ currentCount: trackerCount || 0,
1259
+ minAmount: minAmount || 0,
1260
+ maxAmount: maxAmount || 0,
1261
+ minCount: minCount || 0,
1262
+ maxCount: maxCount || 0,
1169
1263
  });
1170
1264
  conditionData.push({
1171
1265
  isMet: !isDisqualify,
1172
1266
  kind: 'contractInteractions',
1173
1267
  trackerAmount,
1174
- trackerGoal,
1175
- percentCompleted: calculatePercent(trackerAmount, trackerGoal, !isDisqualify),
1268
+ percentCompleted: percent,
1176
1269
  text: displayText,
1177
1270
  });
1178
1271
  if (isDisqualify)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackedapp/utils",
3
- "version": "1.17.2",
3
+ "version": "1.17.4",
4
4
  "description": "Public utilities for Stacked platform SDK",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -23,8 +23,8 @@
23
23
  "license": "ISC",
24
24
  "devDependencies": {
25
25
  "@semantic-release/commit-analyzer": "^13.0.1",
26
- "@semantic-release/gitlab": "^13.2.9",
27
- "@semantic-release/npm": "^13.1.3",
26
+ "@semantic-release/gitlab": "^13.3.0",
27
+ "@semantic-release/npm": "^13.1.4",
28
28
  "@semantic-release/release-notes-generator": "^14.1.0",
29
29
  "conventional-changelog-conventionalcommits": "^9.1.0"
30
30
  }