@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
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.73",
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.14",
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,17 +1,26 @@
1
- import { IStoreItem, MetadataType, UserAccountTypes } from '@rpg-engine/shared';
2
- import React, { useEffect, useState } from 'react';
1
+ import {
2
+ IProductBlueprint,
3
+ MetadataType,
4
+ UserAccountTypes,
5
+ } from '@rpg-engine/shared';
6
+ import React from 'react';
3
7
  import { FaCartPlus } from 'react-icons/fa';
4
8
  import styled from 'styled-components';
5
9
  import { SelectArrow } from '../Arrow/SelectArrow';
6
10
  import { ICharacterProps } from '../Character/CharacterSelection';
7
11
  import { CTAButton } from '../shared/CTAButton/CTAButton';
8
12
  import { SpriteFromAtlas } from '../shared/SpriteFromAtlas';
13
+ import { useCharacterSkinNavigation } from '../../hooks/useCharacterSkinNavigation';
9
14
 
10
15
  interface IStoreCharacterSkinRowProps {
11
- item: IStoreItem;
16
+ item: IProductBlueprint;
12
17
  atlasJSON: Record<string, any>;
13
18
  atlasIMG: string;
14
- onAddToCart: (item: IStoreItem, quantity: number, metadata?: Record<string, any>) => void;
19
+ onAddToCart: (
20
+ item: IProductBlueprint,
21
+ quantity: number,
22
+ metadata?: Record<string, any>
23
+ ) => void;
15
24
  userAccountType: UserAccountTypes;
16
25
  }
17
26
 
@@ -22,33 +31,21 @@ export const StoreCharacterSkinRow: React.FC<IStoreCharacterSkinRowProps> = ({
22
31
  onAddToCart,
23
32
  userAccountType,
24
33
  }) => {
25
- const [currentIndex, setCurrentIndex] = useState(0);
26
-
27
34
  // Get available characters from metadata
28
- const availableCharacters: ICharacterProps[] =
29
- item.metadataType === MetadataType.CharacterSkin &&
30
- item.metadataConfig?.availableCharacters || [];
31
-
35
+ const availableCharacters: ICharacterProps[] =
36
+ (item.metadataType === MetadataType.CharacterSkin &&
37
+ item.metadataConfig?.availableCharacters) ||
38
+ [];
39
+
32
40
  // Get the active character entity atlas info
33
41
  const entityAtlasJSON = item.metadataConfig?.atlasJSON;
34
42
  const entityAtlasIMG = item.metadataConfig?.atlasIMG;
35
-
36
- // Effect to reset currentIndex when switching items
37
- useEffect(() => {
38
- setCurrentIndex(0);
39
- }, [item._id]);
40
-
41
- const handlePreviousSkin = () => {
42
- setCurrentIndex((prevIndex) =>
43
- prevIndex === 0 ? availableCharacters.length - 1 : prevIndex - 1
44
- );
45
- };
46
43
 
47
- const handleNextSkin = () => {
48
- setCurrentIndex((prevIndex) =>
49
- prevIndex === availableCharacters.length - 1 ? 0 : prevIndex + 1
50
- );
51
- };
44
+ const {
45
+ currentCharacter,
46
+ handlePreviousSkin,
47
+ handleNextSkin,
48
+ } = useCharacterSkinNavigation(availableCharacters, item.key);
52
49
 
53
50
  const hasRequiredAccount =
54
51
  !item.requiredAccountType?.length ||
@@ -56,26 +53,24 @@ export const StoreCharacterSkinRow: React.FC<IStoreCharacterSkinRowProps> = ({
56
53
 
57
54
  const handleAddToCart = () => {
58
55
  if (!hasRequiredAccount) return;
59
-
56
+
60
57
  // Always use a quantity of 1
61
58
  if (availableCharacters.length > 0 && currentCharacter) {
62
- onAddToCart(item, 1, {
59
+ onAddToCart(item, 1, {
63
60
  selectedSkinName: currentCharacter.name,
64
- selectedSkinTextureKey: currentCharacter.textureKey
61
+ selectedSkinTextureKey: currentCharacter.textureKey,
65
62
  });
66
63
  } else {
67
64
  onAddToCart(item, 1);
68
65
  }
69
66
  };
70
-
67
+
71
68
  const getSpriteKey = (textureKey: string) => {
72
69
  return textureKey + '/down/standing/0.png';
73
70
  };
74
-
75
- const currentCharacter = availableCharacters[currentIndex];
76
71
 
77
72
  return (
78
- <ItemWrapper>
73
+ <ItemWrapper $isHighlighted={item.store?.isHighlighted || false}>
79
74
  <ItemIconContainer>
80
75
  {entityAtlasJSON && entityAtlasIMG && currentCharacter ? (
81
76
  <SpriteFromAtlas
@@ -87,7 +82,7 @@ export const StoreCharacterSkinRow: React.FC<IStoreCharacterSkinRowProps> = ({
87
82
  imgScale={2}
88
83
  centered
89
84
  />
90
- ) : (
85
+ ) : item.texturePath ? (
91
86
  <SpriteFromAtlas
92
87
  atlasJSON={atlasJSON}
93
88
  atlasIMG={atlasIMG}
@@ -97,19 +92,29 @@ export const StoreCharacterSkinRow: React.FC<IStoreCharacterSkinRowProps> = ({
97
92
  imgScale={2}
98
93
  centered
99
94
  />
95
+ ) : (
96
+ <DefaultIcon>👤</DefaultIcon>
100
97
  )}
101
98
  </ItemIconContainer>
102
99
 
103
100
  <ItemDetails>
104
101
  <Header>
105
- <ItemName>{item.name}</ItemName>
102
+ <ItemName>{item.name}</ItemName>
106
103
  </Header>
107
104
  {availableCharacters.length > 0 && currentCharacter && (
108
105
  <SelectedSkinNav>
109
106
  <SelectedSkin>Selected:</SelectedSkin>
110
- <SkinNavArrow direction="left" onPointerDown={handlePreviousSkin} size={24} />
107
+ <SkinNavArrow
108
+ direction="left"
109
+ onPointerDown={handlePreviousSkin}
110
+ size={24}
111
+ />
111
112
  <SelectedSkin>{currentCharacter.name}</SelectedSkin>
112
- <SkinNavArrow direction="right" onPointerDown={handleNextSkin} size={24} />
113
+ <SkinNavArrow
114
+ direction="right"
115
+ onPointerDown={handleNextSkin}
116
+ size={24}
117
+ />
113
118
  </SelectedSkinNav>
114
119
  )}
115
120
  <ItemPrice>${item.price}</ItemPrice>
@@ -126,12 +131,16 @@ export const StoreCharacterSkinRow: React.FC<IStoreCharacterSkinRowProps> = ({
126
131
  );
127
132
  };
128
133
 
129
- const ItemWrapper = styled.div`
134
+ const ItemWrapper = styled.div<{ $isHighlighted: boolean }>`
130
135
  display: flex;
131
136
  align-items: center;
132
- gap: 1rem;
133
- padding: 1rem;
137
+ gap: 0.75rem;
138
+ padding: 0.5rem 1rem;
134
139
  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
140
+ background: ${props =>
141
+ props.$isHighlighted ? 'rgba(255, 215, 0, 0.1)' : 'transparent'};
142
+ border-left: ${props =>
143
+ props.$isHighlighted ? '3px solid #ffd700' : '3px solid transparent'};
135
144
 
136
145
  &:last-child {
137
146
  border-bottom: none;
@@ -152,31 +161,31 @@ const ItemDetails = styled.div`
152
161
  flex: 1;
153
162
  display: flex;
154
163
  flex-direction: column;
155
- gap: 0.5rem;
164
+ gap: 0.25rem;
156
165
  `;
157
166
 
158
167
  const ItemName = styled.div`
159
168
  font-family: 'Press Start 2P', cursive;
160
- font-size: 0.875rem;
169
+ font-size: 0.75rem;
161
170
  color: #ffffff;
162
171
  `;
163
172
 
164
173
  const SelectedSkin = styled.div`
165
174
  font-family: 'Press Start 2P', cursive;
166
- font-size: 0.65rem;
175
+ font-size: 0.5rem;
167
176
  color: #fef08a;
168
177
  `;
169
178
 
170
179
  const ItemPrice = styled.div`
171
180
  font-family: 'Press Start 2P', cursive;
172
- font-size: 0.75rem;
181
+ font-size: 0.625rem;
173
182
  color: #fef08a;
174
183
  `;
175
184
 
176
185
  const Controls = styled.div`
177
186
  display: flex;
178
187
  align-items: center;
179
- gap: 1rem;
188
+ gap: 0.5rem;
180
189
  min-width: fit-content;
181
190
  `;
182
191
 
@@ -195,4 +204,13 @@ const SelectedSkinNav = styled.div`
195
204
  display: flex;
196
205
  align-items: center;
197
206
  gap: 0.5rem;
198
- `;
207
+ `;
208
+
209
+ const DefaultIcon = styled.div`
210
+ font-size: 1.5rem;
211
+ display: flex;
212
+ align-items: center;
213
+ justify-content: center;
214
+ width: 32px;
215
+ height: 32px;
216
+ `;
@@ -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,18 +1,24 @@
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';
5
5
  import { SelectArrow } from '../Arrow/SelectArrow';
6
6
  import { CTAButton } from '../shared/CTAButton/CTAButton';
7
7
  import { SpriteFromAtlas } from '../shared/SpriteFromAtlas';
8
+ import { useQuantityControl } from '../../hooks/useQuantityControl';
8
9
 
9
10
  interface IStoreItemRowProps {
10
- item: IStoreItem;
11
+ item: IProductBlueprint;
11
12
  atlasJSON: Record<string, any>;
12
13
  atlasIMG: string;
13
- onAddToCart: (item: IStoreItem, quantity: number, metadata?: Record<string, any>) => void;
14
+ onAddToCart: (
15
+ item: IProductBlueprint,
16
+ quantity: number,
17
+ metadata?: Record<string, any>
18
+ ) => void;
14
19
  userAccountType: UserAccountTypes;
15
20
  showTextInput?: boolean;
21
+ textInputPlaceholder?: string;
16
22
  }
17
23
 
18
24
  export const StoreItemRow: React.FC<IStoreItemRowProps> = ({
@@ -22,27 +28,17 @@ export const StoreItemRow: React.FC<IStoreItemRowProps> = ({
22
28
  onAddToCart,
23
29
  userAccountType,
24
30
  showTextInput = false,
31
+ textInputPlaceholder = item.inputPlaceholder,
25
32
  }) => {
26
- const [quantity, setQuantity] = useState(1);
27
33
  const [textInputValue, setTextInputValue] = useState('');
28
-
29
- const handleQuantityChange = (e: React.ChangeEvent<HTMLInputElement>) => {
30
- const value = parseInt(e.target.value) || 1;
31
- setQuantity(Math.min(Math.max(1, value), 99));
32
- };
33
-
34
- const handleBlur = () => {
35
- if (quantity < 1) setQuantity(1);
36
- if (quantity > 99) setQuantity(99);
37
- };
38
-
39
- const incrementQuantity = () => {
40
- setQuantity(prev => Math.min(prev + 1, 99));
41
- };
42
-
43
- const decrementQuantity = () => {
44
- setQuantity(prev => Math.max(1, prev - 1));
45
- };
34
+ const {
35
+ quantity,
36
+ handleQuantityChange,
37
+ handleBlur,
38
+ incrementQuantity,
39
+ decrementQuantity,
40
+ resetQuantity,
41
+ } = useQuantityControl();
46
42
 
47
43
  const hasRequiredAccount =
48
44
  !item.requiredAccountType?.length ||
@@ -54,13 +50,13 @@ export const StoreItemRow: React.FC<IStoreItemRowProps> = ({
54
50
  onAddToCart(item, 1, { inputValue: textInputValue });
55
51
  setTextInputValue('');
56
52
  } else {
57
- onAddToCart(item, quantity);
58
- setQuantity(1);
53
+ onAddToCart(item, quantity);
54
+ resetQuantity();
59
55
  }
60
56
  };
61
57
 
62
58
  return (
63
- <ItemWrapper>
59
+ <ItemWrapper $isHighlighted={item.store?.isHighlighted || false}>
64
60
  <ItemIconContainer>
65
61
  <SpriteFromAtlas
66
62
  atlasJSON={atlasJSON}
@@ -76,6 +72,7 @@ export const StoreItemRow: React.FC<IStoreItemRowProps> = ({
76
72
  <ItemDetails>
77
73
  <ItemName>{item.name}</ItemName>
78
74
  <ItemPrice>${item.price}</ItemPrice>
75
+ <ItemDescription>{item.description}</ItemDescription>
79
76
  </ItemDetails>
80
77
 
81
78
  <Controls>
@@ -84,34 +81,34 @@ export const StoreItemRow: React.FC<IStoreItemRowProps> = ({
84
81
  <TextInput
85
82
  type="text"
86
83
  value={textInputValue}
87
- placeholder="Enter value"
84
+ placeholder={textInputPlaceholder}
88
85
  onChange={e => setTextInputValue(e.target.value)}
89
86
  className="rpgui-input"
90
87
  />
91
88
  ) : item.isStackable ? (
92
- <ArrowsContainer>
93
- <SelectArrow
94
- direction="left"
95
- onPointerDown={decrementQuantity}
96
- size={24}
97
- />
98
-
99
- <QuantityInput
100
- type="number"
101
- value={quantity}
102
- onChange={handleQuantityChange}
103
- onBlur={handleBlur}
104
- min={1}
105
- max={99}
106
- className="rpgui-input"
107
- />
108
-
109
- <SelectArrow
110
- direction="right"
111
- onPointerDown={incrementQuantity}
112
- size={24}
113
- />
114
- </ArrowsContainer>
89
+ <ArrowsContainer>
90
+ <SelectArrow
91
+ direction="left"
92
+ onPointerDown={decrementQuantity}
93
+ size={24}
94
+ />
95
+
96
+ <QuantityInput
97
+ type="number"
98
+ value={quantity}
99
+ onChange={handleQuantityChange}
100
+ onBlur={handleBlur}
101
+ min={1}
102
+ max={99}
103
+ className="rpgui-input"
104
+ />
105
+
106
+ <SelectArrow
107
+ direction="right"
108
+ onPointerDown={incrementQuantity}
109
+ size={24}
110
+ />
111
+ </ArrowsContainer>
115
112
  ) : null}
116
113
 
117
114
  <CTAButton
@@ -125,12 +122,16 @@ export const StoreItemRow: React.FC<IStoreItemRowProps> = ({
125
122
  );
126
123
  };
127
124
 
128
- const ItemWrapper = styled.div`
125
+ const ItemWrapper = styled.div<{ $isHighlighted: boolean }>`
129
126
  display: flex;
130
127
  align-items: center;
131
- gap: 1rem;
132
- padding: 1rem;
128
+ gap: 0.75rem;
129
+ padding: 0.5rem 1rem;
133
130
  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
131
+ background: ${props =>
132
+ props.$isHighlighted ? 'rgba(255, 215, 0, 0.1)' : 'transparent'};
133
+ border-left: ${props =>
134
+ props.$isHighlighted ? '3px solid #ffd700' : '3px solid transparent'};
134
135
 
135
136
  &:last-child {
136
137
  border-bottom: none;
@@ -151,25 +152,32 @@ const ItemDetails = styled.div`
151
152
  flex: 1;
152
153
  display: flex;
153
154
  flex-direction: column;
154
- gap: 0.5rem;
155
+ gap: 0.25rem;
155
156
  `;
156
157
 
157
158
  const ItemName = styled.div`
158
159
  font-family: 'Press Start 2P', cursive;
159
- font-size: 0.875rem;
160
+ font-size: 0.75rem;
160
161
  color: #ffffff;
161
162
  `;
162
163
 
163
164
  const ItemPrice = styled.div`
164
165
  font-family: 'Press Start 2P', cursive;
165
- font-size: 0.75rem;
166
+ font-size: 0.625rem;
166
167
  color: #fef08a;
167
168
  `;
168
169
 
170
+ const ItemDescription = styled.div`
171
+ font-family: 'Press Start 2P', cursive;
172
+ font-size: 0.625rem;
173
+ color: rgba(255, 255, 255, 0.7);
174
+ line-height: 1.4;
175
+ `;
176
+
169
177
  const Controls = styled.div`
170
178
  display: flex;
171
179
  align-items: center;
172
- gap: 1rem;
180
+ gap: 0.5rem;
173
181
  min-width: fit-content;
174
182
  `;
175
183
 
@@ -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
  }