@rpg-engine/long-bow 0.8.219 → 0.8.221
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/DraggableContainer.d.ts +0 -6
- package/dist/components/Store/CartView.d.ts +0 -2
- package/dist/components/Store/MetadataCollector.d.ts +2 -2
- package/dist/components/Store/Store.d.ts +10 -28
- package/dist/components/Store/StoreHeader.d.ts +14 -0
- package/dist/components/Store/hooks/useStoreCart.d.ts +2 -0
- package/dist/components/Store/hooks/useStoreMetadata.d.ts +4 -11
- package/dist/components/Store/hooks/useStoreTabs.d.ts +20 -0
- package/dist/components/Store/internal/packToBlueprint.d.ts +2 -0
- package/dist/components/Store/sections/StoreItemsSection.d.ts +5 -3
- package/dist/hooks/useStoreFiltering.d.ts +7 -4
- package/dist/long-bow.cjs.development.js +379 -441
- 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 +381 -443
- package/dist/long-bow.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/components/DraggableContainer.tsx +0 -24
- package/src/components/Store/CartView.tsx +116 -137
- package/src/components/Store/MetadataCollector.tsx +60 -40
- package/src/components/Store/Store.tsx +75 -285
- package/src/components/Store/StoreHeader.tsx +74 -0
- package/src/components/Store/__test__/MetadataCollector.spec.tsx +94 -164
- package/src/components/Store/__test__/Store.spec.tsx +4 -0
- package/src/components/Store/__test__/useStoreMetadata.spec.tsx +58 -156
- package/src/components/Store/__test__/useStoreTabs.spec.tsx +69 -0
- package/src/components/Store/hooks/useStoreCart.ts +5 -2
- package/src/components/Store/hooks/useStoreMetadata.ts +30 -48
- package/src/components/Store/hooks/useStoreTabs.ts +104 -0
- package/src/components/Store/internal/packToBlueprint.ts +21 -0
- package/src/components/Store/sections/StoreItemsSection.tsx +19 -60
- package/src/components/Store/sections/StorePacksSection.tsx +0 -1
- package/src/components/shared/ScrollableContent/ScrollableContent.tsx +3 -6
- package/src/hooks/useStoreFiltering.spec.tsx +79 -0
- package/src/hooks/useStoreFiltering.ts +27 -9
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { IItemPack, IPurchase, IProductBlueprint,
|
|
1
|
+
import { IItemPack, IPurchase, IProductBlueprint, UserAccountTypes } from '@rpg-engine/shared';
|
|
2
2
|
import { Crown } from 'pixelarticons/react/Crown';
|
|
3
3
|
import { Gift } from 'pixelarticons/react/Gift';
|
|
4
4
|
import { Package } from 'pixelarticons/react/Package';
|
|
@@ -9,22 +9,20 @@ import styled from 'styled-components';
|
|
|
9
9
|
import { uiColors } from '../../constants/uiColors';
|
|
10
10
|
import { DraggableContainer } from '../DraggableContainer';
|
|
11
11
|
import { RPGUIContainerTypes } from '../RPGUI/RPGUIContainer';
|
|
12
|
-
import { Tabs } from '../shared/Tabs';
|
|
13
|
-
import { LabelPill } from '../shared/LabelPill/LabelPill';
|
|
14
12
|
import { CTAButton } from '../shared/CTAButton/CTAButton';
|
|
13
|
+
import { LabelPill } from '../shared/LabelPill/LabelPill';
|
|
15
14
|
import { CartView } from './CartView';
|
|
16
15
|
import { FeaturedBanner, IFeaturedItem } from './FeaturedBanner';
|
|
16
|
+
import { packToBlueprint } from './internal/packToBlueprint';
|
|
17
17
|
import { useStoreCart } from './hooks/useStoreCart';
|
|
18
|
+
import { TabId, useStoreTabs } from './hooks/useStoreTabs';
|
|
18
19
|
import { MetadataCollector } from './MetadataCollector';
|
|
20
|
+
import { StoreHeader } from './StoreHeader';
|
|
19
21
|
import { StoreItemsSection } from './sections/StoreItemsSection';
|
|
20
22
|
import { StorePacksSection } from './sections/StorePacksSection';
|
|
21
23
|
import { StoreRedeemSection } from './sections/StoreRedeemSection';
|
|
22
24
|
import { StoreItemDetails } from './StoreItemDetails';
|
|
23
25
|
|
|
24
|
-
// Define TabId union type for tab identifiers
|
|
25
|
-
type TabId = 'premium' | 'packs' | 'items' | 'characters' | 'wallet' | 'history' | 'redeem';
|
|
26
|
-
|
|
27
|
-
// Define IStoreProps locally as a workaround
|
|
28
26
|
export interface IStoreProps {
|
|
29
27
|
items: IProductBlueprint[];
|
|
30
28
|
packs?: IItemPack[];
|
|
@@ -47,53 +45,32 @@ export interface IStoreProps {
|
|
|
47
45
|
customCharactersContent?: React.ReactNode;
|
|
48
46
|
customWalletContent?: React.ReactNode;
|
|
49
47
|
customHistoryContent?: React.ReactNode;
|
|
50
|
-
/** When true the store renders full-screen (useful on mobile). */
|
|
51
48
|
fullScreen?: boolean;
|
|
52
|
-
/** Override the DraggableContainer width (e.g. "90vw"). Defaults to "1000px". */
|
|
53
|
-
containerWidth?: string;
|
|
54
|
-
/** Override the DraggableContainer height (e.g. "80vh"). Defaults to "min(85vh, 900px)" so the inner scroll area is bounded. */
|
|
55
|
-
containerHeight?: string;
|
|
56
|
-
/** Width applied below `mobileBreakpoint` (e.g. "95vw"). Defaults to "95vw". */
|
|
57
|
-
mobileContainerWidth?: string;
|
|
58
|
-
/** Height applied below `mobileBreakpoint` (e.g. "95vh"). Defaults to "95vh" so mobile fills the viewport. */
|
|
59
|
-
mobileContainerHeight?: string;
|
|
60
|
-
/** Viewport px under which mobile overrides apply. Defaults to 768. */
|
|
61
|
-
mobileBreakpoint?: number;
|
|
62
49
|
packsBadge?: string;
|
|
63
50
|
featuredItems?: IFeaturedItem[];
|
|
64
51
|
onQuickBuy?: (item: IProductBlueprint, quantity?: number) => void;
|
|
65
52
|
itemBadges?: Record<string, { badges?: import('./StoreBadges').IStoreBadge[]; buyCount?: number; viewersCount?: number; saleEndsAt?: string; originalPrice?: number }>;
|
|
66
53
|
packBadges?: Record<string, { badges?: import('./StoreBadges').IStoreBadge[]; buyCount?: number; viewersCount?: number; saleEndsAt?: string; originalPrice?: number }>;
|
|
67
|
-
/** Fires when an item row becomes visible (on mount). Useful for store_item_viewed analytics. */
|
|
68
54
|
onItemView?: (item: IProductBlueprint, position: number) => void;
|
|
69
|
-
/** Fires when a pack row becomes visible (on mount). Useful for pack_viewed analytics. */
|
|
70
55
|
onPackView?: (pack: IItemPack, position: number) => void;
|
|
71
|
-
/** Fires when the active store tab changes (e.g. 'items', 'packs', 'premium'). */
|
|
72
56
|
onTabChange?: (tab: string, itemsShown: number) => void;
|
|
73
|
-
/** Fires when the category filter changes in the items tab. */
|
|
74
57
|
onCategoryChange?: (category: string, itemsShown: number) => void;
|
|
75
|
-
/** Fires when the cart is opened. */
|
|
76
58
|
onCartOpen?: () => void;
|
|
77
|
-
/** Fires when any item or pack is added to the cart. */
|
|
78
59
|
onAddToCart?: (item: IProductBlueprint, quantity: number) => void;
|
|
79
|
-
/** Fires when an item is removed from the cart. */
|
|
80
60
|
onRemoveFromCart?: (itemKey: string) => void;
|
|
81
|
-
/** Fires when the user taps "Pay" — before the purchase resolves. */
|
|
82
61
|
onCheckoutStart?: (items: Array<{ key: string; name: string; quantity: number }>, total: number) => void;
|
|
83
|
-
/** Fires after a successful purchase. */
|
|
84
62
|
onPurchaseSuccess?: (items: Array<{ key: string; name: string; quantity: number }>, total: number) => void;
|
|
85
|
-
/** Fires when a purchase fails. */
|
|
86
63
|
onPurchaseError?: (error: string) => void;
|
|
87
|
-
/** Called when the DC nudge in CartView is tapped — open the DC purchase flow. */
|
|
88
|
-
onBuyDC?: () => void;
|
|
89
|
-
/** Currency symbol to display (e.g. "$" for USD, "R$" for BRL). Defaults to "$". */
|
|
90
64
|
currencySymbol?: string;
|
|
91
|
-
/** Callback to redeem a voucher code. When provided, the Redeem tab is shown. */
|
|
92
65
|
onRedeem?: (code: string) => Promise<{ success: boolean; dcAmount?: number; error?: string }>;
|
|
93
|
-
/** Called when the voucher code input gains focus. */
|
|
94
66
|
onRedeemInputFocus?: () => void;
|
|
95
|
-
/** Called when the voucher code input loses focus. */
|
|
96
67
|
onRedeemInputBlur?: () => void;
|
|
68
|
+
/** Override the modal width. Defaults to '1000px'. */
|
|
69
|
+
width?: string;
|
|
70
|
+
/** Override the modal height. Defaults to 'min(85vh, 900px)'. */
|
|
71
|
+
height?: string;
|
|
72
|
+
/** Override the item category filter pills. Auto-derived from item.itemType when omitted. */
|
|
73
|
+
itemCategoryOptions?: Array<{ value: string; label: string }>;
|
|
97
74
|
}
|
|
98
75
|
|
|
99
76
|
export type { IFeaturedItem };
|
|
@@ -120,11 +97,6 @@ export const Store: React.FC<IStoreProps> = ({
|
|
|
120
97
|
customWalletContent,
|
|
121
98
|
customHistoryContent,
|
|
122
99
|
fullScreen = false,
|
|
123
|
-
containerWidth,
|
|
124
|
-
containerHeight,
|
|
125
|
-
mobileContainerWidth,
|
|
126
|
-
mobileContainerHeight,
|
|
127
|
-
mobileBreakpoint,
|
|
128
100
|
packsTabLabel = 'Packs',
|
|
129
101
|
packsBadge,
|
|
130
102
|
featuredItems,
|
|
@@ -141,28 +113,16 @@ export const Store: React.FC<IStoreProps> = ({
|
|
|
141
113
|
onCheckoutStart,
|
|
142
114
|
onPurchaseSuccess,
|
|
143
115
|
onPurchaseError,
|
|
144
|
-
onBuyDC,
|
|
145
116
|
currencySymbol = '$',
|
|
146
117
|
onRedeem,
|
|
147
118
|
onRedeemInputFocus,
|
|
148
119
|
onRedeemInputBlur,
|
|
120
|
+
width = '1000px',
|
|
121
|
+
height = 'min(85vh, 900px)',
|
|
122
|
+
itemCategoryOptions,
|
|
149
123
|
}) => {
|
|
150
|
-
const defaultTabOrder: TabId[] = ['premium', 'packs', 'items'];
|
|
151
124
|
const [selectedPack, setSelectedPack] = useState<IItemPack | null>(null);
|
|
152
|
-
|
|
153
|
-
const allTabIds: TabId[] = [
|
|
154
|
-
...(tabOrder ?? defaultTabOrder),
|
|
155
|
-
...(customCharactersContent ? ['characters' as TabId] : []),
|
|
156
|
-
...(onRedeem ? ['redeem' as TabId] : []),
|
|
157
|
-
...((onShowWallet || customWalletContent) ? ['wallet' as TabId] : []),
|
|
158
|
-
...((onShowHistory || customHistoryContent) ? ['history' as TabId] : []),
|
|
159
|
-
];
|
|
160
|
-
const validTabs = Array.from(new Set(allTabIds.filter(id => !(hidePremiumTab && id === 'premium'))));
|
|
161
|
-
if (defaultActiveTab && validTabs.includes(defaultActiveTab)) {
|
|
162
|
-
return defaultActiveTab;
|
|
163
|
-
}
|
|
164
|
-
return validTabs[0] ?? (hidePremiumTab ? 'items' : 'premium');
|
|
165
|
-
});
|
|
125
|
+
|
|
166
126
|
const {
|
|
167
127
|
cartItems,
|
|
168
128
|
handleAddToCart,
|
|
@@ -173,9 +133,32 @@ export const Store: React.FC<IStoreProps> = ({
|
|
|
173
133
|
getTotalItems,
|
|
174
134
|
getTotalPrice,
|
|
175
135
|
isCartOpen,
|
|
136
|
+
isCollectingMetadata,
|
|
137
|
+
currentMetadataItem,
|
|
138
|
+
resolveMetadata,
|
|
176
139
|
} = useStoreCart();
|
|
177
|
-
|
|
178
|
-
const
|
|
140
|
+
|
|
141
|
+
const filteredItems = useMemo(() => ({
|
|
142
|
+
items: items,
|
|
143
|
+
premium: items.filter(item => (item.requiredAccountType?.length ?? 0) > 0),
|
|
144
|
+
}), [items]);
|
|
145
|
+
|
|
146
|
+
const { availableTabIds, activeTab, handleTabChange } = useStoreTabs({
|
|
147
|
+
tabOrder,
|
|
148
|
+
defaultActiveTab,
|
|
149
|
+
hidePremiumTab,
|
|
150
|
+
hasCharacters: !!customCharactersContent,
|
|
151
|
+
hasRedeem: !!onRedeem,
|
|
152
|
+
hasWallet: !!(onShowWallet || customWalletContent),
|
|
153
|
+
hasHistory: !!(onShowHistory || customHistoryContent),
|
|
154
|
+
onTabChange: onTabChange as ((tab: TabId, itemsShown: number) => void) | undefined,
|
|
155
|
+
getItemCount: (tab) => {
|
|
156
|
+
if (tab === 'items') return filteredItems.items.length;
|
|
157
|
+
if (tab === 'premium') return filteredItems.premium.length;
|
|
158
|
+
if (tab === 'packs') return packs.length;
|
|
159
|
+
return 0;
|
|
160
|
+
},
|
|
161
|
+
});
|
|
179
162
|
|
|
180
163
|
const handleOpenCart = () => {
|
|
181
164
|
openCart();
|
|
@@ -193,87 +176,14 @@ export const Store: React.FC<IStoreProps> = ({
|
|
|
193
176
|
};
|
|
194
177
|
|
|
195
178
|
const handleAddPackToCart = (pack: IItemPack, quantity: number = 1) => {
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
description: pack.description || '',
|
|
200
|
-
price: pack.priceUSD,
|
|
201
|
-
currency: PaymentCurrency.USD,
|
|
202
|
-
texturePath: pack.image.default || pack.image.src,
|
|
203
|
-
type: PurchaseType.Pack,
|
|
204
|
-
onPurchase: async () => {},
|
|
205
|
-
itemType: ItemType.Consumable,
|
|
206
|
-
itemSubType: ItemSubType.Other,
|
|
207
|
-
rarity: ItemRarities.Common,
|
|
208
|
-
weight: 0,
|
|
209
|
-
isStackable: false,
|
|
210
|
-
maxStackSize: 1,
|
|
211
|
-
isUsable: false,
|
|
212
|
-
};
|
|
213
|
-
handleAddToCart(packItem, quantity);
|
|
214
|
-
onAddToCart?.(packItem, quantity);
|
|
215
|
-
};
|
|
216
|
-
|
|
217
|
-
const filterItems = (
|
|
218
|
-
itemsToFilter: IProductBlueprint[],
|
|
219
|
-
type: 'items' | 'premium'
|
|
220
|
-
): IProductBlueprint[] => {
|
|
221
|
-
return itemsToFilter.filter(item => {
|
|
222
|
-
if (type === 'premium') {
|
|
223
|
-
return item.requiredAccountType?.length ?? 0 > 0;
|
|
224
|
-
}
|
|
225
|
-
return true;
|
|
226
|
-
});
|
|
227
|
-
};
|
|
228
|
-
|
|
229
|
-
const filteredItems = useMemo(
|
|
230
|
-
() => ({
|
|
231
|
-
items: filterItems(items, 'items'),
|
|
232
|
-
premium: filterItems(items, 'premium'),
|
|
233
|
-
}),
|
|
234
|
-
[items]
|
|
235
|
-
);
|
|
236
|
-
|
|
237
|
-
const handleMetadataCollected = (metadata: Record<string, any>) => {
|
|
238
|
-
if (currentMetadataItem && window.__metadataResolvers) {
|
|
239
|
-
// Resolve the promise in the useStoreMetadata hook
|
|
240
|
-
window.__metadataResolvers.resolve(metadata);
|
|
241
|
-
|
|
242
|
-
// Reset the metadata collection state
|
|
243
|
-
setCurrentMetadataItem(null);
|
|
244
|
-
// Removed unused setPendingMetadataQuantity call
|
|
245
|
-
}
|
|
179
|
+
const bp = packToBlueprint(pack);
|
|
180
|
+
handleAddToCart(bp, quantity);
|
|
181
|
+
onAddToCart?.(bp, quantity);
|
|
246
182
|
};
|
|
247
183
|
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
window.__metadataResolvers.resolve(null);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// Reset the metadata collection state
|
|
255
|
-
setCurrentMetadataItem(null);
|
|
256
|
-
// Removed unused setPendingMetadataQuantity call
|
|
257
|
-
setIsCollectingMetadata(false);
|
|
258
|
-
};
|
|
259
|
-
|
|
260
|
-
if (loading) {
|
|
261
|
-
return <LoadingMessage>Loading...</LoadingMessage>;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
if (error) {
|
|
265
|
-
return <ErrorMessage>{error}</ErrorMessage>;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// Build tabs dynamically based on props
|
|
269
|
-
const tabIds: TabId[] = [
|
|
270
|
-
...(tabOrder ?? defaultTabOrder),
|
|
271
|
-
...(customCharactersContent ? ['characters' as TabId] : []),
|
|
272
|
-
...(onRedeem ? ['redeem' as TabId] : []),
|
|
273
|
-
...((onShowWallet || customWalletContent) ? ['wallet' as TabId] : []),
|
|
274
|
-
...((onShowHistory || customHistoryContent) ? ['history' as TabId] : [])
|
|
275
|
-
];
|
|
276
|
-
const availableTabIds: TabId[] = Array.from(new Set(tabIds.filter(id => !(hidePremiumTab && id === 'premium'))));
|
|
184
|
+
const makePackQuickBuy = onQuickBuy
|
|
185
|
+
? (pack: IItemPack, qty?: number) => onQuickBuy(packToBlueprint(pack), qty)
|
|
186
|
+
: undefined;
|
|
277
187
|
|
|
278
188
|
const tabsMap: Record<string, { id: TabId; title: ReactNode; icon: ReactNode; content: ReactNode }> = {
|
|
279
189
|
premium: {
|
|
@@ -284,26 +194,7 @@ export const Store: React.FC<IStoreProps> = ({
|
|
|
284
194
|
<StorePacksSection
|
|
285
195
|
packs={packs.filter(pack => pack.priceUSD >= 9.99)}
|
|
286
196
|
onAddToCart={handleAddPackToCart}
|
|
287
|
-
onQuickBuy={
|
|
288
|
-
const bp: IProductBlueprint = {
|
|
289
|
-
key: pack.key,
|
|
290
|
-
name: pack.title,
|
|
291
|
-
description: pack.description || '',
|
|
292
|
-
price: pack.priceUSD,
|
|
293
|
-
currency: PaymentCurrency.USD,
|
|
294
|
-
texturePath: pack.image.default || pack.image.src,
|
|
295
|
-
type: PurchaseType.Pack,
|
|
296
|
-
onPurchase: async () => {},
|
|
297
|
-
itemType: ItemType.Consumable,
|
|
298
|
-
itemSubType: ItemSubType.Other,
|
|
299
|
-
rarity: ItemRarities.Common,
|
|
300
|
-
weight: 0,
|
|
301
|
-
isStackable: false,
|
|
302
|
-
maxStackSize: 1,
|
|
303
|
-
isUsable: false,
|
|
304
|
-
};
|
|
305
|
-
onQuickBuy(bp, qty);
|
|
306
|
-
} : undefined}
|
|
197
|
+
onQuickBuy={makePackQuickBuy}
|
|
307
198
|
onSelectPack={setSelectedPack}
|
|
308
199
|
atlasJSON={atlasJSON}
|
|
309
200
|
atlasIMG={atlasIMG}
|
|
@@ -326,26 +217,7 @@ export const Store: React.FC<IStoreProps> = ({
|
|
|
326
217
|
<StorePacksSection
|
|
327
218
|
packs={hidePremiumTab ? packs : packs.filter(pack => pack.priceUSD < 9.99)}
|
|
328
219
|
onAddToCart={handleAddPackToCart}
|
|
329
|
-
onQuickBuy={
|
|
330
|
-
const bp: IProductBlueprint = {
|
|
331
|
-
key: pack.key,
|
|
332
|
-
name: pack.title,
|
|
333
|
-
description: pack.description || '',
|
|
334
|
-
price: pack.priceUSD,
|
|
335
|
-
currency: PaymentCurrency.USD,
|
|
336
|
-
texturePath: pack.image.default || pack.image.src,
|
|
337
|
-
type: PurchaseType.Pack,
|
|
338
|
-
onPurchase: async () => {},
|
|
339
|
-
itemType: ItemType.Consumable,
|
|
340
|
-
itemSubType: ItemSubType.Other,
|
|
341
|
-
rarity: ItemRarities.Common,
|
|
342
|
-
weight: 0,
|
|
343
|
-
isStackable: false,
|
|
344
|
-
maxStackSize: 1,
|
|
345
|
-
isUsable: false,
|
|
346
|
-
};
|
|
347
|
-
onQuickBuy(bp, qty);
|
|
348
|
-
} : undefined}
|
|
220
|
+
onQuickBuy={makePackQuickBuy}
|
|
349
221
|
onSelectPack={setSelectedPack}
|
|
350
222
|
atlasJSON={atlasJSON}
|
|
351
223
|
atlasIMG={atlasIMG}
|
|
@@ -363,7 +235,7 @@ export const Store: React.FC<IStoreProps> = ({
|
|
|
363
235
|
<StoreItemsSection
|
|
364
236
|
items={filteredItems.items}
|
|
365
237
|
onAddToCart={handleAddToCartTracked}
|
|
366
|
-
onQuickBuy={onQuickBuy ? (item, qty
|
|
238
|
+
onQuickBuy={onQuickBuy ? (item, qty) => onQuickBuy(item, qty) : undefined}
|
|
367
239
|
atlasJSON={atlasJSON}
|
|
368
240
|
atlasIMG={atlasIMG}
|
|
369
241
|
userAccountType={userAccountType}
|
|
@@ -372,6 +244,7 @@ export const Store: React.FC<IStoreProps> = ({
|
|
|
372
244
|
onItemView={onItemView}
|
|
373
245
|
onCategoryChange={onCategoryChange}
|
|
374
246
|
currencySymbol={currencySymbol}
|
|
247
|
+
categoryOptions={itemCategoryOptions}
|
|
375
248
|
/>
|
|
376
249
|
),
|
|
377
250
|
},
|
|
@@ -415,42 +288,37 @@ export const Store: React.FC<IStoreProps> = ({
|
|
|
415
288
|
},
|
|
416
289
|
};
|
|
417
290
|
|
|
291
|
+
if (loading) return <LoadingMessage>Loading...</LoadingMessage>;
|
|
292
|
+
if (error) return <ErrorMessage>{error}</ErrorMessage>;
|
|
293
|
+
|
|
418
294
|
return (
|
|
419
295
|
<DraggableContainer
|
|
420
|
-
title="Store"
|
|
421
296
|
onCloseButton={onClose}
|
|
422
|
-
width={
|
|
297
|
+
width={width}
|
|
423
298
|
minWidth="700px"
|
|
424
|
-
height={
|
|
425
|
-
mobileWidth={mobileContainerWidth ?? "95vw"}
|
|
426
|
-
mobileHeight={mobileContainerHeight ?? "95vh"}
|
|
427
|
-
mobileBreakpoint={mobileBreakpoint}
|
|
299
|
+
height={height}
|
|
428
300
|
type={RPGUIContainerTypes.Framed}
|
|
429
|
-
cancelDrag="
|
|
301
|
+
cancelDrag=".store-scroll-area, [class*='CartView'], [class*='StoreItemDetails'], .close-button"
|
|
430
302
|
isFullScreen={fullScreen}
|
|
431
303
|
>
|
|
432
|
-
{isCollectingMetadata && currentMetadataItem
|
|
304
|
+
{isCollectingMetadata && currentMetadataItem?.metadataType ? (
|
|
433
305
|
<MetadataCollector
|
|
434
306
|
metadataType={currentMetadataItem.metadataType}
|
|
435
307
|
config={currentMetadataItem.metadataConfig || {}}
|
|
436
|
-
onCollect={
|
|
437
|
-
onCancel={
|
|
308
|
+
onCollect={resolveMetadata}
|
|
309
|
+
onCancel={() => resolveMetadata(null)}
|
|
438
310
|
/>
|
|
439
311
|
) : isCartOpen ? (
|
|
440
312
|
<CartView
|
|
441
313
|
cartItems={cartItems}
|
|
442
314
|
onRemoveFromCart={handleRemoveFromCartTracked}
|
|
443
315
|
onClose={closeCart}
|
|
444
|
-
onPurchase={async () => {
|
|
445
|
-
handleCartPurchase(onPurchase);
|
|
446
|
-
return true;
|
|
447
|
-
}}
|
|
316
|
+
onPurchase={async () => { handleCartPurchase(onPurchase); return true; }}
|
|
448
317
|
atlasJSON={atlasJSON}
|
|
449
318
|
atlasIMG={atlasIMG}
|
|
450
319
|
onCheckoutStart={onCheckoutStart}
|
|
451
320
|
onPurchaseSuccess={onPurchaseSuccess}
|
|
452
321
|
onPurchaseError={onPurchaseError}
|
|
453
|
-
onBuyDC={onBuyDC}
|
|
454
322
|
currencySymbol={currencySymbol}
|
|
455
323
|
/>
|
|
456
324
|
) : selectedPack ? (
|
|
@@ -466,7 +334,7 @@ export const Store: React.FC<IStoreProps> = ({
|
|
|
466
334
|
currencySymbol={currencySymbol}
|
|
467
335
|
/>
|
|
468
336
|
) : (
|
|
469
|
-
<Container>
|
|
337
|
+
<Container className="store-scroll-area">
|
|
470
338
|
{featuredItems && featuredItems.length > 0 && (
|
|
471
339
|
<FeaturedBanner
|
|
472
340
|
items={featuredItems}
|
|
@@ -478,62 +346,23 @@ export const Store: React.FC<IStoreProps> = ({
|
|
|
478
346
|
}}
|
|
479
347
|
onQuickBuy={onQuickBuy ? item => {
|
|
480
348
|
const blueprint = items.find(bp => bp.key === item.key);
|
|
481
|
-
if (blueprint)
|
|
482
|
-
|
|
349
|
+
if (blueprint) {
|
|
350
|
+
onQuickBuy(blueprint, 1);
|
|
351
|
+
} else {
|
|
483
352
|
const pack = packs.find(p => p.key === item.key);
|
|
484
|
-
if (pack)
|
|
485
|
-
const packBlueprint: IProductBlueprint = {
|
|
486
|
-
key: pack.key,
|
|
487
|
-
name: pack.title,
|
|
488
|
-
description: pack.description || '',
|
|
489
|
-
price: pack.priceUSD,
|
|
490
|
-
currency: PaymentCurrency.USD,
|
|
491
|
-
texturePath: pack.image.default || pack.image.src,
|
|
492
|
-
type: PurchaseType.Pack,
|
|
493
|
-
onPurchase: async () => {},
|
|
494
|
-
itemType: ItemType.Consumable,
|
|
495
|
-
itemSubType: ItemSubType.Other,
|
|
496
|
-
rarity: ItemRarities.Common,
|
|
497
|
-
weight: 0,
|
|
498
|
-
isStackable: false,
|
|
499
|
-
maxStackSize: 1,
|
|
500
|
-
isUsable: false,
|
|
501
|
-
};
|
|
502
|
-
onQuickBuy(packBlueprint, 1);
|
|
503
|
-
}
|
|
353
|
+
if (pack) onQuickBuy(packToBlueprint(pack), 1);
|
|
504
354
|
}
|
|
505
355
|
} : undefined}
|
|
506
356
|
/>
|
|
507
357
|
)}
|
|
508
358
|
<MainContent>
|
|
509
|
-
<
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
setActiveTab(nextTab);
|
|
517
|
-
if (onTabChange) {
|
|
518
|
-
const itemCount = nextTab === 'items' ? filteredItems.items.length
|
|
519
|
-
: nextTab === 'premium' ? filteredItems.premium.length
|
|
520
|
-
: nextTab === 'packs' ? packs.length
|
|
521
|
-
: 0;
|
|
522
|
-
onTabChange(nextTab, itemCount);
|
|
523
|
-
}
|
|
524
|
-
}}
|
|
525
|
-
/>
|
|
526
|
-
</TabsFlexWrapper>
|
|
527
|
-
<CartButtonWrapper>
|
|
528
|
-
<CTAButton
|
|
529
|
-
icon={<FaShoppingCart />}
|
|
530
|
-
onClick={handleOpenCart}
|
|
531
|
-
/>
|
|
532
|
-
{getTotalItems() > 0 && (
|
|
533
|
-
<CartBadge>{getTotalItems()}</CartBadge>
|
|
534
|
-
)}
|
|
535
|
-
</CartButtonWrapper>
|
|
536
|
-
</HeaderRow>
|
|
359
|
+
<StoreHeader
|
|
360
|
+
tabs={availableTabIds.map(id => ({ id, label: tabsMap[id]?.title, icon: tabsMap[id]?.icon }))}
|
|
361
|
+
activeTabId={activeTab}
|
|
362
|
+
onTabChange={handleTabChange}
|
|
363
|
+
cartItemCount={getTotalItems()}
|
|
364
|
+
onOpenCart={handleOpenCart}
|
|
365
|
+
/>
|
|
537
366
|
<TabContent>
|
|
538
367
|
{tabsMap[activeTab]?.content}
|
|
539
368
|
</TabContent>
|
|
@@ -542,7 +371,7 @@ export const Store: React.FC<IStoreProps> = ({
|
|
|
542
371
|
<Footer>
|
|
543
372
|
<CTAButton
|
|
544
373
|
icon={<FaShoppingCart />}
|
|
545
|
-
label={`
|
|
374
|
+
label={`Proceed to Checkout (${currencySymbol}${getTotalPrice().toFixed(2)})`}
|
|
546
375
|
onClick={handleOpenCart}
|
|
547
376
|
fullWidth
|
|
548
377
|
/>
|
|
@@ -558,50 +387,12 @@ const Container = styled.div`
|
|
|
558
387
|
display: flex;
|
|
559
388
|
flex-direction: column;
|
|
560
389
|
width: 100%;
|
|
561
|
-
height:
|
|
390
|
+
height: 100%;
|
|
562
391
|
gap: 0.5rem;
|
|
563
392
|
position: relative;
|
|
564
393
|
overflow: hidden;
|
|
565
394
|
`;
|
|
566
395
|
|
|
567
|
-
const HeaderRow = styled.div`
|
|
568
|
-
display: flex;
|
|
569
|
-
align-items: flex-end;
|
|
570
|
-
justify-content: space-between;
|
|
571
|
-
margin-bottom: 0.25rem;
|
|
572
|
-
padding-top: 10px;
|
|
573
|
-
padding-right: 12px;
|
|
574
|
-
`;
|
|
575
|
-
|
|
576
|
-
const TabsFlexWrapper = styled.div`
|
|
577
|
-
flex: 1;
|
|
578
|
-
min-width: 0;
|
|
579
|
-
`;
|
|
580
|
-
|
|
581
|
-
const CartButtonWrapper = styled.div`
|
|
582
|
-
position: relative;
|
|
583
|
-
flex-shrink: 0;
|
|
584
|
-
`;
|
|
585
|
-
|
|
586
|
-
const CartBadge = styled.div`
|
|
587
|
-
position: absolute;
|
|
588
|
-
top: -8px;
|
|
589
|
-
right: -8px;
|
|
590
|
-
background: #ef4444; /* red */
|
|
591
|
-
color: white;
|
|
592
|
-
font-family: 'Press Start 2P', cursive;
|
|
593
|
-
font-size: 0.5rem;
|
|
594
|
-
padding: 4px;
|
|
595
|
-
border-radius: 50%;
|
|
596
|
-
border: 2px solid #000;
|
|
597
|
-
display: flex;
|
|
598
|
-
align-items: center;
|
|
599
|
-
justify-content: center;
|
|
600
|
-
min-width: 16px;
|
|
601
|
-
min-height: 16px;
|
|
602
|
-
box-sizing: content-box;
|
|
603
|
-
`;
|
|
604
|
-
|
|
605
396
|
const MainContent = styled.div`
|
|
606
397
|
flex: 1;
|
|
607
398
|
display: flex;
|
|
@@ -616,7 +407,7 @@ const TabContent = styled.div`
|
|
|
616
407
|
`;
|
|
617
408
|
|
|
618
409
|
const Footer = styled.div`
|
|
619
|
-
padding:
|
|
410
|
+
padding: 1rem;
|
|
620
411
|
border-top: 2px solid #f59e0b;
|
|
621
412
|
background: rgba(0, 0, 0, 0.2);
|
|
622
413
|
flex-shrink: 0;
|
|
@@ -628,7 +419,6 @@ const TabLabelWithBadge = styled.span`
|
|
|
628
419
|
gap: 5px;
|
|
629
420
|
`;
|
|
630
421
|
|
|
631
|
-
|
|
632
422
|
const LoadingMessage = styled.div`
|
|
633
423
|
text-align: center;
|
|
634
424
|
color: ${uiColors.white};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import React, { ReactNode } from 'react';
|
|
2
|
+
import { FaShoppingCart } from 'react-icons/fa';
|
|
3
|
+
import styled from 'styled-components';
|
|
4
|
+
import { CTAButton } from '../shared/CTAButton/CTAButton';
|
|
5
|
+
import { Tabs } from '../shared/Tabs';
|
|
6
|
+
import { TabId } from './hooks/useStoreTabs';
|
|
7
|
+
|
|
8
|
+
export interface IStoreHeaderProps {
|
|
9
|
+
tabs: Array<{ id: TabId; label: ReactNode; icon: ReactNode }>;
|
|
10
|
+
activeTabId: TabId;
|
|
11
|
+
onTabChange: (tabId: string) => void;
|
|
12
|
+
cartItemCount: number;
|
|
13
|
+
onOpenCart: () => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const StoreHeader: React.FC<IStoreHeaderProps> = ({
|
|
17
|
+
tabs,
|
|
18
|
+
activeTabId,
|
|
19
|
+
onTabChange,
|
|
20
|
+
cartItemCount,
|
|
21
|
+
onOpenCart,
|
|
22
|
+
}) => (
|
|
23
|
+
<HeaderRow>
|
|
24
|
+
<TabsFlexWrapper>
|
|
25
|
+
<Tabs
|
|
26
|
+
options={tabs.map(t => ({ id: t.id, label: t.label, icon: t.icon }))}
|
|
27
|
+
activeTabId={activeTabId}
|
|
28
|
+
onTabChange={onTabChange}
|
|
29
|
+
/>
|
|
30
|
+
</TabsFlexWrapper>
|
|
31
|
+
<CartButtonWrapper>
|
|
32
|
+
<CTAButton icon={<FaShoppingCart />} onClick={onOpenCart} />
|
|
33
|
+
{cartItemCount > 0 && <CartBadge>{cartItemCount}</CartBadge>}
|
|
34
|
+
</CartButtonWrapper>
|
|
35
|
+
</HeaderRow>
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const HeaderRow = styled.div`
|
|
39
|
+
display: flex;
|
|
40
|
+
align-items: flex-end;
|
|
41
|
+
justify-content: space-between;
|
|
42
|
+
margin-bottom: 0.25rem;
|
|
43
|
+
padding-top: 10px;
|
|
44
|
+
padding-right: 12px;
|
|
45
|
+
`;
|
|
46
|
+
|
|
47
|
+
const TabsFlexWrapper = styled.div`
|
|
48
|
+
flex: 1;
|
|
49
|
+
min-width: 0;
|
|
50
|
+
`;
|
|
51
|
+
|
|
52
|
+
const CartButtonWrapper = styled.div`
|
|
53
|
+
position: relative;
|
|
54
|
+
flex-shrink: 0;
|
|
55
|
+
`;
|
|
56
|
+
|
|
57
|
+
const CartBadge = styled.div`
|
|
58
|
+
position: absolute;
|
|
59
|
+
top: -8px;
|
|
60
|
+
right: -8px;
|
|
61
|
+
background: #ef4444;
|
|
62
|
+
color: white;
|
|
63
|
+
font-family: 'Press Start 2P', cursive;
|
|
64
|
+
font-size: 0.5rem;
|
|
65
|
+
padding: 4px;
|
|
66
|
+
border-radius: 50%;
|
|
67
|
+
border: 2px solid #000;
|
|
68
|
+
display: flex;
|
|
69
|
+
align-items: center;
|
|
70
|
+
justify-content: center;
|
|
71
|
+
min-width: 16px;
|
|
72
|
+
min-height: 16px;
|
|
73
|
+
box-sizing: content-box;
|
|
74
|
+
`;
|