@rpg-engine/long-bow 0.8.71 → 0.8.72

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rpg-engine/long-bow",
3
- "version": "0.8.71",
3
+ "version": "0.8.72",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "typings": "dist/index.d.ts",
@@ -84,7 +84,7 @@
84
84
  "dependencies": {
85
85
  "@capacitor/core": "^6.1.0",
86
86
  "@rollup/plugin-image": "^2.1.1",
87
- "@rpg-engine/shared": "^0.9.123",
87
+ "@rpg-engine/shared": "^0.10.10",
88
88
  "dayjs": "^1.11.2",
89
89
  "font-awesome": "^4.7.0",
90
90
  "fs-extra": "^10.1.0",
@@ -1,4 +1,4 @@
1
- import { IStoreItem, MetadataType } from '@rpg-engine/shared';
1
+ import { IProductBlueprint, MetadataType } from '@rpg-engine/shared';
2
2
  import React, { useState } from 'react';
3
3
  import { FaInfoCircle, FaShoppingBag, FaTimes, FaTrash } from 'react-icons/fa';
4
4
  import styled from 'styled-components';
@@ -9,12 +9,15 @@ import { SpriteFromAtlas } from '../shared/SpriteFromAtlas';
9
9
 
10
10
 
11
11
 
12
+ // Local cart item interface
13
+ interface ICartItem {
14
+ item: IProductBlueprint;
15
+ quantity: number;
16
+ metadata?: Record<string, any>;
17
+ }
18
+
12
19
  interface ICartViewProps {
13
- cartItems: {
14
- item: IStoreItem;
15
- quantity: number;
16
- metadata?: Record<string, any>;
17
- }[];
20
+ cartItems: ICartItem[];
18
21
  onRemoveFromCart: (itemKey: string) => void;
19
22
  onClose: () => void;
20
23
  onPurchase: () => Promise<boolean>;
@@ -1,4 +1,4 @@
1
- import { IItemPack, IPurchase, IStoreItem, ItemRarities, ItemSubType, ItemType, UserAccountTypes } from '@rpg-engine/shared';
1
+ import { IItemPack, IPurchase, IProductBlueprint, ItemRarities, ItemSubType, ItemType, UserAccountTypes, PaymentCurrency, PurchaseType } from '@rpg-engine/shared';
2
2
  import React, { ReactNode, useMemo, useState } from 'react';
3
3
  import { FaHistory, FaShoppingCart } from 'react-icons/fa';
4
4
  import styled from 'styled-components';
@@ -19,7 +19,7 @@ type TabId = 'premium' | 'packs' | 'items';
19
19
 
20
20
  // Define IStoreProps locally as a workaround
21
21
  export interface IStoreProps {
22
- items: IStoreItem[];
22
+ items: IProductBlueprint[];
23
23
  packs?: IItemPack[];
24
24
  atlasJSON: any;
25
25
  atlasIMG: string;
@@ -71,41 +71,33 @@ export const Store: React.FC<IStoreProps> = ({
71
71
  isCartOpen,
72
72
  } = useStoreCart();
73
73
  const [isCollectingMetadata, setIsCollectingMetadata] = useState(false);
74
- const [currentMetadataItem, setCurrentMetadataItem] = useState<IStoreItem | null>(null);
74
+ const [currentMetadataItem, setCurrentMetadataItem] = useState<IProductBlueprint | null>(null);
75
75
 
76
76
  const handleAddPackToCart = (pack: IItemPack) => {
77
- const packItem: IStoreItem = {
78
- _id: pack.key,
77
+ const packItem: IProductBlueprint = {
79
78
  key: pack.key,
80
79
  name: pack.title,
80
+ description: pack.description || '',
81
81
  price: pack.priceUSD,
82
+ currency: PaymentCurrency.USD,
82
83
  texturePath: pack.image.default || pack.image.src,
83
- textureKey: pack.image.default || pack.image.src,
84
- type: ItemType.Consumable,
85
- subType: ItemSubType.Other,
86
- description: pack.description || '',
87
- fullDescription: pack.description || '',
88
- textureAtlas: 'items',
89
- weight: 0,
84
+ type: PurchaseType.Pack,
85
+ onPurchase: async () => {},
86
+ itemType: ItemType.Consumable,
87
+ itemSubType: ItemSubType.Other,
90
88
  rarity: ItemRarities.Common,
91
- allowedEquipSlotType: [],
92
- isEquipable: false,
89
+ weight: 0,
93
90
  isStackable: false,
94
- isTwoHanded: false,
95
- hasUseWith: false,
96
91
  maxStackSize: 1,
97
92
  isUsable: false,
98
- isStorable: true,
99
- isSolid: false,
100
- isItemContainer: false,
101
93
  };
102
94
  handleAddToCart(packItem, 1);
103
95
  };
104
96
 
105
97
  const filterItems = (
106
- itemsToFilter: IStoreItem[],
98
+ itemsToFilter: IProductBlueprint[],
107
99
  type: 'items' | 'premium'
108
- ): IStoreItem[] => {
100
+ ): IProductBlueprint[] => {
109
101
  return itemsToFilter.filter(item => {
110
102
  if (type === 'premium') {
111
103
  return item.requiredAccountType?.length ?? 0 > 0;
@@ -1,4 +1,4 @@
1
- import { IStoreItem, MetadataType, UserAccountTypes } from '@rpg-engine/shared';
1
+ import { IProductBlueprint, MetadataType, UserAccountTypes } from '@rpg-engine/shared';
2
2
  import React, { useEffect, useState } from 'react';
3
3
  import { FaCartPlus } from 'react-icons/fa';
4
4
  import styled from 'styled-components';
@@ -8,10 +8,10 @@ import { CTAButton } from '../shared/CTAButton/CTAButton';
8
8
  import { SpriteFromAtlas } from '../shared/SpriteFromAtlas';
9
9
 
10
10
  interface IStoreCharacterSkinRowProps {
11
- item: IStoreItem;
11
+ item: IProductBlueprint;
12
12
  atlasJSON: Record<string, any>;
13
13
  atlasIMG: string;
14
- onAddToCart: (item: IStoreItem, quantity: number, metadata?: Record<string, any>) => void;
14
+ onAddToCart: (item: IProductBlueprint, quantity: number, metadata?: Record<string, any>) => void;
15
15
  userAccountType: UserAccountTypes;
16
16
  }
17
17
 
@@ -36,7 +36,7 @@ export const StoreCharacterSkinRow: React.FC<IStoreCharacterSkinRowProps> = ({
36
36
  // Effect to reset currentIndex when switching items
37
37
  useEffect(() => {
38
38
  setCurrentIndex(0);
39
- }, [item._id]);
39
+ }, [item.key]);
40
40
 
41
41
  const handlePreviousSkin = () => {
42
42
  setCurrentIndex((prevIndex) =>
@@ -1,14 +1,14 @@
1
- import { IItemPack, IStoreItem } from '@rpg-engine/shared';
1
+ import { IItemPack, IProductBlueprint } from '@rpg-engine/shared';
2
2
  import React from 'react';
3
3
  import { FaArrowLeft, FaCartPlus } from 'react-icons/fa';
4
4
  import styled from 'styled-components';
5
5
  import { CTAButton } from '../shared/CTAButton/CTAButton';
6
6
 
7
7
  interface IStoreItemDetailsProps {
8
- item: IStoreItem | (IItemPack & { name: string; texturePath: string });
8
+ item: IProductBlueprint | (IItemPack & { name: string; texturePath: string });
9
9
  imageUrl: string | { src: string; default?: string };
10
10
  onBack: () => void;
11
- onAddToCart: (item: IStoreItem) => void;
11
+ onAddToCart: (item: IProductBlueprint) => void;
12
12
  }
13
13
 
14
14
  export const StoreItemDetails: React.FC<IStoreItemDetailsProps> = ({
@@ -53,7 +53,7 @@ export const StoreItemDetails: React.FC<IStoreItemDetailsProps> = ({
53
53
  <CTAButton
54
54
  icon={<FaCartPlus />}
55
55
  label="Add to Cart"
56
- onClick={() => onAddToCart(item as IStoreItem)}
56
+ onClick={() => onAddToCart(item as IProductBlueprint)}
57
57
  fullWidth
58
58
  />
59
59
  </Actions>
@@ -1,4 +1,4 @@
1
- import { IStoreItem, UserAccountTypes } from '@rpg-engine/shared';
1
+ import { IProductBlueprint, UserAccountTypes } from '@rpg-engine/shared';
2
2
  import React, { useState } from 'react';
3
3
  import { FaCartPlus } from 'react-icons/fa';
4
4
  import styled from 'styled-components';
@@ -7,12 +7,13 @@ import { CTAButton } from '../shared/CTAButton/CTAButton';
7
7
  import { SpriteFromAtlas } from '../shared/SpriteFromAtlas';
8
8
 
9
9
  interface IStoreItemRowProps {
10
- item: IStoreItem;
10
+ item: IProductBlueprint;
11
11
  atlasJSON: Record<string, any>;
12
12
  atlasIMG: string;
13
- onAddToCart: (item: IStoreItem, quantity: number, metadata?: Record<string, any>) => void;
13
+ onAddToCart: (item: IProductBlueprint, quantity: number, metadata?: Record<string, any>) => void;
14
14
  userAccountType: UserAccountTypes;
15
15
  showTextInput?: boolean;
16
+ textInputPlaceholder?: string;
16
17
  }
17
18
 
18
19
  export const StoreItemRow: React.FC<IStoreItemRowProps> = ({
@@ -22,6 +23,7 @@ export const StoreItemRow: React.FC<IStoreItemRowProps> = ({
22
23
  onAddToCart,
23
24
  userAccountType,
24
25
  showTextInput = false,
26
+ textInputPlaceholder = item.inputPlaceholder,
25
27
  }) => {
26
28
  const [quantity, setQuantity] = useState(1);
27
29
  const [textInputValue, setTextInputValue] = useState('');
@@ -76,6 +78,7 @@ export const StoreItemRow: React.FC<IStoreItemRowProps> = ({
76
78
  <ItemDetails>
77
79
  <ItemName>{item.name}</ItemName>
78
80
  <ItemPrice>${item.price}</ItemPrice>
81
+ <ItemDescription>{item.description}</ItemDescription>
79
82
  </ItemDetails>
80
83
 
81
84
  <Controls>
@@ -84,7 +87,7 @@ export const StoreItemRow: React.FC<IStoreItemRowProps> = ({
84
87
  <TextInput
85
88
  type="text"
86
89
  value={textInputValue}
87
- placeholder="Enter value"
90
+ placeholder={textInputPlaceholder}
88
91
  onChange={e => setTextInputValue(e.target.value)}
89
92
  className="rpgui-input"
90
93
  />
@@ -166,6 +169,13 @@ const ItemPrice = styled.div`
166
169
  color: #fef08a;
167
170
  `;
168
171
 
172
+ const ItemDescription = styled.div`
173
+ font-family: 'Press Start 2P', cursive;
174
+ font-size: 0.625rem;
175
+ color: rgba(255, 255, 255, 0.7);
176
+ line-height: 1.4;
177
+ `;
178
+
169
179
  const Controls = styled.div`
170
180
  display: flex;
171
181
  align-items: center;
@@ -1,23 +1,24 @@
1
1
  import {
2
- ICartItem as IBaseCartItem,
3
2
  IPurchase,
4
3
  IPurchaseUnit,
5
- IStoreItem,
4
+ IProductBlueprint,
6
5
  MetadataType,
7
6
  PurchaseType
8
7
  } from '@rpg-engine/shared';
9
8
  import { useEffect, useRef, useState } from 'react';
10
9
  import { useStoreMetadata } from './useStoreMetadata';
11
10
 
12
- // Extend the base cart item to include metadata
13
- interface ICartItem extends IBaseCartItem {
11
+ // Create local cart item interface that uses IProductBlueprint
12
+ interface ICartItem {
13
+ item: IProductBlueprint;
14
+ quantity: number;
14
15
  metadata?: Record<string, any>;
15
16
  }
16
17
 
17
18
  interface IUseStoreCart {
18
19
  cartItems: ICartItem[];
19
20
  isCartOpen: boolean;
20
- handleAddToCart: (item: IStoreItem, quantity: number, metadata?: Record<string, any>) => void;
21
+ handleAddToCart: (item: IProductBlueprint, quantity: number, metadata?: Record<string, any>) => void;
21
22
  handleRemoveFromCart: (itemKey: string) => void;
22
23
  handlePurchase: (onPurchase: (purchase: IPurchase) => void) => void;
23
24
  openCart: () => void;
@@ -40,7 +41,7 @@ export const useStoreCart = (): IUseStoreCart => {
40
41
 
41
42
  const { collectMetadata, isCollectingMetadata } = useStoreMetadata();
42
43
 
43
- const handleAddToCart = async (item: IStoreItem, quantity: number, preselectedMetadata?: Record<string, any>) => {
44
+ const handleAddToCart = async (item: IProductBlueprint, quantity: number, preselectedMetadata?: Record<string, any>) => {
44
45
  // If metadata is already provided (from inline selection), use it directly
45
46
  if (preselectedMetadata) {
46
47
  setCartItems(prevItems => {
@@ -57,7 +58,7 @@ export const useStoreCart = (): IUseStoreCart => {
57
58
  }
58
59
 
59
60
  // If item requires metadata but none was provided, collect it before adding to cart
60
- if (item.metadataType && item.metadataType !== MetadataType.None) {
61
+ if (item.metadataType === MetadataType.CharacterSkin) {
61
62
  const metadata = await collectMetadata(item);
62
63
  if (!metadata) return; // User cancelled
63
64
 
@@ -156,8 +157,12 @@ export const useStoreCart = (): IUseStoreCart => {
156
157
  };
157
158
 
158
159
  // Helper functions
159
- function getPurchaseType(item: IStoreItem): PurchaseType {
160
- // Check if the item comes from a pack based on naming convention or other property
160
+ function getPurchaseType(item: IProductBlueprint): PurchaseType {
161
+ // Use the type from IProductBlueprint if available, otherwise infer
162
+ if (item.type) {
163
+ return item.type;
164
+ }
165
+ // Fallback logic for backward compatibility
161
166
  if (item.key.startsWith('pack_')) {
162
167
  return PurchaseType.Pack;
163
168
  } else {
@@ -1,16 +1,16 @@
1
- import { IStoreItem, MetadataType } from "@rpg-engine/shared";
1
+ import { IProductBlueprint, MetadataType } from "@rpg-engine/shared";
2
2
  import { useState } from "react";
3
3
 
4
4
  interface IUseStoreMetadata {
5
- collectMetadata: (item: IStoreItem) => Promise<Record<string, any> | null>;
5
+ collectMetadata: (item: IProductBlueprint) => Promise<Record<string, any> | null>;
6
6
  isCollectingMetadata: boolean;
7
7
  }
8
8
 
9
9
  export const useStoreMetadata = (): IUseStoreMetadata => {
10
10
  const [isCollectingMetadata, setIsCollectingMetadata] = useState(false);
11
11
 
12
- const collectMetadata = async (item: IStoreItem): Promise<Record<string, any> | null> => {
13
- if (!item.metadataType || item.metadataType === MetadataType.None) {
12
+ const collectMetadata = async (item: IProductBlueprint): Promise<Record<string, any> | null> => {
13
+ if (!item.metadataType || item.metadataType !== MetadataType.CharacterSkin) {
14
14
  return null;
15
15
  }
16
16
 
@@ -49,7 +49,7 @@ declare global {
49
49
  interface Window {
50
50
  __metadataResolvers?: {
51
51
  resolve: (metadata: Record<string, any> | null) => void;
52
- item: IStoreItem;
52
+ item: IProductBlueprint;
53
53
  };
54
54
  }
55
55
  }
@@ -1,4 +1,4 @@
1
- import { IStoreItem, MetadataType, UserAccountTypes } from '@rpg-engine/shared';
1
+ import { IProductBlueprint, MetadataType, UserAccountTypes } from '@rpg-engine/shared';
2
2
  import React, { useState } from 'react';
3
3
  import { ScrollableContent } from '../../shared/ScrollableContent/ScrollableContent';
4
4
  import { StoreCharacterSkinRow } from '../StoreCharacterSkinRow';
@@ -6,8 +6,8 @@ import { StoreItemRow } from '../StoreItemRow';
6
6
 
7
7
 
8
8
  interface IStoreItemsSectionProps {
9
- items: IStoreItem[];
10
- onAddToCart: (item: IStoreItem, quantity: number, metadata?: Record<string, any>) => void;
9
+ items: IProductBlueprint[];
10
+ onAddToCart: (item: IProductBlueprint, quantity: number, metadata?: Record<string, any>) => void;
11
11
  atlasJSON: Record<string, any>;
12
12
  atlasIMG: string;
13
13
  userAccountType?: UserAccountTypes;
@@ -28,12 +28,12 @@ export const StoreItemsSection: React.FC<IStoreItemsSectionProps> = ({
28
28
  item.name.toLowerCase().includes(searchQuery.toLowerCase())
29
29
  );
30
30
 
31
- const renderStoreItem = (item: IStoreItem) => {
31
+ const renderStoreItem = (item: IProductBlueprint) => {
32
32
  // Prefer a specialized character skin row when needed
33
33
  if (item.metadataType === MetadataType.CharacterSkin) {
34
34
  return (
35
35
  <StoreCharacterSkinRow
36
- key={item._id}
36
+ key={item.key}
37
37
  item={item}
38
38
  atlasJSON={atlasJSON}
39
39
  atlasIMG={atlasIMG}
@@ -43,10 +43,10 @@ export const StoreItemsSection: React.FC<IStoreItemsSectionProps> = ({
43
43
  );
44
44
  }
45
45
  // Render text input row when configured for this item key
46
- if (textInputItemKeys.includes(item.key) || textInputItemKeys.includes(item._id)) {
46
+ if (textInputItemKeys.includes(item.key)) {
47
47
  return (
48
48
  <StoreItemRow
49
- key={item._id}
49
+ key={item.key}
50
50
  item={item}
51
51
  atlasJSON={atlasJSON}
52
52
  atlasIMG={atlasIMG}
@@ -59,7 +59,7 @@ export const StoreItemsSection: React.FC<IStoreItemsSectionProps> = ({
59
59
  // Fallback to standard arrow-based row
60
60
  return (
61
61
  <StoreItemRow
62
- key={item._id}
62
+ key={item.key}
63
63
  item={item}
64
64
  atlasJSON={atlasJSON}
65
65
  atlasIMG={atlasIMG}
@@ -1,4 +1,4 @@
1
- import { IItemPack, IPurchase, IStoreItem, ItemRarities, ItemSubType, ItemType, MetadataType, UserAccountTypes } from '@rpg-engine/shared';
1
+ import { IItemPack, IPurchase, IProductBlueprint, ItemRarities, ItemSubType, ItemType, MetadataType, UserAccountTypes, PaymentCurrency, PurchaseType } from '@rpg-engine/shared';
2
2
  import type { Meta, StoryObj } from '@storybook/react';
3
3
  import React from 'react';
4
4
  import { RPGUIRoot } from '../../../components/RPGUI/RPGUIRoot';
@@ -29,41 +29,24 @@ export default meta;
29
29
  type Story = StoryObj<typeof Store>;
30
30
 
31
31
  // Create mock items once, with fixed stock values
32
- const storeItems: IStoreItem[] = mockItems.map((item, index) => ({
33
- _id: `original-${item.key}-${index}`,
32
+ const storeItems: IProductBlueprint[] = mockItems.map((item, index) => ({
34
33
  key: `original-${item.key}-${index}`,
35
34
  name: item.name,
36
35
  description: item.description,
37
- fullDescription: item.description,
36
+ price: item.basePrice,
37
+ currency: PaymentCurrency.USD,
38
38
  texturePath: item.texturePath,
39
39
  textureAtlas: 'items',
40
40
  textureKey: item.texturePath,
41
- price: item.basePrice,
42
- stock: 5 + (index % 5), // Fixed stock values based on index
43
- type: item.type,
44
- subType: item.subType,
45
- attack: item.attack || 0,
46
- defense: item.defense || 0,
47
- weight: item.weight,
41
+ type: PurchaseType.Item,
42
+ onPurchase: async () => {},
43
+ itemType: item.type,
44
+ itemSubType: item.subType,
48
45
  rarity: item.rarity,
49
- isEquipable: true,
46
+ weight: item.weight,
50
47
  isStackable: item.maxStackSize > 1,
51
- isUsable: true,
52
- isStorable: true,
53
- hasUseWith: false,
54
- isSolid: false,
55
- isTwoHanded: false,
56
- isItemContainer: false,
57
- layer: 1,
58
- allowedEquipSlotType: item.allowedEquipSlotType || [],
59
48
  maxStackSize: item.maxStackSize || 1,
60
- usableEffectDescription: item.usableEffectDescription || '',
61
- canSell: item.canSell ?? true,
62
- rangeType: item.rangeType,
63
- entityEffects: item.entityEffects || [],
64
- entityEffectChance: item.entityEffectChance || 0,
65
- equippedBuff: item.equippedBuff || [],
66
- equippedBuffDescription: item.equippedBuffDescription || '',
49
+ isUsable: true,
67
50
  requiredAccountType: item.rarity === 'Legendary' ? [UserAccountTypes.PremiumGold] : [],
68
51
  }));
69
52
 
@@ -87,35 +70,25 @@ const availableCharacters = [
87
70
  ];
88
71
 
89
72
  // Create character skin items
90
- const characterSkinItems: IStoreItem[] = [
73
+ const characterSkinItems: IProductBlueprint[] = [
91
74
  {
92
- _id: 'skin-character-customization',
93
75
  key: 'skin-character-customization',
94
76
  name: 'Character Skin Customization',
95
77
  description: 'Customize your character\'s appearance with a variety of skins',
96
- fullDescription: 'This premium item allows you to customize your character\'s appearance by selecting from a variety of available skins.',
78
+ price: 14.99,
79
+ currency: PaymentCurrency.USD,
97
80
  texturePath: 'items/character_customization.png',
98
81
  textureAtlas: 'items',
99
82
  textureKey: 'items/character_customization.png',
100
- price: 14.99,
101
- type: ItemType.Other,
102
- subType: ItemSubType.Other,
103
- attack: 0,
104
- defense: 0,
105
- weight: 0,
83
+ type: PurchaseType.Item,
84
+ onPurchase: async () => {},
85
+ itemType: ItemType.Other,
86
+ itemSubType: ItemSubType.Other,
106
87
  rarity: ItemRarities.Rare,
107
- isEquipable: false,
88
+ weight: 0,
108
89
  isStackable: false,
109
- isUsable: true,
110
- isStorable: true,
111
- hasUseWith: false,
112
- isSolid: false,
113
- isTwoHanded: false,
114
- isItemContainer: false,
115
- layer: 1,
116
- allowedEquipSlotType: [],
117
90
  maxStackSize: 1,
118
- // Add metadata type and config
91
+ isUsable: true,
119
92
  metadataType: MetadataType.CharacterSkin,
120
93
  metadataConfig: {
121
94
  availableCharacters,
@@ -124,34 +97,24 @@ const characterSkinItems: IStoreItem[] = [
124
97
  },
125
98
  },
126
99
  {
127
- _id: 'skin-premium-character-pack',
128
100
  key: 'skin-premium-character-pack',
129
101
  name: 'Premium Character Skin Pack',
130
102
  description: 'A premium collection of exclusive character skins',
131
- fullDescription: 'This exclusive premium pack gives you access to rare and unique character skins to stand out from the crowd.',
103
+ price: 24.99,
104
+ currency: PaymentCurrency.USD,
132
105
  texturePath: 'items/premium_character_pack.png',
133
106
  textureAtlas: 'items',
134
107
  textureKey: 'items/premium_character_pack.png',
135
- price: 24.99,
136
- type: ItemType.Other,
137
- subType: ItemSubType.Other,
138
- attack: 0,
139
- defense: 0,
140
- weight: 0,
108
+ type: PurchaseType.Item,
109
+ onPurchase: async () => {},
110
+ itemType: ItemType.Other,
111
+ itemSubType: ItemSubType.Other,
141
112
  rarity: ItemRarities.Epic,
142
- isEquipable: false,
113
+ weight: 0,
143
114
  isStackable: false,
144
- isUsable: true,
145
- isStorable: true,
146
- hasUseWith: false,
147
- isSolid: false,
148
- isTwoHanded: false,
149
- isItemContainer: false,
150
- layer: 1,
151
- allowedEquipSlotType: [],
152
115
  maxStackSize: 1,
116
+ isUsable: true,
153
117
  requiredAccountType: [UserAccountTypes.PremiumSilver],
154
- // Add metadata type and config with the same character options
155
118
  metadataType: MetadataType.CharacterSkin,
156
119
  metadataConfig: {
157
120
  availableCharacters,
@@ -162,22 +125,19 @@ const characterSkinItems: IStoreItem[] = [
162
125
  ];
163
126
 
164
127
  // Create duplicated items once with unique keys
165
- const duplicatedItems: IStoreItem[] = [
128
+ const duplicatedItems: IProductBlueprint[] = [
166
129
  ...storeItems,
167
130
  ...characterSkinItems,
168
131
  ...storeItems.map((item, index) => ({
169
132
  ...item,
170
- _id: `copy1-${item.key}-${index}`,
171
133
  key: `copy1-${item.key}-${index}`,
172
134
  })),
173
135
  ...storeItems.map((item, index) => ({
174
136
  ...item,
175
- _id: `copy2-${item.key}-${index}`,
176
137
  key: `copy2-${item.key}-${index}`,
177
138
  })),
178
139
  ...storeItems.map((item, index) => ({
179
140
  ...item,
180
- _id: `copy3-${item.key}-${index}`,
181
141
  key: `copy3-${item.key}-${index}`,
182
142
  })),
183
143
  ];