@rpg-engine/long-bow 0.8.71 → 0.8.73

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 (34) hide show
  1. package/dist/components/Store/CartView.d.ts +7 -6
  2. package/dist/components/Store/Store.d.ts +2 -2
  3. package/dist/components/Store/StoreCharacterSkinRow.d.ts +3 -3
  4. package/dist/components/Store/StoreItemDetails.d.ts +3 -3
  5. package/dist/components/Store/StoreItemRow.d.ts +4 -3
  6. package/dist/components/Store/hooks/useStoreCart.d.ts +5 -3
  7. package/dist/components/Store/hooks/useStoreMetadata.d.ts +3 -3
  8. package/dist/components/Store/sections/StoreItemsSection.d.ts +3 -3
  9. package/dist/hooks/useCharacterSkinNavigation.d.ts +7 -0
  10. package/dist/hooks/usePackFiltering.d.ts +7 -0
  11. package/dist/hooks/useQuantityControl.d.ts +10 -0
  12. package/dist/hooks/useStoreFiltering.d.ts +11 -0
  13. package/dist/long-bow.cjs.development.js +264 -112
  14. package/dist/long-bow.cjs.development.js.map +1 -1
  15. package/dist/long-bow.cjs.production.min.js +1 -1
  16. package/dist/long-bow.cjs.production.min.js.map +1 -1
  17. package/dist/long-bow.esm.js +265 -113
  18. package/dist/long-bow.esm.js.map +1 -1
  19. package/package.json +2 -2
  20. package/src/components/Store/CartView.tsx +9 -6
  21. package/src/components/Store/Store.tsx +13 -21
  22. package/src/components/Store/StoreCharacterSkinRow.tsx +64 -46
  23. package/src/components/Store/StoreItemDetails.tsx +4 -4
  24. package/src/components/Store/StoreItemRow.tsx +64 -56
  25. package/src/components/Store/hooks/useStoreCart.ts +14 -9
  26. package/src/components/Store/hooks/useStoreMetadata.ts +5 -5
  27. package/src/components/Store/sections/StoreItemsSection.tsx +78 -27
  28. package/src/components/Store/sections/StorePacksSection.tsx +5 -10
  29. package/src/hooks/useCharacterSkinNavigation.ts +34 -0
  30. package/src/hooks/usePackFiltering.ts +20 -0
  31. package/src/hooks/useQuantityControl.ts +41 -0
  32. package/src/hooks/useStoreFiltering.ts +51 -0
  33. package/src/mocks/dailyTasks.mocks.ts +6 -6
  34. package/src/stories/Features/store/Store.stories.tsx +59 -72
@@ -1,13 +1,25 @@
1
- import { IStoreItem, MetadataType, UserAccountTypes } from '@rpg-engine/shared';
2
- import React, { useState } from 'react';
1
+ import {
2
+ IProductBlueprint,
3
+ MetadataType,
4
+ UserAccountTypes,
5
+ ItemType,
6
+ } from '@rpg-engine/shared';
7
+ import React from 'react';
8
+ import styled from 'styled-components';
3
9
  import { ScrollableContent } from '../../shared/ScrollableContent/ScrollableContent';
4
10
  import { StoreCharacterSkinRow } from '../StoreCharacterSkinRow';
5
11
  import { StoreItemRow } from '../StoreItemRow';
6
-
12
+ import { Dropdown } from '../../Dropdown';
13
+ import { SearchBar } from '../../shared/SearchBar/SearchBar';
14
+ import { useStoreFiltering } from '../../../hooks/useStoreFiltering';
7
15
 
8
16
  interface IStoreItemsSectionProps {
9
- items: IStoreItem[];
10
- onAddToCart: (item: IStoreItem, quantity: number, metadata?: Record<string, any>) => void;
17
+ items: IProductBlueprint[];
18
+ onAddToCart: (
19
+ item: IProductBlueprint,
20
+ quantity: number,
21
+ metadata?: Record<string, any>
22
+ ) => void;
11
23
  atlasJSON: Record<string, any>;
12
24
  atlasIMG: string;
13
25
  userAccountType?: UserAccountTypes;
@@ -22,18 +34,20 @@ export const StoreItemsSection: React.FC<IStoreItemsSectionProps> = ({
22
34
  userAccountType,
23
35
  textInputItemKeys = [],
24
36
  }) => {
25
- const [searchQuery, setSearchQuery] = useState('');
26
-
27
- const filteredItems = items.filter(item =>
28
- item.name.toLowerCase().includes(searchQuery.toLowerCase())
29
- );
37
+ const {
38
+ searchQuery,
39
+ setSearchQuery,
40
+ setSelectedCategory,
41
+ categoryOptions,
42
+ filteredItems,
43
+ } = useStoreFiltering(items);
30
44
 
31
- const renderStoreItem = (item: IStoreItem) => {
45
+ const renderStoreItem = (item: IProductBlueprint) => {
32
46
  // Prefer a specialized character skin row when needed
33
47
  if (item.metadataType === MetadataType.CharacterSkin) {
34
48
  return (
35
49
  <StoreCharacterSkinRow
36
- key={item._id}
50
+ key={item.key}
37
51
  item={item}
38
52
  atlasJSON={atlasJSON}
39
53
  atlasIMG={atlasIMG}
@@ -43,10 +57,10 @@ export const StoreItemsSection: React.FC<IStoreItemsSectionProps> = ({
43
57
  );
44
58
  }
45
59
  // Render text input row when configured for this item key
46
- if (textInputItemKeys.includes(item.key) || textInputItemKeys.includes(item._id)) {
60
+ if (textInputItemKeys.includes(item.key)) {
47
61
  return (
48
62
  <StoreItemRow
49
- key={item._id}
63
+ key={item.key}
50
64
  item={item}
51
65
  atlasJSON={atlasJSON}
52
66
  atlasIMG={atlasIMG}
@@ -59,7 +73,7 @@ export const StoreItemsSection: React.FC<IStoreItemsSectionProps> = ({
59
73
  // Fallback to standard arrow-based row
60
74
  return (
61
75
  <StoreItemRow
62
- key={item._id}
76
+ key={item.key}
63
77
  item={item}
64
78
  atlasJSON={atlasJSON}
65
79
  atlasIMG={atlasIMG}
@@ -70,17 +84,54 @@ export const StoreItemsSection: React.FC<IStoreItemsSectionProps> = ({
70
84
  };
71
85
 
72
86
  return (
73
- <ScrollableContent
74
- items={filteredItems}
75
- renderItem={renderStoreItem}
76
- emptyMessage="No items available."
77
- searchOptions={{
78
- value: searchQuery,
79
- onChange: setSearchQuery,
80
- placeholder: 'Search items...',
81
- }}
82
- layout="list"
83
- maxHeight="400px"
84
- />
87
+ <StoreContainer>
88
+ <SearchHeader>
89
+ <SearchBarContainer>
90
+ <SearchBar
91
+ value={searchQuery}
92
+ onChange={setSearchQuery}
93
+ placeholder="Search items..."
94
+ />
95
+ </SearchBarContainer>
96
+ <DropdownContainer>
97
+ <Dropdown
98
+ options={categoryOptions}
99
+ onChange={value => setSelectedCategory(value as ItemType | 'all')}
100
+ width="100%"
101
+ />
102
+ </DropdownContainer>
103
+ </SearchHeader>
104
+
105
+ <ScrollableContent
106
+ items={filteredItems}
107
+ renderItem={renderStoreItem}
108
+ emptyMessage="No items match your filters."
109
+ layout="list"
110
+ maxHeight="350px"
111
+ />
112
+ </StoreContainer>
85
113
  );
86
114
  };
115
+
116
+ const StoreContainer = styled.div`
117
+ display: flex;
118
+ flex-direction: column;
119
+ height: 100%;
120
+ gap: 0.5rem;
121
+ `;
122
+
123
+ const SearchHeader = styled.div`
124
+ display: flex;
125
+ gap: 0.5rem;
126
+ align-items: center;
127
+ padding-top: 0.25rem;
128
+ `;
129
+
130
+ const SearchBarContainer = styled.div`
131
+ flex: 0.75;
132
+ `;
133
+
134
+ const DropdownContainer = styled.div`
135
+ flex: 0.25;
136
+ min-width: 140px;
137
+ `;
@@ -1,10 +1,11 @@
1
1
  import { IItemPack } from '@rpg-engine/shared';
2
- import React, { useCallback, useMemo, useState } from 'react';
2
+ import React, { useCallback } from 'react';
3
3
  import { FaCartPlus } from 'react-icons/fa';
4
4
  import styled from 'styled-components';
5
5
  import { CTAButton } from '../../shared/CTAButton/CTAButton';
6
6
  import { ScrollableContent } from '../../shared/ScrollableContent/ScrollableContent';
7
7
  import { ShoppingCardHorizontal } from '../../shared/ShoppingCart/CartCardHorizontal';
8
+ import { usePackFiltering } from '../../../hooks/usePackFiltering';
8
9
 
9
10
  interface IStorePacksSectionProps {
10
11
  packs: IItemPack[];
@@ -17,7 +18,9 @@ export const StorePacksSection: React.FC<IStorePacksSectionProps> = ({
17
18
  onAddToCart,
18
19
  onSelectPack,
19
20
  }) => {
20
- const [searchQuery, setSearchQuery] = useState('');
21
+ const { searchQuery, setSearchQuery, filteredPacks } = usePackFiltering(
22
+ packs
23
+ );
21
24
 
22
25
  const renderPackFooter = useCallback(
23
26
  (pack: IItemPack) => (
@@ -50,14 +53,6 @@ export const StorePacksSection: React.FC<IStorePacksSectionProps> = ({
50
53
  [onSelectPack, renderPackFooter]
51
54
  );
52
55
 
53
- const filteredPacks = useMemo(
54
- () =>
55
- packs.filter(pack =>
56
- pack.title.toLowerCase().includes(searchQuery.toLowerCase())
57
- ),
58
- [packs, searchQuery]
59
- );
60
-
61
56
  return (
62
57
  <ScrollableContent
63
58
  items={filteredPacks}
@@ -0,0 +1,34 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { ICharacterProps } from '../components/Character/CharacterSelection';
3
+
4
+ export const useCharacterSkinNavigation = (
5
+ availableCharacters: ICharacterProps[],
6
+ itemKey: string
7
+ ) => {
8
+ const [currentIndex, setCurrentIndex] = useState(0);
9
+
10
+ useEffect(() => {
11
+ setCurrentIndex(0);
12
+ }, [itemKey]);
13
+
14
+ const handlePreviousSkin = () => {
15
+ setCurrentIndex(prevIndex =>
16
+ prevIndex === 0 ? availableCharacters.length - 1 : prevIndex - 1
17
+ );
18
+ };
19
+
20
+ const handleNextSkin = () => {
21
+ setCurrentIndex(prevIndex =>
22
+ prevIndex === availableCharacters.length - 1 ? 0 : prevIndex + 1
23
+ );
24
+ };
25
+
26
+ const currentCharacter = availableCharacters[currentIndex];
27
+
28
+ return {
29
+ currentIndex,
30
+ currentCharacter,
31
+ handlePreviousSkin,
32
+ handleNextSkin,
33
+ };
34
+ };
@@ -0,0 +1,20 @@
1
+ import { useMemo, useState } from 'react';
2
+ import { IItemPack } from '@rpg-engine/shared';
3
+
4
+ export const usePackFiltering = (packs: IItemPack[]) => {
5
+ const [searchQuery, setSearchQuery] = useState('');
6
+
7
+ const filteredPacks = useMemo(
8
+ () =>
9
+ packs.filter(pack =>
10
+ pack.title.toLowerCase().includes(searchQuery.toLowerCase())
11
+ ),
12
+ [packs, searchQuery]
13
+ );
14
+
15
+ return {
16
+ searchQuery,
17
+ setSearchQuery,
18
+ filteredPacks,
19
+ };
20
+ };
@@ -0,0 +1,41 @@
1
+ import { useState } from 'react';
2
+
3
+ export const useQuantityControl = (
4
+ initialQuantity: number = 1,
5
+ min: number = 1,
6
+ max: number = 99
7
+ ) => {
8
+ const [quantity, setQuantity] = useState(initialQuantity);
9
+
10
+ const handleQuantityChange = (e: React.ChangeEvent<HTMLInputElement>) => {
11
+ const value = parseInt(e.target.value) || min;
12
+ setQuantity(Math.min(Math.max(min, value), max));
13
+ };
14
+
15
+ const handleBlur = () => {
16
+ if (quantity < min) setQuantity(min);
17
+ if (quantity > max) setQuantity(max);
18
+ };
19
+
20
+ const incrementQuantity = () => {
21
+ setQuantity(prev => Math.min(prev + 1, max));
22
+ };
23
+
24
+ const decrementQuantity = () => {
25
+ setQuantity(prev => Math.max(min, prev - 1));
26
+ };
27
+
28
+ const resetQuantity = () => {
29
+ setQuantity(initialQuantity);
30
+ };
31
+
32
+ return {
33
+ quantity,
34
+ setQuantity,
35
+ handleQuantityChange,
36
+ handleBlur,
37
+ incrementQuantity,
38
+ decrementQuantity,
39
+ resetQuantity,
40
+ };
41
+ };
@@ -0,0 +1,51 @@
1
+ import { useMemo, useState } from 'react';
2
+ import { IProductBlueprint, ItemType } from '@rpg-engine/shared';
3
+ import { IOptionsProps } from '../components/Dropdown';
4
+
5
+ export const useStoreFiltering = (items: IProductBlueprint[]) => {
6
+ const [searchQuery, setSearchQuery] = useState('');
7
+ const [selectedCategory, setSelectedCategory] = useState<ItemType | 'all'>(
8
+ 'all'
9
+ );
10
+
11
+ const categoryOptions: IOptionsProps[] = useMemo(() => {
12
+ const uniqueCategories = Array.from(
13
+ new Set(items.map(item => item.itemType))
14
+ );
15
+ const allCategories = ['all', ...uniqueCategories] as (ItemType | 'all')[];
16
+ return allCategories.map((category, index) => ({
17
+ id: index,
18
+ value: category,
19
+ option: category === 'all' ? 'All' : category,
20
+ }));
21
+ }, [items]);
22
+
23
+ const filteredItems = useMemo(() => {
24
+ return items
25
+ .filter(item => {
26
+ const matchesSearch = item.name
27
+ .toLowerCase()
28
+ .includes(searchQuery.toLowerCase());
29
+ const matchesCategory =
30
+ selectedCategory === 'all' || item.itemType === selectedCategory;
31
+ return matchesSearch && matchesCategory;
32
+ })
33
+ .sort((a, b) => {
34
+ const aHighlighted = a.store?.isHighlighted || false;
35
+ const bHighlighted = b.store?.isHighlighted || false;
36
+
37
+ if (aHighlighted && !bHighlighted) return -1;
38
+ if (!aHighlighted && bHighlighted) return 1;
39
+ return 0;
40
+ });
41
+ }, [items, searchQuery, selectedCategory]);
42
+
43
+ return {
44
+ searchQuery,
45
+ setSearchQuery,
46
+ selectedCategory,
47
+ setSelectedCategory,
48
+ categoryOptions,
49
+ filteredItems,
50
+ };
51
+ };
@@ -1,8 +1,8 @@
1
- import { DailyTaskBlueprintMapVisitRegular, DailyTaskChallengeKillMobsBlueprint, DailyTaskRegularCraftFoodBlueprint, DailyTaskRegularCraftPotionsBlueprint, DailyTaskRegularKillMobsBlueprint, ICharacterDailyTask, RewardType, TaskDifficulty, TaskStatus, TaskType } from "@rpg-engine/shared";
1
+ import { ICharacterDailyTask, RewardType, TaskDifficulty, TaskStatus, TaskType } from "@rpg-engine/shared";
2
2
 
3
3
  export const mockTasks: ICharacterDailyTask[] = [
4
4
  {
5
- key: DailyTaskRegularKillMobsBlueprint.HuntRats,
5
+ key: 'hunt-rats',
6
6
  name: 'Kill Rats',
7
7
  description: 'Eliminate 5 rats in the forest',
8
8
  type: TaskType.KillMobs,
@@ -43,7 +43,7 @@ export const mockTasks: ICharacterDailyTask[] = [
43
43
  tier: 0
44
44
  },
45
45
  {
46
- key: DailyTaskRegularCraftFoodBlueprint.CraftCheese,
46
+ key: 'craft-cheese',
47
47
  name: 'Gather Resources',
48
48
  description: 'Collect 10 pieces of wood',
49
49
  type: TaskType.CollectItems,
@@ -84,7 +84,7 @@ export const mockTasks: ICharacterDailyTask[] = [
84
84
  tier: 0
85
85
  },
86
86
  {
87
- key: DailyTaskChallengeKillMobsBlueprint.HuntEloraTheQueen,
87
+ key: 'hunt-elora-the-queen',
88
88
  name: 'Slay the Dragon',
89
89
  description: 'Defeat the Queen Dragon',
90
90
  type: TaskType.KillMobs,
@@ -125,7 +125,7 @@ export const mockTasks: ICharacterDailyTask[] = [
125
125
  tier: 0
126
126
  },
127
127
  {
128
- key: DailyTaskBlueprintMapVisitRegular.ExploreVillage,
128
+ key: 'explore-village',
129
129
  name: "Village Ilya Explorer",
130
130
  description: "Visit the key locations in the ilya village",
131
131
  difficulty: TaskDifficulty.Regular,
@@ -167,7 +167,7 @@ export const mockTasks: ICharacterDailyTask[] = [
167
167
  tier: 0
168
168
  },
169
169
  {
170
- key: DailyTaskRegularCraftPotionsBlueprint.CraftLifePotion,
170
+ key: 'craft-life-potion',
171
171
  name: 'Craft Potions',
172
172
  description: 'Craft 5 health potions for the local alchemist',
173
173
  type: TaskType.CraftItems,
@@ -1,4 +1,4 @@
1
- import { IItemPack, IPurchase, IStoreItem, ItemRarities, ItemSubType, ItemType, MetadataType, UserAccountTypes } from '@rpg-engine/shared';
1
+ import { IItemPack, IProductBlueprint, IPurchase, ItemRarities, ItemSubType, ItemType, MetadataType, PaymentCurrency, PurchaseType, UserAccountTypes } 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,42 +29,26 @@ 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] : [],
51
+
68
52
  }));
69
53
 
70
54
  // Sample character skins
@@ -86,72 +70,77 @@ const availableCharacters = [
86
70
  },
87
71
  ];
88
72
 
73
+ // Create character name change item
74
+ const characterNameChangeItem: IProductBlueprint = {
75
+ key: 'character-name-change',
76
+ name: 'Character Name Change',
77
+ description: 'Change your character\'s name to something new',
78
+ price: 9.99,
79
+ currency: PaymentCurrency.USD,
80
+ texturePath: 'items/character_customization.png',
81
+ textureAtlas: 'items',
82
+ textureKey: 'items/character_customization.png',
83
+ type: PurchaseType.Item,
84
+ onPurchase: async () => { },
85
+ itemType: ItemType.Other,
86
+ itemSubType: ItemSubType.Other,
87
+ rarity: ItemRarities.Common,
88
+ weight: 0,
89
+ isStackable: false,
90
+ maxStackSize: 1,
91
+ isUsable: true,
92
+ inputPlaceholder: 'Enter new character name',
93
+ };
94
+
89
95
  // Create character skin items
90
- const characterSkinItems: IStoreItem[] = [
96
+ const characterSkinItems: IProductBlueprint[] = [
91
97
  {
92
- _id: 'skin-character-customization',
93
98
  key: 'skin-character-customization',
94
99
  name: 'Character Skin Customization',
95
100
  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.',
101
+ price: 14.99,
102
+ currency: PaymentCurrency.USD,
97
103
  texturePath: 'items/character_customization.png',
98
104
  textureAtlas: 'items',
99
105
  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,
106
+ type: PurchaseType.Item,
107
+ onPurchase: async () => { },
108
+ itemType: ItemType.Other,
109
+ itemSubType: ItemSubType.Other,
106
110
  rarity: ItemRarities.Rare,
107
- isEquipable: false,
111
+ weight: 0,
108
112
  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
113
  maxStackSize: 1,
118
- // Add metadata type and config
114
+ isUsable: true,
119
115
  metadataType: MetadataType.CharacterSkin,
120
116
  metadataConfig: {
121
117
  availableCharacters,
122
118
  atlasJSON: entitiesAtlasJSON,
123
119
  atlasIMG: entitiesAtlasIMG,
124
120
  },
121
+ store: {
122
+ isHighlighted: true, // Highlight the first character skin item
123
+ },
125
124
  },
126
125
  {
127
- _id: 'skin-premium-character-pack',
128
126
  key: 'skin-premium-character-pack',
129
127
  name: 'Premium Character Skin Pack',
130
128
  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.',
129
+ price: 24.99,
130
+ currency: PaymentCurrency.USD,
132
131
  texturePath: 'items/premium_character_pack.png',
133
132
  textureAtlas: 'items',
134
133
  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,
134
+ type: PurchaseType.Item,
135
+ onPurchase: async () => { },
136
+ itemType: ItemType.Other,
137
+ itemSubType: ItemSubType.Other,
141
138
  rarity: ItemRarities.Epic,
142
- isEquipable: false,
139
+ weight: 0,
143
140
  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
141
  maxStackSize: 1,
142
+ isUsable: true,
153
143
  requiredAccountType: [UserAccountTypes.PremiumSilver],
154
- // Add metadata type and config with the same character options
155
144
  metadataType: MetadataType.CharacterSkin,
156
145
  metadataConfig: {
157
146
  availableCharacters,
@@ -162,22 +151,20 @@ const characterSkinItems: IStoreItem[] = [
162
151
  ];
163
152
 
164
153
  // Create duplicated items once with unique keys
165
- const duplicatedItems: IStoreItem[] = [
154
+ const duplicatedItems: IProductBlueprint[] = [
166
155
  ...storeItems,
156
+ characterNameChangeItem,
167
157
  ...characterSkinItems,
168
158
  ...storeItems.map((item, index) => ({
169
159
  ...item,
170
- _id: `copy1-${item.key}-${index}`,
171
160
  key: `copy1-${item.key}-${index}`,
172
161
  })),
173
162
  ...storeItems.map((item, index) => ({
174
163
  ...item,
175
- _id: `copy2-${item.key}-${index}`,
176
164
  key: `copy2-${item.key}-${index}`,
177
165
  })),
178
166
  ...storeItems.map((item, index) => ({
179
167
  ...item,
180
- _id: `copy3-${item.key}-${index}`,
181
168
  key: `copy3-${item.key}-${index}`,
182
169
  })),
183
170
  ];
@@ -200,20 +187,20 @@ const mockPacks: IItemPack[] = [
200
187
  image: customSkinImage,
201
188
  },
202
189
 
203
- {
190
+ {
204
191
  key: 'ultimate-pack',
205
192
  title: '👑 Ultimate Premium Pack',
206
193
  description: 'The most exclusive collection of items, effects, and perks. Includes everything from previous tiers plus legendary items!',
207
194
  priceUSD: 99.99,
208
195
  image: customSkinImage,
209
196
  },
210
- {
197
+ {
211
198
  key: 'gold-pack',
212
199
  title: '🥇 Gold Premium Pack',
213
200
  description: 'The ultimate premium experience with rare items, unique effects, and exclusive content. For true champions!',
214
201
  priceUSD: 49.99,
215
202
  image: customSkinImage,
216
- },
203
+ },
217
204
  {
218
205
  key: 'silver-pack',
219
206
  title: '🥈 Silver Premium Pack',
@@ -249,7 +236,7 @@ export const Default: Story = {
249
236
  hidePremiumTab={true}
250
237
  tabOrder={['items', 'packs']}
251
238
  defaultActiveTab="items"
252
- textInputItemKeys={['original-greater-life-potion-2', 'original-angelic-sword-1']}
239
+ textInputItemKeys={['original-greater-life-potion-2', 'original-angelic-sword-1', 'character-name-change']}
253
240
  />
254
241
  ),
255
242
  };