@rpg-engine/long-bow 0.8.219 → 0.8.220
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/MetadataCollector.d.ts +2 -2
- package/dist/components/Store/Store.d.ts +10 -27
- 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 +349 -396
- 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 +351 -398
- 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 +7 -2
- package/src/components/Store/MetadataCollector.tsx +60 -40
- package/src/components/Store/Store.tsx +75 -282
- 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,33 @@ 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
64
|
onBuyDC?: () => void;
|
|
89
|
-
/** Currency symbol to display (e.g. "$" for USD, "R$" for BRL). Defaults to "$". */
|
|
90
65
|
currencySymbol?: string;
|
|
91
|
-
/** Callback to redeem a voucher code. When provided, the Redeem tab is shown. */
|
|
92
66
|
onRedeem?: (code: string) => Promise<{ success: boolean; dcAmount?: number; error?: string }>;
|
|
93
|
-
/** Called when the voucher code input gains focus. */
|
|
94
67
|
onRedeemInputFocus?: () => void;
|
|
95
|
-
/** Called when the voucher code input loses focus. */
|
|
96
68
|
onRedeemInputBlur?: () => void;
|
|
69
|
+
/** Override the modal width. Defaults to '1000px'. */
|
|
70
|
+
width?: string;
|
|
71
|
+
/** Override the modal height. Defaults to 'min(85vh, 900px)'. */
|
|
72
|
+
height?: string;
|
|
73
|
+
/** Override the item category filter pills. Auto-derived from item.itemType when omitted. */
|
|
74
|
+
itemCategoryOptions?: Array<{ value: string; label: string }>;
|
|
97
75
|
}
|
|
98
76
|
|
|
99
77
|
export type { IFeaturedItem };
|
|
@@ -120,11 +98,6 @@ export const Store: React.FC<IStoreProps> = ({
|
|
|
120
98
|
customWalletContent,
|
|
121
99
|
customHistoryContent,
|
|
122
100
|
fullScreen = false,
|
|
123
|
-
containerWidth,
|
|
124
|
-
containerHeight,
|
|
125
|
-
mobileContainerWidth,
|
|
126
|
-
mobileContainerHeight,
|
|
127
|
-
mobileBreakpoint,
|
|
128
101
|
packsTabLabel = 'Packs',
|
|
129
102
|
packsBadge,
|
|
130
103
|
featuredItems,
|
|
@@ -146,23 +119,12 @@ export const Store: React.FC<IStoreProps> = ({
|
|
|
146
119
|
onRedeem,
|
|
147
120
|
onRedeemInputFocus,
|
|
148
121
|
onRedeemInputBlur,
|
|
122
|
+
width = '1000px',
|
|
123
|
+
height = 'min(85vh, 900px)',
|
|
124
|
+
itemCategoryOptions,
|
|
149
125
|
}) => {
|
|
150
|
-
const defaultTabOrder: TabId[] = ['premium', 'packs', 'items'];
|
|
151
126
|
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
|
-
});
|
|
127
|
+
|
|
166
128
|
const {
|
|
167
129
|
cartItems,
|
|
168
130
|
handleAddToCart,
|
|
@@ -173,9 +135,32 @@ export const Store: React.FC<IStoreProps> = ({
|
|
|
173
135
|
getTotalItems,
|
|
174
136
|
getTotalPrice,
|
|
175
137
|
isCartOpen,
|
|
138
|
+
isCollectingMetadata,
|
|
139
|
+
currentMetadataItem,
|
|
140
|
+
resolveMetadata,
|
|
176
141
|
} = useStoreCart();
|
|
177
|
-
|
|
178
|
-
const
|
|
142
|
+
|
|
143
|
+
const filteredItems = useMemo(() => ({
|
|
144
|
+
items: items,
|
|
145
|
+
premium: items.filter(item => (item.requiredAccountType?.length ?? 0) > 0),
|
|
146
|
+
}), [items]);
|
|
147
|
+
|
|
148
|
+
const { availableTabIds, activeTab, handleTabChange } = useStoreTabs({
|
|
149
|
+
tabOrder,
|
|
150
|
+
defaultActiveTab,
|
|
151
|
+
hidePremiumTab,
|
|
152
|
+
hasCharacters: !!customCharactersContent,
|
|
153
|
+
hasRedeem: !!onRedeem,
|
|
154
|
+
hasWallet: !!(onShowWallet || customWalletContent),
|
|
155
|
+
hasHistory: !!(onShowHistory || customHistoryContent),
|
|
156
|
+
onTabChange: onTabChange as ((tab: TabId, itemsShown: number) => void) | undefined,
|
|
157
|
+
getItemCount: (tab) => {
|
|
158
|
+
if (tab === 'items') return filteredItems.items.length;
|
|
159
|
+
if (tab === 'premium') return filteredItems.premium.length;
|
|
160
|
+
if (tab === 'packs') return packs.length;
|
|
161
|
+
return 0;
|
|
162
|
+
},
|
|
163
|
+
});
|
|
179
164
|
|
|
180
165
|
const handleOpenCart = () => {
|
|
181
166
|
openCart();
|
|
@@ -193,87 +178,14 @@ export const Store: React.FC<IStoreProps> = ({
|
|
|
193
178
|
};
|
|
194
179
|
|
|
195
180
|
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
|
-
}
|
|
181
|
+
const bp = packToBlueprint(pack);
|
|
182
|
+
handleAddToCart(bp, quantity);
|
|
183
|
+
onAddToCart?.(bp, quantity);
|
|
246
184
|
};
|
|
247
185
|
|
|
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'))));
|
|
186
|
+
const makePackQuickBuy = onQuickBuy
|
|
187
|
+
? (pack: IItemPack, qty?: number) => onQuickBuy(packToBlueprint(pack), qty)
|
|
188
|
+
: undefined;
|
|
277
189
|
|
|
278
190
|
const tabsMap: Record<string, { id: TabId; title: ReactNode; icon: ReactNode; content: ReactNode }> = {
|
|
279
191
|
premium: {
|
|
@@ -284,26 +196,7 @@ export const Store: React.FC<IStoreProps> = ({
|
|
|
284
196
|
<StorePacksSection
|
|
285
197
|
packs={packs.filter(pack => pack.priceUSD >= 9.99)}
|
|
286
198
|
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}
|
|
199
|
+
onQuickBuy={makePackQuickBuy}
|
|
307
200
|
onSelectPack={setSelectedPack}
|
|
308
201
|
atlasJSON={atlasJSON}
|
|
309
202
|
atlasIMG={atlasIMG}
|
|
@@ -326,26 +219,7 @@ export const Store: React.FC<IStoreProps> = ({
|
|
|
326
219
|
<StorePacksSection
|
|
327
220
|
packs={hidePremiumTab ? packs : packs.filter(pack => pack.priceUSD < 9.99)}
|
|
328
221
|
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}
|
|
222
|
+
onQuickBuy={makePackQuickBuy}
|
|
349
223
|
onSelectPack={setSelectedPack}
|
|
350
224
|
atlasJSON={atlasJSON}
|
|
351
225
|
atlasIMG={atlasIMG}
|
|
@@ -363,7 +237,7 @@ export const Store: React.FC<IStoreProps> = ({
|
|
|
363
237
|
<StoreItemsSection
|
|
364
238
|
items={filteredItems.items}
|
|
365
239
|
onAddToCart={handleAddToCartTracked}
|
|
366
|
-
onQuickBuy={onQuickBuy ? (item, qty
|
|
240
|
+
onQuickBuy={onQuickBuy ? (item, qty) => onQuickBuy(item, qty) : undefined}
|
|
367
241
|
atlasJSON={atlasJSON}
|
|
368
242
|
atlasIMG={atlasIMG}
|
|
369
243
|
userAccountType={userAccountType}
|
|
@@ -372,6 +246,7 @@ export const Store: React.FC<IStoreProps> = ({
|
|
|
372
246
|
onItemView={onItemView}
|
|
373
247
|
onCategoryChange={onCategoryChange}
|
|
374
248
|
currencySymbol={currencySymbol}
|
|
249
|
+
categoryOptions={itemCategoryOptions}
|
|
375
250
|
/>
|
|
376
251
|
),
|
|
377
252
|
},
|
|
@@ -415,36 +290,32 @@ export const Store: React.FC<IStoreProps> = ({
|
|
|
415
290
|
},
|
|
416
291
|
};
|
|
417
292
|
|
|
293
|
+
if (loading) return <LoadingMessage>Loading...</LoadingMessage>;
|
|
294
|
+
if (error) return <ErrorMessage>{error}</ErrorMessage>;
|
|
295
|
+
|
|
418
296
|
return (
|
|
419
297
|
<DraggableContainer
|
|
420
|
-
title="Store"
|
|
421
298
|
onCloseButton={onClose}
|
|
422
|
-
width={
|
|
299
|
+
width={width}
|
|
423
300
|
minWidth="700px"
|
|
424
|
-
height={
|
|
425
|
-
mobileWidth={mobileContainerWidth ?? "95vw"}
|
|
426
|
-
mobileHeight={mobileContainerHeight ?? "95vh"}
|
|
427
|
-
mobileBreakpoint={mobileBreakpoint}
|
|
301
|
+
height={height}
|
|
428
302
|
type={RPGUIContainerTypes.Framed}
|
|
429
|
-
cancelDrag="
|
|
303
|
+
cancelDrag=".store-scroll-area, [class*='CartView'], [class*='StoreItemDetails'], .close-button"
|
|
430
304
|
isFullScreen={fullScreen}
|
|
431
305
|
>
|
|
432
|
-
{isCollectingMetadata && currentMetadataItem
|
|
306
|
+
{isCollectingMetadata && currentMetadataItem?.metadataType ? (
|
|
433
307
|
<MetadataCollector
|
|
434
308
|
metadataType={currentMetadataItem.metadataType}
|
|
435
309
|
config={currentMetadataItem.metadataConfig || {}}
|
|
436
|
-
onCollect={
|
|
437
|
-
onCancel={
|
|
310
|
+
onCollect={resolveMetadata}
|
|
311
|
+
onCancel={() => resolveMetadata(null)}
|
|
438
312
|
/>
|
|
439
313
|
) : isCartOpen ? (
|
|
440
314
|
<CartView
|
|
441
315
|
cartItems={cartItems}
|
|
442
316
|
onRemoveFromCart={handleRemoveFromCartTracked}
|
|
443
317
|
onClose={closeCart}
|
|
444
|
-
onPurchase={async () => {
|
|
445
|
-
handleCartPurchase(onPurchase);
|
|
446
|
-
return true;
|
|
447
|
-
}}
|
|
318
|
+
onPurchase={async () => { handleCartPurchase(onPurchase); return true; }}
|
|
448
319
|
atlasJSON={atlasJSON}
|
|
449
320
|
atlasIMG={atlasIMG}
|
|
450
321
|
onCheckoutStart={onCheckoutStart}
|
|
@@ -466,7 +337,7 @@ export const Store: React.FC<IStoreProps> = ({
|
|
|
466
337
|
currencySymbol={currencySymbol}
|
|
467
338
|
/>
|
|
468
339
|
) : (
|
|
469
|
-
<Container>
|
|
340
|
+
<Container className="store-scroll-area">
|
|
470
341
|
{featuredItems && featuredItems.length > 0 && (
|
|
471
342
|
<FeaturedBanner
|
|
472
343
|
items={featuredItems}
|
|
@@ -478,62 +349,23 @@ export const Store: React.FC<IStoreProps> = ({
|
|
|
478
349
|
}}
|
|
479
350
|
onQuickBuy={onQuickBuy ? item => {
|
|
480
351
|
const blueprint = items.find(bp => bp.key === item.key);
|
|
481
|
-
if (blueprint)
|
|
482
|
-
|
|
352
|
+
if (blueprint) {
|
|
353
|
+
onQuickBuy(blueprint, 1);
|
|
354
|
+
} else {
|
|
483
355
|
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
|
-
}
|
|
356
|
+
if (pack) onQuickBuy(packToBlueprint(pack), 1);
|
|
504
357
|
}
|
|
505
358
|
} : undefined}
|
|
506
359
|
/>
|
|
507
360
|
)}
|
|
508
361
|
<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>
|
|
362
|
+
<StoreHeader
|
|
363
|
+
tabs={availableTabIds.map(id => ({ id, label: tabsMap[id]?.title, icon: tabsMap[id]?.icon }))}
|
|
364
|
+
activeTabId={activeTab}
|
|
365
|
+
onTabChange={handleTabChange}
|
|
366
|
+
cartItemCount={getTotalItems()}
|
|
367
|
+
onOpenCart={handleOpenCart}
|
|
368
|
+
/>
|
|
537
369
|
<TabContent>
|
|
538
370
|
{tabsMap[activeTab]?.content}
|
|
539
371
|
</TabContent>
|
|
@@ -542,7 +374,7 @@ export const Store: React.FC<IStoreProps> = ({
|
|
|
542
374
|
<Footer>
|
|
543
375
|
<CTAButton
|
|
544
376
|
icon={<FaShoppingCart />}
|
|
545
|
-
label={`
|
|
377
|
+
label={`Proceed to Checkout (${currencySymbol}${getTotalPrice().toFixed(2)})`}
|
|
546
378
|
onClick={handleOpenCart}
|
|
547
379
|
fullWidth
|
|
548
380
|
/>
|
|
@@ -558,50 +390,12 @@ const Container = styled.div`
|
|
|
558
390
|
display: flex;
|
|
559
391
|
flex-direction: column;
|
|
560
392
|
width: 100%;
|
|
561
|
-
height:
|
|
393
|
+
height: 100%;
|
|
562
394
|
gap: 0.5rem;
|
|
563
395
|
position: relative;
|
|
564
396
|
overflow: hidden;
|
|
565
397
|
`;
|
|
566
398
|
|
|
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
399
|
const MainContent = styled.div`
|
|
606
400
|
flex: 1;
|
|
607
401
|
display: flex;
|
|
@@ -616,7 +410,7 @@ const TabContent = styled.div`
|
|
|
616
410
|
`;
|
|
617
411
|
|
|
618
412
|
const Footer = styled.div`
|
|
619
|
-
padding:
|
|
413
|
+
padding: 1rem;
|
|
620
414
|
border-top: 2px solid #f59e0b;
|
|
621
415
|
background: rgba(0, 0, 0, 0.2);
|
|
622
416
|
flex-shrink: 0;
|
|
@@ -628,7 +422,6 @@ const TabLabelWithBadge = styled.span`
|
|
|
628
422
|
gap: 5px;
|
|
629
423
|
`;
|
|
630
424
|
|
|
631
|
-
|
|
632
425
|
const LoadingMessage = styled.div`
|
|
633
426
|
text-align: center;
|
|
634
427
|
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
|
+
`;
|