@rpg-engine/long-bow 0.8.161 → 0.8.163
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/Marketplace/BuyOrderRows.d.ts +11 -0
- package/dist/components/Marketplace/GroupedRowContainer.d.ts +7 -0
- package/dist/components/Store/Store.d.ts +1 -0
- package/dist/components/Store/sections/StorePacksSection.d.ts +1 -1
- package/dist/components/shared/ItemRowWrapper.d.ts +3 -0
- package/dist/long-bow.cjs.development.js +329 -189
- 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 +329 -190
- package/dist/long-bow.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/components/Marketplace/BuyOrderRows.tsx +53 -16
- package/src/components/Marketplace/BuyPanel.tsx +140 -114
- package/src/components/Marketplace/GroupedRowContainer.tsx +86 -0
- package/src/components/Marketplace/MarketplaceRows.tsx +24 -125
- package/src/components/Store/Store.tsx +40 -21
- package/src/components/Store/StoreItemRow.tsx +28 -33
- package/src/components/Store/sections/StoreItemsSection.tsx +7 -13
- package/src/components/Store/sections/StorePacksSection.tsx +95 -37
- package/src/components/shared/ItemRowWrapper.tsx +22 -0
- package/src/stories/Features/store/Store.stories.tsx +2 -1
- package/src/stories/Features/trading/Marketplace.stories.tsx +18 -5
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { IItemPack, IPurchase, IProductBlueprint, ItemRarities, ItemSubType, ItemType, UserAccountTypes, PaymentCurrency, PurchaseType } from '@rpg-engine/shared';
|
|
2
|
+
import { Box } from 'pixelarticons/react/Box';
|
|
3
|
+
import { Crown } from 'pixelarticons/react/Crown';
|
|
4
|
+
import { Gift } from 'pixelarticons/react/Gift';
|
|
5
|
+
import { Wallet } from 'pixelarticons/react/Wallet';
|
|
2
6
|
import React, { ReactNode, useMemo, useState } from 'react';
|
|
3
7
|
import { FaHistory, FaShoppingCart, FaWallet } from 'react-icons/fa';
|
|
4
8
|
import styled from 'styled-components';
|
|
5
9
|
import { uiColors } from '../../constants/uiColors';
|
|
6
10
|
import { DraggableContainer } from '../DraggableContainer';
|
|
7
|
-
import { InternalTabs } from '../InternalTabs/InternalTabs';
|
|
8
11
|
import { RPGUIContainerTypes } from '../RPGUI/RPGUIContainer';
|
|
12
|
+
import { Tabs } from '../shared/Tabs';
|
|
13
|
+
import { LabelPill } from '../shared/LabelPill/LabelPill';
|
|
9
14
|
import { CTAButton } from '../shared/CTAButton/CTAButton';
|
|
10
15
|
import { CartView } from './CartView';
|
|
11
16
|
import { useStoreCart } from './hooks/useStoreCart';
|
|
@@ -38,6 +43,7 @@ export interface IStoreProps {
|
|
|
38
43
|
textInputItemKeys?: string[];
|
|
39
44
|
customPacksContent?: React.ReactNode;
|
|
40
45
|
customWalletContent?: React.ReactNode;
|
|
46
|
+
packsBadge?: string;
|
|
41
47
|
}
|
|
42
48
|
|
|
43
49
|
export const Store: React.FC<IStoreProps> = ({
|
|
@@ -60,6 +66,7 @@ export const Store: React.FC<IStoreProps> = ({
|
|
|
60
66
|
customPacksContent,
|
|
61
67
|
customWalletContent,
|
|
62
68
|
packsTabLabel = 'Packs',
|
|
69
|
+
packsBadge,
|
|
63
70
|
}) => {
|
|
64
71
|
const [selectedPack, setSelectedPack] = useState<IItemPack | null>(null);
|
|
65
72
|
const [activeTab, setActiveTab] = useState<TabId>(() => {
|
|
@@ -83,7 +90,7 @@ export const Store: React.FC<IStoreProps> = ({
|
|
|
83
90
|
const [isCollectingMetadata, setIsCollectingMetadata] = useState(false);
|
|
84
91
|
const [currentMetadataItem, setCurrentMetadataItem] = useState<IProductBlueprint | null>(null);
|
|
85
92
|
|
|
86
|
-
const handleAddPackToCart = (pack: IItemPack) => {
|
|
93
|
+
const handleAddPackToCart = (pack: IItemPack, quantity: number = 1) => {
|
|
87
94
|
const packItem: IProductBlueprint = {
|
|
88
95
|
key: pack.key,
|
|
89
96
|
name: pack.title,
|
|
@@ -101,7 +108,7 @@ export const Store: React.FC<IStoreProps> = ({
|
|
|
101
108
|
maxStackSize: 1,
|
|
102
109
|
isUsable: false,
|
|
103
110
|
};
|
|
104
|
-
handleAddToCart(packItem,
|
|
111
|
+
handleAddToCart(packItem, quantity);
|
|
105
112
|
};
|
|
106
113
|
|
|
107
114
|
const filterItems = (
|
|
@@ -159,10 +166,11 @@ export const Store: React.FC<IStoreProps> = ({
|
|
|
159
166
|
const tabIds: TabId[] = tabOrder ?? ['premium', 'packs', 'items'];
|
|
160
167
|
const availableTabIds: TabId[] = tabIds.filter(id => !(hidePremiumTab && id === 'premium'));
|
|
161
168
|
|
|
162
|
-
const tabsMap: Record<string, { id: TabId; title:
|
|
169
|
+
const tabsMap: Record<string, { id: TabId; title: ReactNode; icon: ReactNode; content: ReactNode }> = {
|
|
163
170
|
premium: {
|
|
164
171
|
id: 'premium',
|
|
165
172
|
title: 'Premium',
|
|
173
|
+
icon: <Crown width={18} height={18} />,
|
|
166
174
|
content: (
|
|
167
175
|
<StorePacksSection
|
|
168
176
|
packs={packs.filter(pack => pack.priceUSD >= 9.99)}
|
|
@@ -175,7 +183,13 @@ export const Store: React.FC<IStoreProps> = ({
|
|
|
175
183
|
},
|
|
176
184
|
packs: {
|
|
177
185
|
id: 'packs',
|
|
178
|
-
title:
|
|
186
|
+
title: packsBadge ? (
|
|
187
|
+
<TabLabelWithBadge>
|
|
188
|
+
{packsTabLabel}
|
|
189
|
+
<LabelPill background="#f59e0b" borderColor="#f59e0b" color="#000">{packsBadge}</LabelPill>
|
|
190
|
+
</TabLabelWithBadge>
|
|
191
|
+
) : packsTabLabel,
|
|
192
|
+
icon: <Gift width={18} height={18} />,
|
|
179
193
|
content: customPacksContent ?? (
|
|
180
194
|
<StorePacksSection
|
|
181
195
|
packs={hidePremiumTab ? packs : packs.filter(pack => pack.priceUSD < 9.99)}
|
|
@@ -189,6 +203,7 @@ export const Store: React.FC<IStoreProps> = ({
|
|
|
189
203
|
items: {
|
|
190
204
|
id: 'items',
|
|
191
205
|
title: 'Items',
|
|
206
|
+
icon: <Box width={18} height={18} />,
|
|
192
207
|
content: (
|
|
193
208
|
<StoreItemsSection
|
|
194
209
|
items={filteredItems.items}
|
|
@@ -203,12 +218,11 @@ export const Store: React.FC<IStoreProps> = ({
|
|
|
203
218
|
wallet: {
|
|
204
219
|
id: 'wallet',
|
|
205
220
|
title: 'Wallet',
|
|
221
|
+
icon: <Wallet width={18} height={18} />,
|
|
206
222
|
content: customWalletContent ?? null,
|
|
207
223
|
},
|
|
208
224
|
};
|
|
209
225
|
|
|
210
|
-
const tabs = availableTabIds.map(id => tabsMap[id]);
|
|
211
|
-
|
|
212
226
|
return (
|
|
213
227
|
<DraggableContainer
|
|
214
228
|
title="Store"
|
|
@@ -276,16 +290,14 @@ export const Store: React.FC<IStoreProps> = ({
|
|
|
276
290
|
</CartButton>
|
|
277
291
|
</TopBar>
|
|
278
292
|
<MainContent>
|
|
279
|
-
<
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
inactiveColor="#6b7280"
|
|
284
|
-
borderColor="#f59e0b"
|
|
285
|
-
hoverColor="#fef3c7"
|
|
286
|
-
activeTab={activeTab}
|
|
287
|
-
onTabChange={(tabId: string) => setActiveTab(tabId as TabId)}
|
|
293
|
+
<Tabs
|
|
294
|
+
options={availableTabIds.map(id => ({ id, label: tabsMap[id].title, icon: tabsMap[id].icon }))}
|
|
295
|
+
activeTabId={activeTab}
|
|
296
|
+
onTabChange={(tabId) => setActiveTab(tabId as TabId)}
|
|
288
297
|
/>
|
|
298
|
+
<TabContent>
|
|
299
|
+
{tabsMap[activeTab]?.content}
|
|
300
|
+
</TabContent>
|
|
289
301
|
</MainContent>
|
|
290
302
|
{cartItems.length > 0 && (
|
|
291
303
|
<Footer>
|
|
@@ -349,12 +361,12 @@ const MainContent = styled.div`
|
|
|
349
361
|
flex-direction: column;
|
|
350
362
|
min-height: 0;
|
|
351
363
|
overflow: hidden;
|
|
364
|
+
`;
|
|
352
365
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
}
|
|
366
|
+
const TabContent = styled.div`
|
|
367
|
+
flex: 1;
|
|
368
|
+
overflow-y: auto;
|
|
369
|
+
padding-right: 0.5rem;
|
|
358
370
|
`;
|
|
359
371
|
|
|
360
372
|
const Footer = styled.div`
|
|
@@ -386,6 +398,13 @@ const CartInfo = styled.div`
|
|
|
386
398
|
}
|
|
387
399
|
`;
|
|
388
400
|
|
|
401
|
+
const TabLabelWithBadge = styled.span`
|
|
402
|
+
display: inline-flex;
|
|
403
|
+
align-items: center;
|
|
404
|
+
gap: 5px;
|
|
405
|
+
`;
|
|
406
|
+
|
|
407
|
+
|
|
389
408
|
const LoadingMessage = styled.div`
|
|
390
409
|
text-align: center;
|
|
391
410
|
color: ${uiColors.white};
|
|
@@ -4,6 +4,7 @@ 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
|
+
import { ItemRowWrapper } from '../shared/ItemRowWrapper';
|
|
7
8
|
import { SpriteFromAtlas } from '../shared/SpriteFromAtlas';
|
|
8
9
|
import { useQuantityControl } from '../../hooks/useQuantityControl';
|
|
9
10
|
|
|
@@ -56,27 +57,29 @@ export const StoreItemRow: React.FC<IStoreItemRowProps> = ({
|
|
|
56
57
|
};
|
|
57
58
|
|
|
58
59
|
return (
|
|
59
|
-
<
|
|
60
|
-
<
|
|
61
|
-
<
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
<
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
60
|
+
<ItemRowWrapper $isHighlighted={item.store?.isHighlighted || false}>
|
|
61
|
+
<LeftSection>
|
|
62
|
+
<ItemIconContainer>
|
|
63
|
+
<SpriteFromAtlas
|
|
64
|
+
atlasJSON={atlasJSON}
|
|
65
|
+
atlasIMG={atlasIMG}
|
|
66
|
+
spriteKey={item.texturePath}
|
|
67
|
+
width={32}
|
|
68
|
+
height={32}
|
|
69
|
+
imgScale={2}
|
|
70
|
+
centered
|
|
71
|
+
/>
|
|
72
|
+
</ItemIconContainer>
|
|
73
|
+
|
|
74
|
+
<ItemDetails>
|
|
75
|
+
<ItemName>{item.name}</ItemName>
|
|
76
|
+
<ItemPrice>
|
|
77
|
+
${item.price}
|
|
78
|
+
{(item as any).dcPrice ? ` · ${((item as any).dcPrice as number).toLocaleString()} DC` : ''}
|
|
79
|
+
</ItemPrice>
|
|
80
|
+
<ItemDescription>{item.description}</ItemDescription>
|
|
81
|
+
</ItemDetails>
|
|
82
|
+
</LeftSection>
|
|
80
83
|
|
|
81
84
|
<Controls>
|
|
82
85
|
{/* Show text input if configured, else show arrows only for stackable items */}
|
|
@@ -121,24 +124,16 @@ export const StoreItemRow: React.FC<IStoreItemRowProps> = ({
|
|
|
121
124
|
disabled={!hasRequiredAccount}
|
|
122
125
|
/>
|
|
123
126
|
</Controls>
|
|
124
|
-
</
|
|
127
|
+
</ItemRowWrapper>
|
|
125
128
|
);
|
|
126
129
|
};
|
|
127
130
|
|
|
128
|
-
const
|
|
131
|
+
const LeftSection = styled.div`
|
|
129
132
|
display: flex;
|
|
130
133
|
align-items: center;
|
|
131
134
|
gap: 0.75rem;
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
background: ${props =>
|
|
135
|
-
props.$isHighlighted ? 'rgba(255, 215, 0, 0.1)' : 'transparent'};
|
|
136
|
-
border-left: ${props =>
|
|
137
|
-
props.$isHighlighted ? '3px solid #ffd700' : '3px solid transparent'};
|
|
138
|
-
|
|
139
|
-
&:last-child {
|
|
140
|
-
border-bottom: none;
|
|
141
|
-
}
|
|
135
|
+
flex: 1;
|
|
136
|
+
min-width: 0;
|
|
142
137
|
`;
|
|
143
138
|
|
|
144
139
|
const ItemIconContainer = styled.div`
|
|
@@ -9,7 +9,7 @@ import styled from 'styled-components';
|
|
|
9
9
|
import { ScrollableContent } from '../../shared/ScrollableContent/ScrollableContent';
|
|
10
10
|
import { StoreCharacterSkinRow } from '../StoreCharacterSkinRow';
|
|
11
11
|
import { StoreItemRow } from '../StoreItemRow';
|
|
12
|
-
import {
|
|
12
|
+
import { SegmentedToggle } from '../../shared/SegmentedToggle';
|
|
13
13
|
import { SearchBar } from '../../shared/SearchBar/SearchBar';
|
|
14
14
|
import { useStoreFiltering } from '../../../hooks/useStoreFiltering';
|
|
15
15
|
|
|
@@ -37,6 +37,7 @@ export const StoreItemsSection: React.FC<IStoreItemsSectionProps> = ({
|
|
|
37
37
|
const {
|
|
38
38
|
searchQuery,
|
|
39
39
|
setSearchQuery,
|
|
40
|
+
selectedCategory,
|
|
40
41
|
setSelectedCategory,
|
|
41
42
|
categoryOptions,
|
|
42
43
|
filteredItems,
|
|
@@ -93,13 +94,11 @@ export const StoreItemsSection: React.FC<IStoreItemsSectionProps> = ({
|
|
|
93
94
|
placeholder="Search items..."
|
|
94
95
|
/>
|
|
95
96
|
</SearchBarContainer>
|
|
96
|
-
<
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
/>
|
|
102
|
-
</DropdownContainer>
|
|
97
|
+
<SegmentedToggle
|
|
98
|
+
options={categoryOptions.map(opt => ({ id: opt.value, label: opt.option }))}
|
|
99
|
+
activeId={selectedCategory}
|
|
100
|
+
onChange={id => setSelectedCategory(id as ItemType | 'all')}
|
|
101
|
+
/>
|
|
103
102
|
</SearchHeader>
|
|
104
103
|
|
|
105
104
|
<ScrollableContent
|
|
@@ -130,8 +129,3 @@ const SearchHeader = styled.div`
|
|
|
130
129
|
const SearchBarContainer = styled.div`
|
|
131
130
|
flex: 0.75;
|
|
132
131
|
`;
|
|
133
|
-
|
|
134
|
-
const DropdownContainer = styled.div`
|
|
135
|
-
flex: 0.25;
|
|
136
|
-
min-width: 140px;
|
|
137
|
-
`;
|
|
@@ -3,22 +3,76 @@ import React, { useCallback } from 'react';
|
|
|
3
3
|
import { FaCartPlus } from 'react-icons/fa';
|
|
4
4
|
import styled from 'styled-components';
|
|
5
5
|
import { CTAButton } from '../../shared/CTAButton/CTAButton';
|
|
6
|
+
import { ItemRowWrapper } from '../../shared/ItemRowWrapper';
|
|
6
7
|
import { SpriteFromAtlas } from '../../shared/SpriteFromAtlas';
|
|
7
8
|
import { ScrollableContent } from '../../shared/ScrollableContent/ScrollableContent';
|
|
9
|
+
import { SelectArrow } from '../../Arrow/SelectArrow';
|
|
8
10
|
import { usePackFiltering } from '../../../hooks/usePackFiltering';
|
|
11
|
+
import { useQuantityControl } from '../../../hooks/useQuantityControl';
|
|
9
12
|
|
|
10
13
|
interface IStorePacksSectionProps {
|
|
11
14
|
packs: IItemPack[];
|
|
12
|
-
onAddToCart: (pack: IItemPack) => void;
|
|
15
|
+
onAddToCart: (pack: IItemPack, quantity: number) => void;
|
|
13
16
|
onSelectPack?: (pack: IItemPack) => void;
|
|
14
17
|
atlasJSON?: any;
|
|
15
18
|
atlasIMG?: string;
|
|
16
19
|
}
|
|
17
20
|
|
|
21
|
+
interface IPackRowItemProps {
|
|
22
|
+
pack: IItemPack;
|
|
23
|
+
onAddToCart: (pack: IItemPack, quantity: number) => void;
|
|
24
|
+
renderPackIcon: (pack: IItemPack) => React.ReactNode;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const PackRowItem: React.FC<IPackRowItemProps> = ({ pack, onAddToCart, renderPackIcon }) => {
|
|
28
|
+
const { quantity, handleQuantityChange, handleBlur, incrementQuantity, decrementQuantity, resetQuantity } = useQuantityControl();
|
|
29
|
+
|
|
30
|
+
const handleAdd = () => {
|
|
31
|
+
onAddToCart(pack, quantity);
|
|
32
|
+
resetQuantity();
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<PackRow>
|
|
37
|
+
<LeftSection>
|
|
38
|
+
<PackIconContainer>
|
|
39
|
+
{renderPackIcon(pack)}
|
|
40
|
+
</PackIconContainer>
|
|
41
|
+
|
|
42
|
+
<PackDetails>
|
|
43
|
+
<PackName>{pack.title}</PackName>
|
|
44
|
+
<PackPrice>${pack.priceUSD}</PackPrice>
|
|
45
|
+
{pack.description && <PackDescription>{pack.description}</PackDescription>}
|
|
46
|
+
</PackDetails>
|
|
47
|
+
</LeftSection>
|
|
48
|
+
|
|
49
|
+
<Controls>
|
|
50
|
+
<ArrowsContainer>
|
|
51
|
+
<SelectArrow direction="left" onPointerDown={decrementQuantity} size={24} />
|
|
52
|
+
<QuantityInput
|
|
53
|
+
type="number"
|
|
54
|
+
value={quantity}
|
|
55
|
+
onChange={handleQuantityChange}
|
|
56
|
+
onBlur={handleBlur}
|
|
57
|
+
min={1}
|
|
58
|
+
max={99}
|
|
59
|
+
className="rpgui-input"
|
|
60
|
+
/>
|
|
61
|
+
<SelectArrow direction="right" onPointerDown={incrementQuantity} size={24} />
|
|
62
|
+
</ArrowsContainer>
|
|
63
|
+
<CTAButton
|
|
64
|
+
icon={<FaCartPlus />}
|
|
65
|
+
label="Add"
|
|
66
|
+
onClick={handleAdd}
|
|
67
|
+
/>
|
|
68
|
+
</Controls>
|
|
69
|
+
</PackRow>
|
|
70
|
+
);
|
|
71
|
+
};
|
|
72
|
+
|
|
18
73
|
export const StorePacksSection: React.FC<IStorePacksSectionProps> = ({
|
|
19
74
|
packs,
|
|
20
75
|
onAddToCart,
|
|
21
|
-
onSelectPack,
|
|
22
76
|
atlasJSON,
|
|
23
77
|
atlasIMG,
|
|
24
78
|
}) => {
|
|
@@ -42,30 +96,14 @@ export const StorePacksSection: React.FC<IStorePacksSectionProps> = ({
|
|
|
42
96
|
|
|
43
97
|
const renderPack = useCallback(
|
|
44
98
|
(pack: IItemPack) => (
|
|
45
|
-
<
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
<PackName>{pack.title}</PackName>
|
|
52
|
-
<PackPrice>${pack.priceUSD}</PackPrice>
|
|
53
|
-
{pack.description && <PackDescription>{pack.description}</PackDescription>}
|
|
54
|
-
</PackDetails>
|
|
55
|
-
|
|
56
|
-
<Controls>
|
|
57
|
-
<CTAButton
|
|
58
|
-
icon={<FaCartPlus />}
|
|
59
|
-
label="Add"
|
|
60
|
-
onClick={e => {
|
|
61
|
-
e.stopPropagation();
|
|
62
|
-
onAddToCart(pack);
|
|
63
|
-
}}
|
|
64
|
-
/>
|
|
65
|
-
</Controls>
|
|
66
|
-
</PackRow>
|
|
99
|
+
<PackRowItem
|
|
100
|
+
key={pack.key}
|
|
101
|
+
pack={pack}
|
|
102
|
+
onAddToCart={onAddToCart}
|
|
103
|
+
renderPackIcon={renderPackIcon}
|
|
104
|
+
/>
|
|
67
105
|
),
|
|
68
|
-
[
|
|
106
|
+
[onAddToCart, renderPackIcon]
|
|
69
107
|
);
|
|
70
108
|
|
|
71
109
|
return (
|
|
@@ -84,21 +122,14 @@ export const StorePacksSection: React.FC<IStorePacksSectionProps> = ({
|
|
|
84
122
|
);
|
|
85
123
|
};
|
|
86
124
|
|
|
87
|
-
const PackRow = styled
|
|
125
|
+
const PackRow = styled(ItemRowWrapper)``;
|
|
126
|
+
|
|
127
|
+
const LeftSection = styled.div`
|
|
88
128
|
display: flex;
|
|
89
129
|
align-items: center;
|
|
90
130
|
gap: 0.75rem;
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
cursor: pointer;
|
|
94
|
-
|
|
95
|
-
&:last-child {
|
|
96
|
-
border-bottom: none;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
&:hover {
|
|
100
|
-
background: rgba(255, 255, 255, 0.04);
|
|
101
|
-
}
|
|
131
|
+
flex: 1;
|
|
132
|
+
min-width: 0;
|
|
102
133
|
`;
|
|
103
134
|
|
|
104
135
|
const PackIconContainer = styled.div`
|
|
@@ -146,5 +177,32 @@ const PackDescription = styled.div`
|
|
|
146
177
|
const Controls = styled.div`
|
|
147
178
|
display: flex;
|
|
148
179
|
align-items: center;
|
|
180
|
+
gap: 0.5rem;
|
|
149
181
|
flex-shrink: 0;
|
|
150
182
|
`;
|
|
183
|
+
|
|
184
|
+
const ArrowsContainer = styled.div`
|
|
185
|
+
position: relative;
|
|
186
|
+
display: flex;
|
|
187
|
+
align-items: center;
|
|
188
|
+
width: 120px;
|
|
189
|
+
height: 42px;
|
|
190
|
+
justify-content: space-between;
|
|
191
|
+
`;
|
|
192
|
+
|
|
193
|
+
const QuantityInput = styled.input`
|
|
194
|
+
width: 40px;
|
|
195
|
+
text-align: center;
|
|
196
|
+
margin: 0 auto;
|
|
197
|
+
font-size: 0.875rem;
|
|
198
|
+
background: rgba(0, 0, 0, 0.2);
|
|
199
|
+
color: #ffffff;
|
|
200
|
+
border: none;
|
|
201
|
+
padding: 0.25rem;
|
|
202
|
+
|
|
203
|
+
&::-webkit-inner-spin-button,
|
|
204
|
+
&::-webkit-outer-spin-button {
|
|
205
|
+
-webkit-appearance: none;
|
|
206
|
+
margin: 0;
|
|
207
|
+
}
|
|
208
|
+
`;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
|
|
3
|
+
export const ItemRowWrapper = styled.div<{ $isHighlighted?: boolean }>`
|
|
4
|
+
display: flex;
|
|
5
|
+
align-items: center;
|
|
6
|
+
justify-content: space-between;
|
|
7
|
+
padding: 0.6rem 1rem;
|
|
8
|
+
margin-bottom: 4px;
|
|
9
|
+
background: ${p => p.$isHighlighted ? 'rgba(255, 215, 0, 0.08)' : 'rgba(0, 0, 0, 0.25)'};
|
|
10
|
+
border: 1px solid rgba(255, 255, 255, 0.05);
|
|
11
|
+
border-radius: 6px;
|
|
12
|
+
border-left: 4px solid ${p => p.$isHighlighted ? '#ffd700' : 'transparent'};
|
|
13
|
+
transition: all 0.2s ease-in-out;
|
|
14
|
+
|
|
15
|
+
&:hover {
|
|
16
|
+
background: rgba(245, 158, 11, 0.08);
|
|
17
|
+
border-color: rgba(245, 158, 11, 0.2);
|
|
18
|
+
border-left-color: #f59e0b;
|
|
19
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
|
20
|
+
transform: translateY(-1px);
|
|
21
|
+
}
|
|
22
|
+
`;
|
|
@@ -237,8 +237,9 @@ export const Default: Story = {
|
|
|
237
237
|
atlasIMG={itemsAtlasIMG}
|
|
238
238
|
hidePremiumTab={true}
|
|
239
239
|
tabOrder={['items', 'packs']}
|
|
240
|
-
defaultActiveTab="
|
|
240
|
+
defaultActiveTab="packs"
|
|
241
241
|
textInputItemKeys={['original-greater-life-potion-2', 'original-angelic-sword-1', 'character-name-change']}
|
|
242
|
+
packsBadge="SAVE"
|
|
242
243
|
/>
|
|
243
244
|
),
|
|
244
245
|
};
|
|
@@ -43,13 +43,26 @@ const mockYourBuyOrders: IMarketplaceBuyOrderItem[] = [
|
|
|
43
43
|
];
|
|
44
44
|
|
|
45
45
|
const mockOpenBuyOrders: IMarketplaceBuyOrderItem[] = [
|
|
46
|
+
// Abyssal Tide Staff — single request
|
|
46
47
|
{ _id: 'obo-1', owner: 'player-2', itemBlueprintKey: 'items/abyssal-tide-staff', itemRarity: 'Epic', maxPrice: 1500, escrowedGold: 1500, fee: 75, status: MarketplaceBuyOrderStatus.Active, createdAt: daysAgo(1), updatedAt: daysAgo(1) },
|
|
48
|
+
// Wooden Shield — single request
|
|
47
49
|
{ _id: 'obo-2', owner: 'player-3', itemBlueprintKey: 'items/wooden-shield', maxPrice: 200, escrowedGold: 200, fee: 10, status: MarketplaceBuyOrderStatus.Active, createdAt: daysAgo(5), updatedAt: daysAgo(5) },
|
|
48
|
-
|
|
49
|
-
{ _id: 'obo-
|
|
50
|
+
// Fire Wand — 2 requests (grouped)
|
|
51
|
+
{ _id: 'obo-3a', owner: 'player-4', itemBlueprintKey: 'items/fire-wand', itemRarity: 'Rare', maxPrice: 800, escrowedGold: 800, fee: 40, status: MarketplaceBuyOrderStatus.Active, createdAt: daysAgo(2), updatedAt: daysAgo(2) },
|
|
52
|
+
{ _id: 'obo-3b', owner: 'player-10', itemBlueprintKey: 'items/fire-wand', maxPrice: 550, escrowedGold: 550, fee: 27, status: MarketplaceBuyOrderStatus.Active, createdAt: daysAgo(4), updatedAt: daysAgo(4) },
|
|
53
|
+
// Broad Sword — 3 requests (grouped)
|
|
54
|
+
{ _id: 'obo-4a', owner: 'player-5', itemBlueprintKey: 'items/broad-sword', maxPrice: 350, escrowedGold: 350, fee: 17, status: MarketplaceBuyOrderStatus.Active, createdAt: daysAgo(3), updatedAt: daysAgo(3) },
|
|
55
|
+
{ _id: 'obo-4b', owner: 'player-11', itemBlueprintKey: 'items/broad-sword', itemRarity: 'Rare', maxPrice: 480, escrowedGold: 480, fee: 24, status: MarketplaceBuyOrderStatus.Active, createdAt: daysAgo(1), updatedAt: daysAgo(1) },
|
|
56
|
+
{ _id: 'obo-4c', owner: 'player-12', itemBlueprintKey: 'items/broad-sword', maxPrice: 270, escrowedGold: 270, fee: 13, status: MarketplaceBuyOrderStatus.Active, createdAt: daysAgo(6), updatedAt: daysAgo(6) },
|
|
57
|
+
// Barbarian Helmet — single request
|
|
50
58
|
{ _id: 'obo-5', owner: 'player-6', itemBlueprintKey: 'items/barbarian-helmet', itemRarity: 'Uncommon', maxPrice: 600, escrowedGold: 600, fee: 30, status: MarketplaceBuyOrderStatus.Active, createdAt: daysAgo(1), updatedAt: daysAgo(1) },
|
|
51
|
-
|
|
52
|
-
{ _id: 'obo-
|
|
59
|
+
// Leather Armor — 2 requests (grouped)
|
|
60
|
+
{ _id: 'obo-6a', owner: 'player-7', itemBlueprintKey: 'items/leather-armor', maxPrice: 420, escrowedGold: 420, fee: 21, status: MarketplaceBuyOrderStatus.Active, createdAt: daysAgo(4), updatedAt: daysAgo(4) },
|
|
61
|
+
{ _id: 'obo-6b', owner: 'player-13', itemBlueprintKey: 'items/leather-armor', itemRarity: 'Uncommon', maxPrice: 380, escrowedGold: 380, fee: 19, status: MarketplaceBuyOrderStatus.Active, createdAt: daysAgo(2), updatedAt: daysAgo(2) },
|
|
62
|
+
// Angelic Sword — 2 requests (grouped)
|
|
63
|
+
{ _id: 'obo-7a', owner: 'player-8', itemBlueprintKey: 'items/angelic-sword', itemRarity: 'Legendary', maxPrice: 5000, escrowedGold: 5000, fee: 250, status: MarketplaceBuyOrderStatus.Active, createdAt: daysAgo(6), updatedAt: daysAgo(6) },
|
|
64
|
+
{ _id: 'obo-7b', owner: 'player-14', itemBlueprintKey: 'items/angelic-sword', itemRarity: 'Epic', maxPrice: 3500, escrowedGold: 3500, fee: 175, status: MarketplaceBuyOrderStatus.Active, createdAt: daysAgo(3), updatedAt: daysAgo(3) },
|
|
65
|
+
// Greater Life Potion — single request
|
|
53
66
|
{ _id: 'obo-8', owner: 'player-9', itemBlueprintKey: 'items/greater-life-potion', maxPrice: 90, stackQty: 10, escrowedGold: 900, fee: 45, status: MarketplaceBuyOrderStatus.Active, createdAt: daysAgo(2), updatedAt: daysAgo(2) },
|
|
54
67
|
];
|
|
55
68
|
|
|
@@ -169,7 +182,7 @@ const Template: Story = () => {
|
|
|
169
182
|
onYourBuyOrdersPageChange={p => console.log('your orders page:', p)}
|
|
170
183
|
onCancelBuyOrder={id => setYourBuyOrders(prev => prev.filter(o => o._id !== id))}
|
|
171
184
|
openBuyOrders={mockOpenBuyOrders}
|
|
172
|
-
openBuyOrdersTotal={mockOpenBuyOrders.length}
|
|
185
|
+
openBuyOrdersTotal={mockOpenBuyOrders.length}
|
|
173
186
|
openBuyOrdersPage={1}
|
|
174
187
|
onOpenBuyOrdersPageChange={(p: number) => console.log('open orders page:', p)}
|
|
175
188
|
// Blueprint Search props
|