@ludo.ninja/components 2.2.18 → 2.2.20

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.
@@ -0,0 +1,15 @@
1
+ import { searchSchema as schema } from '@ludo.ninja/api';
2
+ import CreationEntity from '../../../../dto/CreationEntity';
3
+ declare const useFindUserLudoCreations: ({ ownerId, scrollTab, }: {
4
+ scrollTab: string | undefined;
5
+ ownerId: schema.IQueryFindUserLudoCreationsArgs["ownerId"];
6
+ }) => {
7
+ isLoading: boolean;
8
+ error: import("@apollo/client").ApolloError | undefined;
9
+ totalResults: number;
10
+ creations: CreationEntity[];
11
+ refetchQuery: () => Promise<void>;
12
+ loadMore: () => Promise<void>;
13
+ isNextLoading: boolean;
14
+ };
15
+ export default useFindUserLudoCreations;
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const react_1 = require("react");
7
+ const api_1 = require("@ludo.ninja/api");
8
+ const getPageSizeAssets_1 = require("../../../../utils/getPageSizeAssets");
9
+ const CreationEntity_1 = __importDefault(require("../../../../dto/CreationEntity"));
10
+ const limit = 20;
11
+ const useFindUserLudoCreations = ({ ownerId, scrollTab, }) => {
12
+ const [creations, setCreations] = (0, react_1.useState)([]);
13
+ const nextPageToken = (0, react_1.useRef)(null);
14
+ const [results, setResults] = (0, react_1.useState)(0);
15
+ const [isLoading, setIsLoading] = (0, react_1.useState)(true);
16
+ const [isNextLoading, setIsNextLoading] = (0, react_1.useState)(false);
17
+ const isHaveNextPage = creations.length < results;
18
+ const [fetchLudoCreations, { error, refetch, fetchMore }] = api_1.searchSchema.useFindUserLudoCreationsLazyQuery({
19
+ context: {
20
+ uri: api_1.hosts.searchHost,
21
+ },
22
+ fetchPolicy: 'no-cache',
23
+ onCompleted: ({ findUserLudoCreations: { creations, ...nextPage } }) => {
24
+ setCreations(creations.map((creation) => new CreationEntity_1.default(creation)));
25
+ setResults(nextPage.nextPage?.elements || 0);
26
+ setIsLoading(false);
27
+ nextPageToken.current = nextPage.nextPage?.token || null;
28
+ },
29
+ onError: () => {
30
+ setCreations([]);
31
+ setIsLoading(false);
32
+ nextPageToken.current = null;
33
+ },
34
+ });
35
+ (0, react_1.useEffect)(() => {
36
+ setIsLoading(true);
37
+ fetchLudoCreations({
38
+ variables: {
39
+ ownerId,
40
+ page: {
41
+ size: (0, getPageSizeAssets_1.getPageSize)({
42
+ scrollTab,
43
+ limit,
44
+ }),
45
+ token: '',
46
+ },
47
+ },
48
+ });
49
+ }, [ownerId]);
50
+ async function refetchQuery() {
51
+ await refetch();
52
+ }
53
+ async function loadMore() {
54
+ if (!nextPageToken.current || !isHaveNextPage)
55
+ return;
56
+ setIsNextLoading(true);
57
+ try {
58
+ const { data } = await fetchMore({
59
+ variables: {
60
+ ownerId,
61
+ page: {
62
+ token: nextPageToken.current,
63
+ size: limit,
64
+ },
65
+ },
66
+ updateQuery: (previousQueryResult) => {
67
+ return previousQueryResult;
68
+ },
69
+ });
70
+ const { creations: nextCreations, ...nextPage } = data.findUserLudoCreations;
71
+ setCreations((prevCreations) => [
72
+ ...prevCreations,
73
+ ...nextCreations.map((creation) => new CreationEntity_1.default(creation)),
74
+ ]);
75
+ setResults(nextPage.nextPage?.elements || 0);
76
+ nextPageToken.current = nextPage.nextPage?.token || null;
77
+ }
78
+ catch (error) {
79
+ //todo uncomment next line
80
+ // throw new Error(error as string);
81
+ }
82
+ finally {
83
+ setIsNextLoading(false);
84
+ }
85
+ }
86
+ return {
87
+ isLoading,
88
+ error,
89
+ totalResults: results,
90
+ creations,
91
+ refetchQuery,
92
+ loadMore,
93
+ isNextLoading,
94
+ };
95
+ };
96
+ exports.default = useFindUserLudoCreations;
@@ -0,0 +1,5 @@
1
+ import { TAsset } from './types';
2
+ export declare const AssetMediasOpengraph: ({ medias, alt }: {
3
+ medias: TAsset["medias"];
4
+ alt: string;
5
+ }) => import("react/jsx-runtime").JSX.Element[];
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AssetMediasOpengraph = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const Urls_1 = require("../../dto/common/Media/Urls");
6
+ const types_1 = require("./types");
7
+ const utils_1 = require("./utils");
8
+ const env_1 = require("../../store/env");
9
+ const AssetMediasOpengraph = ({ medias, alt }) => {
10
+ return [medias[0]].map((media) => {
11
+ const assetVariant = (0, utils_1.getAssetMediaVariant)({ media });
12
+ switch (assetVariant) {
13
+ case types_1.EAssetVariants.audio:
14
+ return (0, jsx_runtime_1.jsx)(AudioOpengraph, { media: media });
15
+ case types_1.EAssetVariants.video:
16
+ return (0, jsx_runtime_1.jsx)(VideoOpengraph, { media: media });
17
+ case types_1.EAssetVariants.object3d:
18
+ return (0, jsx_runtime_1.jsx)(Object3DOpengraph, { media: media, alt: alt });
19
+ case types_1.EAssetVariants.other:
20
+ case types_1.EAssetVariants.image:
21
+ case types_1.EAssetVariants.screenshot:
22
+ default:
23
+ return (0, jsx_runtime_1.jsx)(ImageOpengraph, { media: media, alt: alt });
24
+ }
25
+ });
26
+ };
27
+ exports.AssetMediasOpengraph = AssetMediasOpengraph;
28
+ const ImageOpengraph = ({ media, alt }) => {
29
+ const NEXT_PUBLIC_STATIC_DOMAIN = (0, env_1.useEnvStore)((state) => state.NEXT_PUBLIC_STATIC_DOMAIN);
30
+ const url = (0, Urls_1.isExternalMediaImage)(media.media, Urls_1.mediaSizes.regular, NEXT_PUBLIC_STATIC_DOMAIN);
31
+ return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("meta", { property: "og:image", content: url }), (0, jsx_runtime_1.jsx)("meta", { property: "og:image:alt", content: alt }), (0, jsx_runtime_1.jsx)("meta", { name: "twitter:image", content: url }), (0, jsx_runtime_1.jsx)("meta", { property: "twitter:image:alt", content: alt })] }));
32
+ };
33
+ const AudioOpengraph = ({ media }) => {
34
+ const getAudioENVDomain = (0, env_1.useEnvStore)((state) => state.getAudioDomain);
35
+ return (0, jsx_runtime_1.jsx)("meta", { property: "og:audio", content: (0, Urls_1.isExternalMediaAudio)(media.media, getAudioENVDomain) });
36
+ };
37
+ const VideoOpengraph = ({ media }) => {
38
+ const getVideoDomain = (0, env_1.useEnvStore)((state) => state.getVideoDomain);
39
+ const url = (0, Urls_1.isExternalMediaVideo)(media.media, getVideoDomain);
40
+ return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("meta", { property: "og:video", content: url }), (0, jsx_runtime_1.jsx)("meta", { property: "og:video:secure_url", content: url }), (0, jsx_runtime_1.jsx)("meta", { property: "og:video:type", content: "application/x-shockwave-flash" }), (0, jsx_runtime_1.jsx)("meta", { property: "og:image", content: url }), (0, jsx_runtime_1.jsx)("meta", { property: "\u201Dog:image:width\u201D", content: "1280" }), (0, jsx_runtime_1.jsx)("meta", { property: "\u201Dog:image:height\u201D", content: "720" })] }));
41
+ };
42
+ const Object3DOpengraph = ({ media, alt }) => {
43
+ //todo: add preview url
44
+ // const NEXT_PUBLIC_STATIC_DOMAIN = useEnvStore((state) => state.NEXT_PUBLIC_STATIC_DOMAIN);
45
+ // const previewUrl = isExternalMediaImage(media.media, mediaSizes.regular, NEXT_PUBLIC_STATIC_DOMAIN);
46
+ return ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, {}));
47
+ };
@@ -0,0 +1,4 @@
1
+ import { TAsset } from './types';
2
+ export declare const AssetMediasViews: ({ medias }: {
3
+ medias: TAsset["medias"];
4
+ }) => import("react/jsx-runtime").JSX.Element[];
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.AssetMediasViews = void 0;
7
+ const jsx_runtime_1 = require("react/jsx-runtime");
8
+ const assetPage_1 = require("../../components/assetPage");
9
+ const AssetImage_1 = __importDefault(require("../../components/assetPage/media/AssetImage"));
10
+ const AssetImage_2 = __importDefault(require("../../components/assetPage/media/AssetImage"));
11
+ const Urls_1 = require("../../dto/common/Media/Urls");
12
+ const types_1 = require("./types");
13
+ const utils_1 = require("./utils");
14
+ const env_1 = require("../../store/env");
15
+ const constants_1 = require("@ludo.ninja/core/build/constants");
16
+ const AssetMediasViews = ({ medias }) => {
17
+ return medias.map((media) => {
18
+ const assetVariant = (0, utils_1.getAssetMediaVariant)({ media });
19
+ switch (assetVariant) {
20
+ case types_1.EAssetVariants.image:
21
+ return (0, jsx_runtime_1.jsx)(AssetImageMediaView, { media: media });
22
+ case types_1.EAssetVariants.audio:
23
+ return (0, jsx_runtime_1.jsx)(AssetAudioMediaView, { media: media });
24
+ case types_1.EAssetVariants.screenshot:
25
+ return (0, jsx_runtime_1.jsx)(AssetImageMediaView, { media: media, isScreenshot: true });
26
+ case types_1.EAssetVariants.video:
27
+ return (0, jsx_runtime_1.jsx)(AssetVideoMediaView, { media: media });
28
+ case types_1.EAssetVariants.object3d:
29
+ return (0, jsx_runtime_1.jsx)(assetPage_1.Viewer3D, { file3D: media.originalUrl, file2D: "" });
30
+ case types_1.EAssetVariants.other:
31
+ return (0, jsx_runtime_1.jsx)(AssetOtherMediaView, { media: media });
32
+ default:
33
+ return ((0, jsx_runtime_1.jsx)(AssetImage_2.default, { alt: media.alt, imageUrl: "/noContent/noContent.svg", errorImg: "/noContent/noContent.svg" }));
34
+ }
35
+ });
36
+ };
37
+ exports.AssetMediasViews = AssetMediasViews;
38
+ const AssetImageMediaView = ({ media, isScreenshot }) => {
39
+ const NEXT_PUBLIC_STATIC_DOMAIN = (0, env_1.useEnvStore)((state) => state.NEXT_PUBLIC_STATIC_DOMAIN);
40
+ return ((0, jsx_runtime_1.jsx)(AssetImage_1.default, { alt: media.alt, imageUrl: (0, Urls_1.isExternalMediaImage)(media.media, Urls_1.mediaSizes.regular, NEXT_PUBLIC_STATIC_DOMAIN), errorImg: `${constants_1.staticLink}/public/noContent/noContent.svg`, originalUrl: media.originalUrl, isScreenshot: isScreenshot }));
41
+ };
42
+ const AssetAudioMediaView = ({ media }) => {
43
+ const getAudioENVDomain = (0, env_1.useEnvStore)((state) => state.getAudioDomain);
44
+ return ((0, jsx_runtime_1.jsx)(assetPage_1.AudioVideoPlayer, { playerType: "audio", poster: null, src: (0, Urls_1.isExternalMediaAudio)(media.media, getAudioENVDomain) }));
45
+ };
46
+ const AssetVideoMediaView = ({ media }) => {
47
+ const getVideoENVDomain = (0, env_1.useEnvStore)((state) => state.getVideoDomain);
48
+ return ((0, jsx_runtime_1.jsx)(assetPage_1.AudioVideoPlayer, { playerType: "video", poster: null, src: (0, Urls_1.isExternalMediaVideo)(media.media, getVideoENVDomain) }));
49
+ };
50
+ const AssetOtherMediaView = ({ media }) => {
51
+ const NEXT_PUBLIC_STATIC_DOMAIN = (0, env_1.useEnvStore)((state) => state.NEXT_PUBLIC_STATIC_DOMAIN);
52
+ const mediaUrl = (0, Urls_1.isExternalMediaImage)(media.media, Urls_1.mediaSizes.regular, NEXT_PUBLIC_STATIC_DOMAIN);
53
+ return ((0, jsx_runtime_1.jsx)(AssetImage_2.default, { alt: media.alt, imageUrl: mediaUrl, errorImg: `${constants_1.staticLink}/public/noContent/noContent.svg`, originalUrl: media.originalUrl ?? mediaUrl }));
54
+ };
@@ -0,0 +1,3 @@
1
+ import { TAsset } from './types';
2
+ import { IAsset } from "@ludo.ninja/api/build/graphql_tools/__generated__/assetsHost/schema";
3
+ export declare const buildAsset: (asset: IAsset) => TAsset;
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildAsset = void 0;
4
+ const ItemType_1 = require("../../dto/common/ItemType");
5
+ const builder_1 = require("../media/builder");
6
+ const core_1 = require("@ludo.ninja/core");
7
+ const utils_1 = require("@ludo.ninja/utils");
8
+ const buildAsset = (asset) => {
9
+ const newAsset = {
10
+ assetId: asset.assetId,
11
+ blockchain: new core_1.BlockChainEntity(asset?.blockchain),
12
+ markets: asset.markets?.map((market) => new core_1.MarketPlaceEntity({
13
+ marketName: market?.marketName,
14
+ marketUrl: market?.marketUrl,
15
+ marketId: market?.marketUrl,
16
+ })) || [],
17
+ ownersProfiles: (asset.ownersProfiles || []),
18
+ ownersAddresses: (asset.ownersAddresses || null),
19
+ itemType: ItemType_1.LabelKeys.asset,
20
+ address: asset.address,
21
+ tokenId: asset.tokenId,
22
+ medias: (asset.medias?.map((media) => media && (0, builder_1.createAssetMedia)({ media, asset })) || []),
23
+ name: asset.name,
24
+ description: asset.description,
25
+ creatorsProfiles: (asset.creatorsProfiles || []),
26
+ creatorsAddresses: (asset.creatorsAddresses || null),
27
+ originalUrls: (asset.originalUrls || []),
28
+ rank: asset.rank ? Math.round(asset.rank) : 0,
29
+ collection: {
30
+ collectionId: asset.collectionId,
31
+ collectionTitle: asset.collectionTitle,
32
+ collectionMedias: (asset.collectionMedias?.map((media) => media &&
33
+ (0, builder_1.createAssetMedia)({
34
+ media,
35
+ asset,
36
+ })) || []),
37
+ },
38
+ category: asset.category,
39
+ categoryLabel: asset.categoryLabel,
40
+ price: {
41
+ latestPriceAmount: asset.latestPriceAmount,
42
+ latestPriceCurrency: asset.latestPriceCurrency,
43
+ },
44
+ attributes: (asset.attributes || []),
45
+ blockTimestamp: asset.blockTimestamp,
46
+ };
47
+ return {
48
+ ...newAsset,
49
+ isSlider: newAsset.medias.length > 1 || newAsset.originalUrls.length > 1,
50
+ splitedAssetId: newAsset.assetId.split(".")[1] || newAsset.assetId,
51
+ originalUrls: newAsset.originalUrls.map((originalUrl) => ({
52
+ url: (0, utils_1.checkGltfUrl)(originalUrl || ""),
53
+ mimeType: null,
54
+ originalMime: null,
55
+ })),
56
+ assetLink: `/${newAsset.itemType}/${newAsset.blockchain.getBlockChainPrivateLabel()}/${newAsset.assetId}${[core_1.BlockChainKeys.elrond, core_1.BlockChainKeys.solana].includes(newAsset.blockchain.getBlockChainPrivateLabel())
57
+ ? ""
58
+ : `/${newAsset.tokenId}`}`,
59
+ };
60
+ };
61
+ exports.buildAsset = buildAsset;
@@ -0,0 +1,49 @@
1
+ import { LabelKeys } from '../../dto/common/ItemType';
2
+ import { IMedia } from '../media/types';
3
+ import { IAsset, IProfile, IAttribute } from "@ludo.ninja/api/build/graphql_tools/__generated__/assetsHost/schema";
4
+ import { BlockChainEntity, MarketPlaceEntity } from "@ludo.ninja/core";
5
+ export type TAsset = {
6
+ assetId: IAsset["assetId"];
7
+ blockchain: BlockChainEntity;
8
+ markets: MarketPlaceEntity[];
9
+ ownersProfiles: IProfile[];
10
+ ownersAddresses: string[] | null;
11
+ itemType: LabelKeys.asset;
12
+ address: IAsset["address"];
13
+ tokenId: IAsset["tokenId"];
14
+ medias: IMedia[];
15
+ name: IAsset["name"];
16
+ description: IAsset["description"];
17
+ creatorsProfiles: IProfile[];
18
+ creatorsAddresses: string[] | null;
19
+ rank: number;
20
+ collection: {
21
+ collectionId: IAsset["collectionId"];
22
+ collectionTitle: IAsset["collectionTitle"];
23
+ collectionMedias: IMedia[];
24
+ };
25
+ category: IAsset["category"];
26
+ categoryLabel: IAsset["categoryLabel"];
27
+ price: {
28
+ latestPriceAmount: IAsset["latestPriceAmount"];
29
+ latestPriceCurrency: IAsset["latestPriceCurrency"];
30
+ };
31
+ attributes: IAttribute[];
32
+ blockTimestamp: IAsset["blockTimestamp"];
33
+ isSlider: boolean;
34
+ splitedAssetId: string;
35
+ originalUrls: {
36
+ url: string;
37
+ mimeType: null;
38
+ originalMime: null;
39
+ }[];
40
+ assetLink: string;
41
+ };
42
+ export declare enum EAssetVariants {
43
+ audio = "audio",
44
+ image = "image",
45
+ object3d = "object3d",
46
+ other = "other",
47
+ screenshot = "screenshot",
48
+ video = "video"
49
+ }
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EAssetVariants = void 0;
4
+ var EAssetVariants;
5
+ (function (EAssetVariants) {
6
+ EAssetVariants["audio"] = "audio";
7
+ EAssetVariants["image"] = "image";
8
+ EAssetVariants["object3d"] = "object3d";
9
+ EAssetVariants["other"] = "other";
10
+ EAssetVariants["screenshot"] = "screenshot";
11
+ EAssetVariants["video"] = "video";
12
+ })(EAssetVariants || (exports.EAssetVariants = EAssetVariants = {}));
@@ -0,0 +1,5 @@
1
+ import { EAssetVariants } from './types';
2
+ import { IMedia } from '../media/types';
3
+ export declare const getAssetMediaVariant: ({ media }: {
4
+ media: IMedia;
5
+ }) => EAssetVariants;
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getAssetMediaVariant = void 0;
4
+ const types_1 = require("./types");
5
+ const _3d_1 = require("../../utils/3d");
6
+ const core_1 = require("@ludo.ninja/core");
7
+ const getAssetMediaVariant = ({ media }) => {
8
+ if ((0, core_1.isImage)({
9
+ media: media.media,
10
+ mimeType: media.mimeType,
11
+ originalMimeType: media.originalMime,
12
+ })) {
13
+ return types_1.EAssetVariants.image;
14
+ }
15
+ if ((0, core_1.isAudio)({
16
+ media: media.media,
17
+ mimeType: media.mimeType,
18
+ originalMimeType: media.originalMime,
19
+ })) {
20
+ return types_1.EAssetVariants.audio;
21
+ }
22
+ if ((0, core_1.isScreenshot)({
23
+ media: media.media,
24
+ mimeType: media.mimeType,
25
+ originalMimeType: media.originalMime,
26
+ })) {
27
+ return types_1.EAssetVariants.screenshot;
28
+ }
29
+ if ((0, core_1.isVideo)({
30
+ media: media.media,
31
+ mimeType: media.mimeType,
32
+ originalMimeType: media.originalMime,
33
+ })) {
34
+ return types_1.EAssetVariants.video;
35
+ }
36
+ if ((0, _3d_1.isObject3D)({
37
+ media: media.media,
38
+ mimeType: media.mimeType,
39
+ })) {
40
+ return types_1.EAssetVariants.object3d;
41
+ }
42
+ return types_1.EAssetVariants.other;
43
+ };
44
+ exports.getAssetMediaVariant = getAssetMediaVariant;
@@ -0,0 +1,7 @@
1
+ import { IMedia } from './types';
2
+ import assetSchema from "@ludo.ninja/api/build/graphql_tools/__generated__/assetsHost/schema";
3
+ export declare const createMedia: (media: IMedia) => IMedia;
4
+ export declare const createAssetMedia: ({ media, asset }: {
5
+ media: assetSchema.IMedia;
6
+ asset: assetSchema.IAsset;
7
+ }) => IMedia;
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createAssetMedia = exports.createMedia = void 0;
4
+ const utils_1 = require("@ludo.ninja/utils");
5
+ const createMedia = (media) => ({
6
+ ...media,
7
+ media: (0, utils_1.checkGltfUrl)(media.media || ""),
8
+ });
9
+ exports.createMedia = createMedia;
10
+ const createAssetMedia = ({ media, asset }) => (0, exports.createMedia)({
11
+ media: media?.url || "",
12
+ nsfw: null,
13
+ mimeType: media?.mimeType || null,
14
+ previewUrl: null,
15
+ originalUrl: media?.originalUrl || null,
16
+ alt: `${asset.name || ""} ${asset.address || ""}`,
17
+ originalMime: media?.originalMime || null,
18
+ });
19
+ exports.createAssetMedia = createAssetMedia;
@@ -0,0 +1,9 @@
1
+ export interface IMedia {
2
+ media: string;
3
+ mimeType: null | string;
4
+ nsfw: number | null;
5
+ previewUrl: string[] | null;
6
+ originalUrl?: string | null;
7
+ alt: string;
8
+ originalMime: null | string;
9
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ludo.ninja/components",
3
- "version": "2.2.18",
3
+ "version": "2.2.20",
4
4
  "private": false,
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -23,7 +23,7 @@
23
23
  "publish": "npm publish --access public -workspace @ludo.ninja/components"
24
24
  },
25
25
  "dependencies": {
26
- "@ludo.ninja/api": "^2.9.8",
26
+ "@ludo.ninja/api": "^3.0.20",
27
27
  "@react-three/drei": "^9.68.3",
28
28
  "@react-three/fiber": "^8.13.0",
29
29
  "chart.js": "^4.4.3",