@sonatel-os/strapi-plugin-tcg 0.2.0 → 0.3.0
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.
- package/README.md +2 -2
- package/dist/_chunks/{App-DvvgeCGs.js → App-B-WZGNkc.js} +17 -3
- package/dist/_chunks/{App-j9qu2-To.mjs → App-CS_cVYEI.mjs} +17 -3
- package/dist/_chunks/{SettingsPage-Bxa_71iT.js → SettingsPage-DNWXuY8j.js} +2 -2
- package/dist/_chunks/{SettingsPage-BcJXkWnW.mjs → SettingsPage-fGiEcX6l.mjs} +2 -2
- package/dist/_chunks/{api-DACN35Y5.mjs → api-BgR-fpDp.mjs} +1 -1
- package/dist/_chunks/{api-Aney2S9e.js → api-hTExOv_J.js} +1 -1
- package/dist/_chunks/{index-DXuaf32J.js → index-CRbzJppw.js} +2 -2
- package/dist/_chunks/{index-DSm3eEtn.mjs → index-ClDgxnnH.mjs} +2 -2
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +1 -1
- package/dist/server/index.js +745 -173
- package/dist/server/index.mjs +745 -173
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ It was built to move **Jambaar** (the Dakar 2026 Youth Olympics card game) off i
|
|
|
16
16
|
| **Live** | **Boosts**: multiply the odds of any rarity for a time window, season-wide or on one pack; presets like "Golden hour". Open/sold-out drops, rewards running low. | `GET /live` |
|
|
17
17
|
| **Pulls** | Dashboard rarity split vs target, audit log with seed, roll, **odds snapshot**, **pity** flag and **mint number**. | `POST /pulls/scan`, `GET /pulls/history`, `GET /pulls/cooldowns` |
|
|
18
18
|
| **Collection** | Per-player view with completion, rank, sets, badges, mint numbers. | `GET /collection`, `GET /collection/stats` |
|
|
19
|
-
| **Trades** | Counts on the dashboard. | `POST /trades/offer`, `POST /trades/scan`, `…/
|
|
19
|
+
| **Trades** | A handshake both players confirm: the initiator's offer (QR) → a receiver joins → proposes cards → the initiator confirms (atomic swap) or declines. Both screens poll `GET /trades/offer/:id` (`status`, `role`, both sides' cards). Counts on the dashboard. | `POST /trades/offer`, `POST /trades/scan`, `GET …/offer/:id`, `…/join`, `…/propose`, `…/confirm`, `…/decline`, `…/reject`, `…/cancel`, `GET /trades/active`, `GET /trades/history` |
|
|
20
20
|
| **Rewards** | **Catalog** with tiers, stock, per-player limit, booth, availability window and a **cost builder**: N cards of a rarity (or at least a rarity), a specific card, a complete set (owned or handed in). | `GET /rewards`, `GET /rewards/eligibility` |
|
|
21
21
|
| **Booth** | Type or scan a redemption code, see the cards and the reward, hand it over or cancel and give the cards back (stock released). | `POST /redemptions`, `GET /redemptions/:id`, `GET /redemptions/history` |
|
|
22
22
|
| **Sets & badges** | **Sets** are collection goals and reward costs. **Badges** are achievements with rules (pulls, rarity pulls, packs visited, streak, completion, mint ≤ N, scanned a drop, set complete…), secret or visible; nine starters seeded per season. | `GET /sets`, `GET /badges` |
|
|
@@ -104,7 +104,7 @@ Clients branch on `code` (see `server/src/constants.js`), never on the French me
|
|
|
104
104
|
|
|
105
105
|
Pulls are deterministic and auditable: `sha256(player:pack:pullNumber:timestamp)` seeds a Mulberry32 PRNG; the first roll picks the rarity from the season's cumulative table, the second picks a card by weight inside that rarity (falling back to the whole pool). Seed, roll, rarity and a verification hash are stored on every pull, and the Audit screen shows them. The Simulate tab runs the same engine 2 000 times against a pack without writing anything.
|
|
106
106
|
|
|
107
|
-
QR tokens are HMAC-signed (`
|
|
107
|
+
QR tokens are HMAC-signed. A **static** drop code (`s.<drop>.<sig>`, the printed poster) never expires and is bounded per player by the drop's cooldown and caps. A **rotating** drop code (`d.<drop>.<nonce>.<exp>.<sig>`, the booth screen) is valid until it rotates and every player may scan it **once** — the ledger key is nonce + player, so a crowd around one screen all get their card; the cooldown is the player's, not the code's. Trade tokens burn their nonce globally. Consumed nonces live in `tcg_nonces` so replay protection works across restarts and pods; expired nonces and trade offers are swept every minute. A drop can hand out several cards per scan (`cardsPerPull`, 1–10): one scan counts once against the cooldown and the daily limits, every card is minted and recorded (`isExtra` on the extra pulls), and the response carries `cards[]` next to the first `card`.
|
|
108
108
|
|
|
109
109
|
## Permissions
|
|
110
110
|
|
|
@@ -7,9 +7,9 @@ const admin = require("@strapi/strapi/admin");
|
|
|
7
7
|
const reactRouterDom = require("react-router-dom");
|
|
8
8
|
const reactIntl = require("react-intl");
|
|
9
9
|
const styledComponents = require("styled-components");
|
|
10
|
-
const api = require("./api-
|
|
10
|
+
const api = require("./api-hTExOv_J.js");
|
|
11
11
|
const react = require("react");
|
|
12
|
-
const index = require("./index-
|
|
12
|
+
const index = require("./index-CRbzJppw.js");
|
|
13
13
|
const QR = require("qrcode");
|
|
14
14
|
const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
|
|
15
15
|
const QR__default = /* @__PURE__ */ _interopDefault(QR);
|
|
@@ -3071,6 +3071,7 @@ const DropEditPage = () => {
|
|
|
3071
3071
|
maxScansTotal: drop.maxScansTotal ?? "",
|
|
3072
3072
|
maxScansPerPlayer: drop.maxScansPerPlayer ?? "",
|
|
3073
3073
|
cooldownMs: drop.cooldownMs ?? "",
|
|
3074
|
+
cardsPerPull: drop.cardsPerPull ?? 1,
|
|
3074
3075
|
sortOrder: drop.sortOrder ?? 0
|
|
3075
3076
|
});
|
|
3076
3077
|
if (drop && !pool)
|
|
@@ -3118,6 +3119,7 @@ const DropEditPage = () => {
|
|
|
3118
3119
|
maxScansTotal: num(form.maxScansTotal),
|
|
3119
3120
|
maxScansPerPlayer: num(form.maxScansPerPlayer),
|
|
3120
3121
|
cooldownMs: num(form.cooldownMs),
|
|
3122
|
+
cardsPerPull: Math.min(10, Math.max(1, Number(form.cardsPerPull) || 1)),
|
|
3121
3123
|
sortOrder: Number(form.sortOrder) || 0
|
|
3122
3124
|
}
|
|
3123
3125
|
}).unwrap();
|
|
@@ -3170,7 +3172,7 @@ const DropEditPage = () => {
|
|
|
3170
3172
|
drop.pack?.name,
|
|
3171
3173
|
drop.mode === "static" ? "printed QR" : "rotating QR",
|
|
3172
3174
|
`${drop.scanCount ?? 0} scans`
|
|
3173
|
-
].filter(Boolean).join(" · ") + `${drop.maxScansTotal != null ? ` / ${drop.maxScansTotal}` : ""}`,
|
|
3175
|
+
].filter(Boolean).join(" · ") + `${drop.maxScansTotal != null ? ` / ${drop.maxScansTotal}` : ""}${Number(drop.cardsPerPull) > 1 ? ` · ${drop.cardsPerPull} cards per scan` : ""}`,
|
|
3174
3176
|
intro: {
|
|
3175
3177
|
id: "drop-edit",
|
|
3176
3178
|
title: "Setting up this drop",
|
|
@@ -3407,6 +3409,18 @@ const DropEditPage = () => {
|
|
|
3407
3409
|
}
|
|
3408
3410
|
)
|
|
3409
3411
|
] }) }),
|
|
3412
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 4, s: 12, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { name: "cardsPerPull", style: { width: "100%" }, children: [
|
|
3413
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: "Cards per scan" }),
|
|
3414
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3415
|
+
designSystem.NumberInput,
|
|
3416
|
+
{
|
|
3417
|
+
value: Number(form.cardsPerPull) || 1,
|
|
3418
|
+
onValueChange: (v) => set("cardsPerPull", v ?? 1),
|
|
3419
|
+
disabled: ro
|
|
3420
|
+
}
|
|
3421
|
+
),
|
|
3422
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, { children: "1 to 10. One scan counts once against the cooldown and the daily limits." })
|
|
3423
|
+
] }) }),
|
|
3410
3424
|
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 12, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { name: "description", style: { width: "100%" }, children: [
|
|
3411
3425
|
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: "Description (shown to players)" }),
|
|
3412
3426
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -5,9 +5,9 @@ import { Page, useRBAC, useNotification } from "@strapi/strapi/admin";
|
|
|
5
5
|
import { useNavigate, useParams, Routes, Route, NavLink } from "react-router-dom";
|
|
6
6
|
import { useIntl } from "react-intl";
|
|
7
7
|
import { styled } from "styled-components";
|
|
8
|
-
import { u as useGetSeasonsQuery, E as Eyebrow, I as Intro, S as SPRING$1, a as useGetDashboardQuery, b as useGetLeaderboardQuery, c as useGetRewardsQuery, d as useGetSetsQuery, e as useGetBadgesQuery, f as Empty, g as Section, h as SetupSteps, i as StatTile, j as Sparkline, k as fmtNumber, R as RarityBadge, B as Bar, t as timeAgo, C as Card, P as Pill, l as StatusBadge, m as fmtDate, n as useGetSeasonDefaultsQuery, o as useGetSeasonQuery, p as useCreateSeasonMutation, q as useUpdateSeasonMutation, r as useDeleteSeasonMutation, s as errorMessage, v as useGetCardsQuery, w as useCreateCardMutation, x as useUpdateCardMutation, y as useDeleteCardMutation, z as rarityOf, A as useGetPacksQuery, D as useCreatePackMutation, F as useGetPackQuery, G as useUpdatePackMutation, H as useSetPackCardsMutation, J as useDeletePackMutation, K as useCreateDropMutation, L as useLazySimulatePackQuery, M as useGetPlayersQuery, N as fmtDateTime, O as useGetPlayerQuery, Q as useSetPlayerActiveMutation, T as useGrantCardMutation, U as useLazyGetRedemptionQuery, V as useFulfillRedemptionMutation, W as useCancelRedemptionMutation, X as useGetRedemptionsQuery, Y as Mono, Z as useGetPullsQuery, _ as useGetDropQuery, $ as useUpdateDropMutation, a0 as useSetDropCardsMutation, a1 as useDeleteDropMutation, a2 as useRotateDropSecretMutation, a3 as useDropQRMutation, a4 as useGetBoostsQuery, a5 as useCreateBoostMutation, a6 as useUpdateBoostMutation, a7 as useDeleteBoostMutation, a8 as useCreateRewardMutation, a9 as useUpdateRewardMutation, aa as useDeleteRewardMutation, ab as useCreateSetMutation, ac as useUpdateSetMutation, ad as useDeleteSetMutation, ae as useCreateBadgeMutation, af as useUpdateBadgeMutation, ag as useDeleteBadgeMutation, ah as useSeedBadgesMutation } from "./api-
|
|
8
|
+
import { u as useGetSeasonsQuery, E as Eyebrow, I as Intro, S as SPRING$1, a as useGetDashboardQuery, b as useGetLeaderboardQuery, c as useGetRewardsQuery, d as useGetSetsQuery, e as useGetBadgesQuery, f as Empty, g as Section, h as SetupSteps, i as StatTile, j as Sparkline, k as fmtNumber, R as RarityBadge, B as Bar, t as timeAgo, C as Card, P as Pill, l as StatusBadge, m as fmtDate, n as useGetSeasonDefaultsQuery, o as useGetSeasonQuery, p as useCreateSeasonMutation, q as useUpdateSeasonMutation, r as useDeleteSeasonMutation, s as errorMessage, v as useGetCardsQuery, w as useCreateCardMutation, x as useUpdateCardMutation, y as useDeleteCardMutation, z as rarityOf, A as useGetPacksQuery, D as useCreatePackMutation, F as useGetPackQuery, G as useUpdatePackMutation, H as useSetPackCardsMutation, J as useDeletePackMutation, K as useCreateDropMutation, L as useLazySimulatePackQuery, M as useGetPlayersQuery, N as fmtDateTime, O as useGetPlayerQuery, Q as useSetPlayerActiveMutation, T as useGrantCardMutation, U as useLazyGetRedemptionQuery, V as useFulfillRedemptionMutation, W as useCancelRedemptionMutation, X as useGetRedemptionsQuery, Y as Mono, Z as useGetPullsQuery, _ as useGetDropQuery, $ as useUpdateDropMutation, a0 as useSetDropCardsMutation, a1 as useDeleteDropMutation, a2 as useRotateDropSecretMutation, a3 as useDropQRMutation, a4 as useGetBoostsQuery, a5 as useCreateBoostMutation, a6 as useUpdateBoostMutation, a7 as useDeleteBoostMutation, a8 as useCreateRewardMutation, a9 as useUpdateRewardMutation, aa as useDeleteRewardMutation, ab as useCreateSetMutation, ac as useUpdateSetMutation, ad as useDeleteSetMutation, ae as useCreateBadgeMutation, af as useUpdateBadgeMutation, ag as useDeleteBadgeMutation, ah as useSeedBadgesMutation } from "./api-BgR-fpDp.mjs";
|
|
9
9
|
import { useState, useEffect, useMemo, createContext, useContext, useRef } from "react";
|
|
10
|
-
import { P as PLUGIN_ID, a as PERMISSIONS } from "./index-
|
|
10
|
+
import { P as PLUGIN_ID, a as PERMISSIONS } from "./index-ClDgxnnH.mjs";
|
|
11
11
|
import QR from "qrcode";
|
|
12
12
|
const KEY = "tcg.season";
|
|
13
13
|
const SeasonContext = createContext({
|
|
@@ -3067,6 +3067,7 @@ const DropEditPage = () => {
|
|
|
3067
3067
|
maxScansTotal: drop.maxScansTotal ?? "",
|
|
3068
3068
|
maxScansPerPlayer: drop.maxScansPerPlayer ?? "",
|
|
3069
3069
|
cooldownMs: drop.cooldownMs ?? "",
|
|
3070
|
+
cardsPerPull: drop.cardsPerPull ?? 1,
|
|
3070
3071
|
sortOrder: drop.sortOrder ?? 0
|
|
3071
3072
|
});
|
|
3072
3073
|
if (drop && !pool)
|
|
@@ -3114,6 +3115,7 @@ const DropEditPage = () => {
|
|
|
3114
3115
|
maxScansTotal: num(form.maxScansTotal),
|
|
3115
3116
|
maxScansPerPlayer: num(form.maxScansPerPlayer),
|
|
3116
3117
|
cooldownMs: num(form.cooldownMs),
|
|
3118
|
+
cardsPerPull: Math.min(10, Math.max(1, Number(form.cardsPerPull) || 1)),
|
|
3117
3119
|
sortOrder: Number(form.sortOrder) || 0
|
|
3118
3120
|
}
|
|
3119
3121
|
}).unwrap();
|
|
@@ -3166,7 +3168,7 @@ const DropEditPage = () => {
|
|
|
3166
3168
|
drop.pack?.name,
|
|
3167
3169
|
drop.mode === "static" ? "printed QR" : "rotating QR",
|
|
3168
3170
|
`${drop.scanCount ?? 0} scans`
|
|
3169
|
-
].filter(Boolean).join(" · ") + `${drop.maxScansTotal != null ? ` / ${drop.maxScansTotal}` : ""}`,
|
|
3171
|
+
].filter(Boolean).join(" · ") + `${drop.maxScansTotal != null ? ` / ${drop.maxScansTotal}` : ""}${Number(drop.cardsPerPull) > 1 ? ` · ${drop.cardsPerPull} cards per scan` : ""}`,
|
|
3170
3172
|
intro: {
|
|
3171
3173
|
id: "drop-edit",
|
|
3172
3174
|
title: "Setting up this drop",
|
|
@@ -3403,6 +3405,18 @@ const DropEditPage = () => {
|
|
|
3403
3405
|
}
|
|
3404
3406
|
)
|
|
3405
3407
|
] }) }),
|
|
3408
|
+
/* @__PURE__ */ jsx(Grid.Item, { col: 4, s: 12, children: /* @__PURE__ */ jsxs(Field.Root, { name: "cardsPerPull", style: { width: "100%" }, children: [
|
|
3409
|
+
/* @__PURE__ */ jsx(Field.Label, { children: "Cards per scan" }),
|
|
3410
|
+
/* @__PURE__ */ jsx(
|
|
3411
|
+
NumberInput,
|
|
3412
|
+
{
|
|
3413
|
+
value: Number(form.cardsPerPull) || 1,
|
|
3414
|
+
onValueChange: (v) => set("cardsPerPull", v ?? 1),
|
|
3415
|
+
disabled: ro
|
|
3416
|
+
}
|
|
3417
|
+
),
|
|
3418
|
+
/* @__PURE__ */ jsx(Field.Hint, { children: "1 to 10. One scan counts once against the cooldown and the daily limits." })
|
|
3419
|
+
] }) }),
|
|
3406
3420
|
/* @__PURE__ */ jsx(Grid.Item, { col: 12, children: /* @__PURE__ */ jsxs(Field.Root, { name: "description", style: { width: "100%" }, children: [
|
|
3407
3421
|
/* @__PURE__ */ jsx(Field.Label, { children: "Description (shown to players)" }),
|
|
3408
3422
|
/* @__PURE__ */ jsx(
|
|
@@ -3,8 +3,8 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
3
3
|
const jsxRuntime = require("react/jsx-runtime");
|
|
4
4
|
const designSystem = require("@strapi/design-system");
|
|
5
5
|
const admin = require("@strapi/strapi/admin");
|
|
6
|
-
const api = require("./api-
|
|
7
|
-
const index = require("./index-
|
|
6
|
+
const api = require("./api-hTExOv_J.js");
|
|
7
|
+
const index = require("./index-CRbzJppw.js");
|
|
8
8
|
const Row = ({ k, v, hint, tone }) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
9
9
|
designSystem.Flex,
|
|
10
10
|
{
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Grid, Box, Flex, Typography, Badge } from "@strapi/design-system";
|
|
3
3
|
import { Page, Layouts } from "@strapi/strapi/admin";
|
|
4
|
-
import { ai as useGetSettingsQuery, I as Intro, g as Section, Y as Mono } from "./api-
|
|
5
|
-
import { a as PERMISSIONS } from "./index-
|
|
4
|
+
import { ai as useGetSettingsQuery, I as Intro, g as Section, Y as Mono } from "./api-BgR-fpDp.mjs";
|
|
5
|
+
import { a as PERMISSIONS } from "./index-ClDgxnnH.mjs";
|
|
6
6
|
const Row = ({ k, v, hint, tone }) => /* @__PURE__ */ jsxs(
|
|
7
7
|
Flex,
|
|
8
8
|
{
|
|
@@ -6,7 +6,7 @@ import { EmptyDocuments } from "@strapi/icons/symbols";
|
|
|
6
6
|
import { useNavigate } from "react-router-dom";
|
|
7
7
|
import { styled, css, keyframes } from "styled-components";
|
|
8
8
|
import { adminApi } from "@strapi/strapi/admin";
|
|
9
|
-
import { P as PLUGIN_ID } from "./index-
|
|
9
|
+
import { P as PLUGIN_ID } from "./index-ClDgxnnH.mjs";
|
|
10
10
|
const fmtDateTime = (value, locale) => {
|
|
11
11
|
if (!value) return "—";
|
|
12
12
|
try {
|
|
@@ -7,7 +7,7 @@ const symbols = require("@strapi/icons/symbols");
|
|
|
7
7
|
const reactRouterDom = require("react-router-dom");
|
|
8
8
|
const styledComponents = require("styled-components");
|
|
9
9
|
const admin = require("@strapi/strapi/admin");
|
|
10
|
-
const index = require("./index-
|
|
10
|
+
const index = require("./index-CRbzJppw.js");
|
|
11
11
|
const fmtDateTime = (value, locale) => {
|
|
12
12
|
if (!value) return "—";
|
|
13
13
|
try {
|
|
@@ -63,7 +63,7 @@ const index = {
|
|
|
63
63
|
intlLabel: { id: `${PLUGIN_ID}.menu.label`, defaultMessage: "TCG" },
|
|
64
64
|
permissions: PERMISSIONS.access,
|
|
65
65
|
Component: async () => {
|
|
66
|
-
const { App } = await Promise.resolve().then(() => require("./App-
|
|
66
|
+
const { App } = await Promise.resolve().then(() => require("./App-B-WZGNkc.js"));
|
|
67
67
|
return App;
|
|
68
68
|
}
|
|
69
69
|
});
|
|
@@ -76,7 +76,7 @@ const index = {
|
|
|
76
76
|
intlLabel: { id: `${PLUGIN_ID}.settings.link`, defaultMessage: "Configuration" },
|
|
77
77
|
permissions: PERMISSIONS.settingsRead,
|
|
78
78
|
Component: async () => {
|
|
79
|
-
const { SettingsPage } = await Promise.resolve().then(() => require("./SettingsPage-
|
|
79
|
+
const { SettingsPage } = await Promise.resolve().then(() => require("./SettingsPage-DNWXuY8j.js"));
|
|
80
80
|
return SettingsPage;
|
|
81
81
|
}
|
|
82
82
|
}
|
|
@@ -62,7 +62,7 @@ const index = {
|
|
|
62
62
|
intlLabel: { id: `${PLUGIN_ID}.menu.label`, defaultMessage: "TCG" },
|
|
63
63
|
permissions: PERMISSIONS.access,
|
|
64
64
|
Component: async () => {
|
|
65
|
-
const { App } = await import("./App-
|
|
65
|
+
const { App } = await import("./App-CS_cVYEI.mjs");
|
|
66
66
|
return App;
|
|
67
67
|
}
|
|
68
68
|
});
|
|
@@ -75,7 +75,7 @@ const index = {
|
|
|
75
75
|
intlLabel: { id: `${PLUGIN_ID}.settings.link`, defaultMessage: "Configuration" },
|
|
76
76
|
permissions: PERMISSIONS.settingsRead,
|
|
77
77
|
Component: async () => {
|
|
78
|
-
const { SettingsPage } = await import("./SettingsPage-
|
|
78
|
+
const { SettingsPage } = await import("./SettingsPage-fGiEcX6l.mjs");
|
|
79
79
|
return SettingsPage;
|
|
80
80
|
}
|
|
81
81
|
}
|
package/dist/admin/index.js
CHANGED
package/dist/admin/index.mjs
CHANGED