@rpg-engine/long-bow 0.8.70 → 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/dist/components/Store/CartView.d.ts +7 -6
- package/dist/components/Store/Store.d.ts +3 -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 +5 -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 +4 -3
- package/dist/long-bow.cjs.development.js +110 -49
- 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 +111 -50
- package/dist/long-bow.esm.js.map +1 -1
- package/package.json +2 -2
- package/src/components/Store/CartView.tsx +20 -6
- package/src/components/Store/Store.tsx +16 -21
- package/src/components/Store/StoreCharacterSkinRow.tsx +4 -4
- package/src/components/Store/StoreItemDetails.tsx +4 -4
- package/src/components/Store/StoreItemRow.tsx +46 -6
- 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 +24 -9
- package/src/stories/Features/store/Store.stories.tsx +29 -68
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.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.
|
|
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 {
|
|
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>;
|
|
@@ -111,6 +114,9 @@ export const CartView: React.FC<ICartViewProps> = ({
|
|
|
111
114
|
</ItemIconContainer>
|
|
112
115
|
<ItemDetails>
|
|
113
116
|
<ItemName>{cartItem.item.name}</ItemName>
|
|
117
|
+
{cartItem.metadata?.inputValue && (
|
|
118
|
+
<CartMeta>{cartItem.metadata.inputValue}</CartMeta>
|
|
119
|
+
)}
|
|
114
120
|
<ItemInfo>
|
|
115
121
|
<span>${formatPrice(cartItem.item.price)}</span>
|
|
116
122
|
<span>×</span>
|
|
@@ -339,3 +345,11 @@ const MetadataValue = styled.div`
|
|
|
339
345
|
text-overflow: ellipsis;
|
|
340
346
|
white-space: nowrap;
|
|
341
347
|
`;
|
|
348
|
+
|
|
349
|
+
const CartMeta = styled.div`
|
|
350
|
+
font-family: 'Press Start 2P', cursive;
|
|
351
|
+
font-size: 0.75rem;
|
|
352
|
+
color: #ffffff;
|
|
353
|
+
opacity: 0.8;
|
|
354
|
+
margin-top: 0.25rem;
|
|
355
|
+
`;
|
|
@@ -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;
|
|
@@ -32,6 +32,7 @@ export interface IStoreProps {
|
|
|
32
32
|
hidePremiumTab?: boolean;
|
|
33
33
|
tabOrder?: TabId[];
|
|
34
34
|
defaultActiveTab?: TabId;
|
|
35
|
+
textInputItemKeys?: string[];
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
export const Store: React.FC<IStoreProps> = ({
|
|
@@ -48,6 +49,7 @@ export const Store: React.FC<IStoreProps> = ({
|
|
|
48
49
|
hidePremiumTab = false,
|
|
49
50
|
tabOrder,
|
|
50
51
|
defaultActiveTab,
|
|
52
|
+
textInputItemKeys = [],
|
|
51
53
|
}) => {
|
|
52
54
|
const [selectedPack, setSelectedPack] = useState<IItemPack | null>(null);
|
|
53
55
|
const [activeTab, setActiveTab] = useState<TabId>(() => {
|
|
@@ -69,41 +71,33 @@ export const Store: React.FC<IStoreProps> = ({
|
|
|
69
71
|
isCartOpen,
|
|
70
72
|
} = useStoreCart();
|
|
71
73
|
const [isCollectingMetadata, setIsCollectingMetadata] = useState(false);
|
|
72
|
-
const [currentMetadataItem, setCurrentMetadataItem] = useState<
|
|
74
|
+
const [currentMetadataItem, setCurrentMetadataItem] = useState<IProductBlueprint | null>(null);
|
|
73
75
|
|
|
74
76
|
const handleAddPackToCart = (pack: IItemPack) => {
|
|
75
|
-
const packItem:
|
|
76
|
-
_id: pack.key,
|
|
77
|
+
const packItem: IProductBlueprint = {
|
|
77
78
|
key: pack.key,
|
|
78
79
|
name: pack.title,
|
|
80
|
+
description: pack.description || '',
|
|
79
81
|
price: pack.priceUSD,
|
|
82
|
+
currency: PaymentCurrency.USD,
|
|
80
83
|
texturePath: pack.image.default || pack.image.src,
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
fullDescription: pack.description || '',
|
|
86
|
-
textureAtlas: 'items',
|
|
87
|
-
weight: 0,
|
|
84
|
+
type: PurchaseType.Pack,
|
|
85
|
+
onPurchase: async () => {},
|
|
86
|
+
itemType: ItemType.Consumable,
|
|
87
|
+
itemSubType: ItemSubType.Other,
|
|
88
88
|
rarity: ItemRarities.Common,
|
|
89
|
-
|
|
90
|
-
isEquipable: false,
|
|
89
|
+
weight: 0,
|
|
91
90
|
isStackable: false,
|
|
92
|
-
isTwoHanded: false,
|
|
93
|
-
hasUseWith: false,
|
|
94
91
|
maxStackSize: 1,
|
|
95
92
|
isUsable: false,
|
|
96
|
-
isStorable: true,
|
|
97
|
-
isSolid: false,
|
|
98
|
-
isItemContainer: false,
|
|
99
93
|
};
|
|
100
94
|
handleAddToCart(packItem, 1);
|
|
101
95
|
};
|
|
102
96
|
|
|
103
97
|
const filterItems = (
|
|
104
|
-
itemsToFilter:
|
|
98
|
+
itemsToFilter: IProductBlueprint[],
|
|
105
99
|
type: 'items' | 'premium'
|
|
106
|
-
):
|
|
100
|
+
): IProductBlueprint[] => {
|
|
107
101
|
return itemsToFilter.filter(item => {
|
|
108
102
|
if (type === 'premium') {
|
|
109
103
|
return item.requiredAccountType?.length ?? 0 > 0;
|
|
@@ -188,6 +182,7 @@ export const Store: React.FC<IStoreProps> = ({
|
|
|
188
182
|
atlasJSON={atlasJSON}
|
|
189
183
|
atlasIMG={atlasIMG}
|
|
190
184
|
userAccountType={userAccountType}
|
|
185
|
+
textInputItemKeys={textInputItemKeys}
|
|
191
186
|
/>
|
|
192
187
|
),
|
|
193
188
|
},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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:
|
|
11
|
+
item: IProductBlueprint;
|
|
12
12
|
atlasJSON: Record<string, any>;
|
|
13
13
|
atlasIMG: string;
|
|
14
|
-
onAddToCart: (item:
|
|
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.
|
|
39
|
+
}, [item.key]);
|
|
40
40
|
|
|
41
41
|
const handlePreviousSkin = () => {
|
|
42
42
|
setCurrentIndex((prevIndex) =>
|
|
@@ -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,4 +1,4 @@
|
|
|
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';
|
|
@@ -7,11 +7,13 @@ import { CTAButton } from '../shared/CTAButton/CTAButton';
|
|
|
7
7
|
import { SpriteFromAtlas } from '../shared/SpriteFromAtlas';
|
|
8
8
|
|
|
9
9
|
interface IStoreItemRowProps {
|
|
10
|
-
item:
|
|
10
|
+
item: IProductBlueprint;
|
|
11
11
|
atlasJSON: Record<string, any>;
|
|
12
12
|
atlasIMG: string;
|
|
13
|
-
onAddToCart: (item:
|
|
13
|
+
onAddToCart: (item: IProductBlueprint, quantity: number, metadata?: Record<string, any>) => void;
|
|
14
14
|
userAccountType: UserAccountTypes;
|
|
15
|
+
showTextInput?: boolean;
|
|
16
|
+
textInputPlaceholder?: string;
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
export const StoreItemRow: React.FC<IStoreItemRowProps> = ({
|
|
@@ -20,8 +22,11 @@ export const StoreItemRow: React.FC<IStoreItemRowProps> = ({
|
|
|
20
22
|
atlasIMG,
|
|
21
23
|
onAddToCart,
|
|
22
24
|
userAccountType,
|
|
25
|
+
showTextInput = false,
|
|
26
|
+
textInputPlaceholder = item.inputPlaceholder,
|
|
23
27
|
}) => {
|
|
24
28
|
const [quantity, setQuantity] = useState(1);
|
|
29
|
+
const [textInputValue, setTextInputValue] = useState('');
|
|
25
30
|
|
|
26
31
|
const handleQuantityChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
27
32
|
const value = parseInt(e.target.value) || 1;
|
|
@@ -45,10 +50,15 @@ export const StoreItemRow: React.FC<IStoreItemRowProps> = ({
|
|
|
45
50
|
!item.requiredAccountType?.length ||
|
|
46
51
|
item.requiredAccountType.includes(userAccountType);
|
|
47
52
|
|
|
48
|
-
const
|
|
53
|
+
const handleAddToCartInternal = () => {
|
|
49
54
|
if (!hasRequiredAccount) return;
|
|
55
|
+
if (showTextInput) {
|
|
56
|
+
onAddToCart(item, 1, { inputValue: textInputValue });
|
|
57
|
+
setTextInputValue('');
|
|
58
|
+
} else {
|
|
50
59
|
onAddToCart(item, quantity);
|
|
51
|
-
|
|
60
|
+
setQuantity(1);
|
|
61
|
+
}
|
|
52
62
|
};
|
|
53
63
|
|
|
54
64
|
return (
|
|
@@ -68,9 +78,20 @@ export const StoreItemRow: React.FC<IStoreItemRowProps> = ({
|
|
|
68
78
|
<ItemDetails>
|
|
69
79
|
<ItemName>{item.name}</ItemName>
|
|
70
80
|
<ItemPrice>${item.price}</ItemPrice>
|
|
81
|
+
<ItemDescription>{item.description}</ItemDescription>
|
|
71
82
|
</ItemDetails>
|
|
72
83
|
|
|
73
84
|
<Controls>
|
|
85
|
+
{/* Show text input if configured, else show arrows only for stackable items */}
|
|
86
|
+
{showTextInput ? (
|
|
87
|
+
<TextInput
|
|
88
|
+
type="text"
|
|
89
|
+
value={textInputValue}
|
|
90
|
+
placeholder={textInputPlaceholder}
|
|
91
|
+
onChange={e => setTextInputValue(e.target.value)}
|
|
92
|
+
className="rpgui-input"
|
|
93
|
+
/>
|
|
94
|
+
) : item.isStackable ? (
|
|
74
95
|
<ArrowsContainer>
|
|
75
96
|
<SelectArrow
|
|
76
97
|
direction="left"
|
|
@@ -94,11 +115,12 @@ export const StoreItemRow: React.FC<IStoreItemRowProps> = ({
|
|
|
94
115
|
size={24}
|
|
95
116
|
/>
|
|
96
117
|
</ArrowsContainer>
|
|
118
|
+
) : null}
|
|
97
119
|
|
|
98
120
|
<CTAButton
|
|
99
121
|
icon={<FaCartPlus />}
|
|
100
122
|
label="Add"
|
|
101
|
-
onClick={
|
|
123
|
+
onClick={handleAddToCartInternal}
|
|
102
124
|
disabled={!hasRequiredAccount}
|
|
103
125
|
/>
|
|
104
126
|
</Controls>
|
|
@@ -147,6 +169,13 @@ const ItemPrice = styled.div`
|
|
|
147
169
|
color: #fef08a;
|
|
148
170
|
`;
|
|
149
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
|
+
|
|
150
179
|
const Controls = styled.div`
|
|
151
180
|
display: flex;
|
|
152
181
|
align-items: center;
|
|
@@ -179,3 +208,14 @@ const QuantityInput = styled.input`
|
|
|
179
208
|
margin: 0;
|
|
180
209
|
}
|
|
181
210
|
`;
|
|
211
|
+
|
|
212
|
+
const TextInput = styled.input`
|
|
213
|
+
width: 120px;
|
|
214
|
+
text-align: center;
|
|
215
|
+
margin: 0 auto;
|
|
216
|
+
font-size: 0.875rem;
|
|
217
|
+
background: rgba(0, 0, 0, 0.2);
|
|
218
|
+
color: #ffffff;
|
|
219
|
+
border: none;
|
|
220
|
+
padding: 0.25rem;
|
|
221
|
+
`;
|
|
@@ -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
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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,11 +6,12 @@ import { StoreItemRow } from '../StoreItemRow';
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
interface IStoreItemsSectionProps {
|
|
9
|
-
items:
|
|
10
|
-
onAddToCart: (item:
|
|
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;
|
|
14
|
+
textInputItemKeys?: string[];
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
export const StoreItemsSection: React.FC<IStoreItemsSectionProps> = ({
|
|
@@ -19,6 +20,7 @@ export const StoreItemsSection: React.FC<IStoreItemsSectionProps> = ({
|
|
|
19
20
|
atlasJSON,
|
|
20
21
|
atlasIMG,
|
|
21
22
|
userAccountType,
|
|
23
|
+
textInputItemKeys = [],
|
|
22
24
|
}) => {
|
|
23
25
|
const [searchQuery, setSearchQuery] = useState('');
|
|
24
26
|
|
|
@@ -26,12 +28,12 @@ export const StoreItemsSection: React.FC<IStoreItemsSectionProps> = ({
|
|
|
26
28
|
item.name.toLowerCase().includes(searchQuery.toLowerCase())
|
|
27
29
|
);
|
|
28
30
|
|
|
29
|
-
const renderStoreItem = (item:
|
|
30
|
-
//
|
|
31
|
+
const renderStoreItem = (item: IProductBlueprint) => {
|
|
32
|
+
// Prefer a specialized character skin row when needed
|
|
31
33
|
if (item.metadataType === MetadataType.CharacterSkin) {
|
|
32
34
|
return (
|
|
33
35
|
<StoreCharacterSkinRow
|
|
34
|
-
key={item.
|
|
36
|
+
key={item.key}
|
|
35
37
|
item={item}
|
|
36
38
|
atlasJSON={atlasJSON}
|
|
37
39
|
atlasIMG={atlasIMG}
|
|
@@ -40,11 +42,24 @@ export const StoreItemsSection: React.FC<IStoreItemsSectionProps> = ({
|
|
|
40
42
|
/>
|
|
41
43
|
);
|
|
42
44
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
// Render text input row when configured for this item key
|
|
46
|
+
if (textInputItemKeys.includes(item.key)) {
|
|
47
|
+
return (
|
|
48
|
+
<StoreItemRow
|
|
49
|
+
key={item.key}
|
|
50
|
+
item={item}
|
|
51
|
+
atlasJSON={atlasJSON}
|
|
52
|
+
atlasIMG={atlasIMG}
|
|
53
|
+
onAddToCart={onAddToCart}
|
|
54
|
+
userAccountType={userAccountType || UserAccountTypes.Free}
|
|
55
|
+
showTextInput
|
|
56
|
+
/>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
// Fallback to standard arrow-based row
|
|
45
60
|
return (
|
|
46
61
|
<StoreItemRow
|
|
47
|
-
key={item.
|
|
62
|
+
key={item.key}
|
|
48
63
|
item={item}
|
|
49
64
|
atlasJSON={atlasJSON}
|
|
50
65
|
atlasIMG={atlasIMG}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { IItemPack, IPurchase,
|
|
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:
|
|
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
|
-
|
|
36
|
+
price: item.basePrice,
|
|
37
|
+
currency: PaymentCurrency.USD,
|
|
38
38
|
texturePath: item.texturePath,
|
|
39
39
|
textureAtlas: 'items',
|
|
40
40
|
textureKey: item.texturePath,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
];
|
|
@@ -249,6 +209,7 @@ export const Default: Story = {
|
|
|
249
209
|
hidePremiumTab={true}
|
|
250
210
|
tabOrder={['items', 'packs']}
|
|
251
211
|
defaultActiveTab="items"
|
|
212
|
+
textInputItemKeys={['original-greater-life-potion-2', 'original-angelic-sword-1']}
|
|
252
213
|
/>
|
|
253
214
|
),
|
|
254
215
|
};
|