@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.
- package/dist/components/Store/CartView.d.ts +7 -6
- package/dist/components/Store/Store.d.ts +2 -2
- package/dist/components/Store/StoreCharacterSkinRow.d.ts +3 -3
- package/dist/components/Store/StoreItemDetails.d.ts +3 -3
- package/dist/components/Store/StoreItemRow.d.ts +4 -3
- package/dist/components/Store/hooks/useStoreCart.d.ts +5 -3
- package/dist/components/Store/hooks/useStoreMetadata.d.ts +3 -3
- package/dist/components/Store/sections/StoreItemsSection.d.ts +3 -3
- package/dist/hooks/useCharacterSkinNavigation.d.ts +7 -0
- package/dist/hooks/usePackFiltering.d.ts +7 -0
- package/dist/hooks/useQuantityControl.d.ts +10 -0
- package/dist/hooks/useStoreFiltering.d.ts +11 -0
- package/dist/long-bow.cjs.development.js +264 -112
- package/dist/long-bow.cjs.development.js.map +1 -1
- package/dist/long-bow.cjs.production.min.js +1 -1
- package/dist/long-bow.cjs.production.min.js.map +1 -1
- package/dist/long-bow.esm.js +265 -113
- package/dist/long-bow.esm.js.map +1 -1
- package/package.json +2 -2
- package/src/components/Store/CartView.tsx +9 -6
- package/src/components/Store/Store.tsx +13 -21
- package/src/components/Store/StoreCharacterSkinRow.tsx +64 -46
- package/src/components/Store/StoreItemDetails.tsx +4 -4
- package/src/components/Store/StoreItemRow.tsx +64 -56
- package/src/components/Store/hooks/useStoreCart.ts +14 -9
- package/src/components/Store/hooks/useStoreMetadata.ts +5 -5
- package/src/components/Store/sections/StoreItemsSection.tsx +78 -27
- package/src/components/Store/sections/StorePacksSection.tsx +5 -10
- package/src/hooks/useCharacterSkinNavigation.ts +34 -0
- package/src/hooks/usePackFiltering.ts +20 -0
- package/src/hooks/useQuantityControl.ts +41 -0
- package/src/hooks/useStoreFiltering.ts +51 -0
- package/src/mocks/dailyTasks.mocks.ts +6 -6
- 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.
|
|
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.
|
|
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 {
|
|
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,
|
|
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:
|
|
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<
|
|
74
|
+
const [currentMetadataItem, setCurrentMetadataItem] = useState<IProductBlueprint | null>(null);
|
|
75
75
|
|
|
76
76
|
const handleAddPackToCart = (pack: IItemPack) => {
|
|
77
|
-
const packItem:
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
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:
|
|
98
|
+
itemsToFilter: IProductBlueprint[],
|
|
107
99
|
type: 'items' | 'premium'
|
|
108
|
-
):
|
|
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 {
|
|
2
|
-
|
|
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:
|
|
16
|
+
item: IProductBlueprint;
|
|
12
17
|
atlasJSON: Record<string, any>;
|
|
13
18
|
atlasIMG: string;
|
|
14
|
-
onAddToCart: (
|
|
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
|
-
|
|
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
|
|
48
|
-
|
|
49
|
-
|
|
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
|
|
107
|
+
<SkinNavArrow
|
|
108
|
+
direction="left"
|
|
109
|
+
onPointerDown={handlePreviousSkin}
|
|
110
|
+
size={24}
|
|
111
|
+
/>
|
|
111
112
|
<SelectedSkin>{currentCharacter.name}</SelectedSkin>
|
|
112
|
-
<SkinNavArrow
|
|
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:
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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:
|
|
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,
|
|
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:
|
|
8
|
+
item: IProductBlueprint | (IItemPack & { name: string; texturePath: string });
|
|
9
9
|
imageUrl: string | { src: string; default?: string };
|
|
10
10
|
onBack: () => void;
|
|
11
|
-
onAddToCart: (item:
|
|
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
|
|
56
|
+
onClick={() => onAddToCart(item as IProductBlueprint)}
|
|
57
57
|
fullWidth
|
|
58
58
|
/>
|
|
59
59
|
</Actions>
|
|
@@ -1,18 +1,24 @@
|
|
|
1
|
-
import {
|
|
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:
|
|
11
|
+
item: IProductBlueprint;
|
|
11
12
|
atlasJSON: Record<string, any>;
|
|
12
13
|
atlasIMG: string;
|
|
13
|
-
onAddToCart: (
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
58
|
-
|
|
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=
|
|
84
|
+
placeholder={textInputPlaceholder}
|
|
88
85
|
onChange={e => setTextInputValue(e.target.value)}
|
|
89
86
|
className="rpgui-input"
|
|
90
87
|
/>
|
|
91
88
|
) : item.isStackable ? (
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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:
|
|
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.
|
|
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.
|
|
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.
|
|
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:
|
|
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
|
-
|
|
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
|
-
//
|
|
13
|
-
interface ICartItem
|
|
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:
|
|
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:
|
|
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
|
|
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:
|
|
160
|
-
//
|
|
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 {
|
|
1
|
+
import { IProductBlueprint, MetadataType } from "@rpg-engine/shared";
|
|
2
2
|
import { useState } from "react";
|
|
3
3
|
|
|
4
4
|
interface IUseStoreMetadata {
|
|
5
|
-
collectMetadata: (item:
|
|
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:
|
|
13
|
-
if (!item.metadataType || item.metadataType
|
|
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:
|
|
52
|
+
item: IProductBlueprint;
|
|
53
53
|
};
|
|
54
54
|
}
|
|
55
55
|
}
|