@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
package/package.json
CHANGED
|
@@ -3,6 +3,7 @@ import { Delete } from 'pixelarticons/react/Delete';
|
|
|
3
3
|
import { ShoppingBag } from 'pixelarticons/react/ShoppingBag';
|
|
4
4
|
import React from 'react';
|
|
5
5
|
import styled from 'styled-components';
|
|
6
|
+
import { GroupedRowContainer } from './GroupedRowContainer';
|
|
6
7
|
import { uiColors } from '../../constants/uiColors';
|
|
7
8
|
import { uiFonts } from '../../constants/uiFonts';
|
|
8
9
|
import { resolveAtlasSpriteKey } from '../../utils/atlasUtils';
|
|
@@ -129,15 +130,6 @@ export const BuyOrderRow: React.FC<IBuyOrderRowProps> = ({
|
|
|
129
130
|
{requestTagLabel}
|
|
130
131
|
</LabelPill>
|
|
131
132
|
)}
|
|
132
|
-
{buyOrder.itemRarity && (
|
|
133
|
-
<LabelPill
|
|
134
|
-
background={rarityGlow ?? 'rgba(255,255,255,0.1)'}
|
|
135
|
-
borderColor="rgba(255,255,255,0.08)"
|
|
136
|
-
color="#fff"
|
|
137
|
-
>
|
|
138
|
-
{buyOrder.itemRarity}
|
|
139
|
-
</LabelPill>
|
|
140
|
-
)}
|
|
141
133
|
{isOwn && (
|
|
142
134
|
<LabelPill
|
|
143
135
|
background={STATUS_COLORS[buyOrder.status]}
|
|
@@ -148,13 +140,15 @@ export const BuyOrderRow: React.FC<IBuyOrderRowProps> = ({
|
|
|
148
140
|
</LabelPill>
|
|
149
141
|
)}
|
|
150
142
|
{timeRemaining && (
|
|
151
|
-
<
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
143
|
+
<SimpleTooltip content="Expires in" direction="top">
|
|
144
|
+
<LabelPill
|
|
145
|
+
background="rgba(255,255,255,0.08)"
|
|
146
|
+
borderColor="rgba(255,255,255,0.08)"
|
|
147
|
+
color="#fff"
|
|
148
|
+
>
|
|
149
|
+
{timeRemaining}
|
|
150
|
+
</LabelPill>
|
|
151
|
+
</SimpleTooltip>
|
|
158
152
|
)}
|
|
159
153
|
</MetaRow>
|
|
160
154
|
</ItemDetails>
|
|
@@ -185,6 +179,49 @@ export const BuyOrderRow: React.FC<IBuyOrderRowProps> = ({
|
|
|
185
179
|
);
|
|
186
180
|
};
|
|
187
181
|
|
|
182
|
+
export interface IGroupedBuyOrderRowProps {
|
|
183
|
+
bestOrder: IMarketplaceBuyOrderItem;
|
|
184
|
+
otherOrders: IMarketplaceBuyOrderItem[];
|
|
185
|
+
atlasJSON?: any;
|
|
186
|
+
atlasIMG?: any;
|
|
187
|
+
isOwn?: boolean;
|
|
188
|
+
onCancel?: (buyOrderId: string) => void;
|
|
189
|
+
onFulfill?: (buyOrderId: string) => void;
|
|
190
|
+
showRequestTag?: boolean;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export const GroupedBuyOrderRow: React.FC<IGroupedBuyOrderRowProps> = ({
|
|
194
|
+
bestOrder,
|
|
195
|
+
otherOrders,
|
|
196
|
+
atlasJSON,
|
|
197
|
+
atlasIMG,
|
|
198
|
+
isOwn,
|
|
199
|
+
onCancel,
|
|
200
|
+
onFulfill,
|
|
201
|
+
showRequestTag,
|
|
202
|
+
}) => {
|
|
203
|
+
const makeRow = (order: IMarketplaceBuyOrderItem) => (
|
|
204
|
+
<BuyOrderRow
|
|
205
|
+
key={order._id}
|
|
206
|
+
buyOrder={order}
|
|
207
|
+
atlasJSON={atlasJSON}
|
|
208
|
+
atlasIMG={atlasIMG}
|
|
209
|
+
isOwn={isOwn}
|
|
210
|
+
onCancel={onCancel}
|
|
211
|
+
onFulfill={onFulfill}
|
|
212
|
+
showRequestTag={showRequestTag}
|
|
213
|
+
/>
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
return (
|
|
217
|
+
<GroupedRowContainer
|
|
218
|
+
mainRow={makeRow(bestOrder)}
|
|
219
|
+
subRows={otherOrders.map(makeRow)}
|
|
220
|
+
badgeLabel="requests"
|
|
221
|
+
/>
|
|
222
|
+
);
|
|
223
|
+
};
|
|
224
|
+
|
|
188
225
|
// ── Styled components matching MarketplaceRows layout ──
|
|
189
226
|
|
|
190
227
|
const RowWrapper = styled.div`
|
|
@@ -14,7 +14,7 @@ import { Dropdown } from '../Dropdown';
|
|
|
14
14
|
import { Input } from '../Input';
|
|
15
15
|
import { SegmentedToggle } from '../shared/SegmentedToggle';
|
|
16
16
|
import { Pager } from '../Pager';
|
|
17
|
-
import {
|
|
17
|
+
import { GroupedBuyOrderRow } from './BuyOrderRows';
|
|
18
18
|
import { MarketplaceBuyModal, MarketplacePaymentMethod } from './MarketplaceBuyModal';
|
|
19
19
|
import { GroupedMarketplaceRow } from './MarketplaceRows';
|
|
20
20
|
import { itemRarityOptions, itemTypeOptions, orderByOptions } from './filters';
|
|
@@ -173,11 +173,29 @@ export const BuyPanel: React.FC<IBuyPanelProps> = ({
|
|
|
173
173
|
});
|
|
174
174
|
}, [name, openBuyOrders, price, selectedRarity]);
|
|
175
175
|
|
|
176
|
+
const groupedBuyOrders = useMemo(() => {
|
|
177
|
+
const groups = new Map<string, IMarketplaceBuyOrderItem[]>();
|
|
178
|
+
for (const order of visibleBuyOrders) {
|
|
179
|
+
const key = order.itemBlueprintKey;
|
|
180
|
+
if (!groups.has(key)) groups.set(key, []);
|
|
181
|
+
groups.get(key)!.push(order);
|
|
182
|
+
}
|
|
183
|
+
return Array.from(groups.values())
|
|
184
|
+
.map(group => {
|
|
185
|
+
const sorted = [...group].sort((a, b) => b.maxPrice - a.maxPrice);
|
|
186
|
+
return { bestOrder: sorted[0], otherOrders: sorted.slice(1) };
|
|
187
|
+
})
|
|
188
|
+
.sort((a, b) => {
|
|
189
|
+
const totalGold = (g: typeof a) => [g.bestOrder, ...g.otherOrders].reduce((sum, o) => sum + o.maxPrice, 0);
|
|
190
|
+
return totalGold(b) - totalGold(a);
|
|
191
|
+
});
|
|
192
|
+
}, [visibleBuyOrders]);
|
|
193
|
+
|
|
176
194
|
const showSellSection = browseMode === 'sell';
|
|
177
195
|
const showBuySection = browseMode === 'buy';
|
|
178
196
|
const hasVisibleContent =
|
|
179
197
|
(showSellSection && groupedItems.length > 0) ||
|
|
180
|
-
(showBuySection &&
|
|
198
|
+
(showBuySection && groupedBuyOrders.length > 0);
|
|
181
199
|
|
|
182
200
|
return (
|
|
183
201
|
<>
|
|
@@ -254,12 +272,14 @@ export const BuyPanel: React.FC<IBuyPanelProps> = ({
|
|
|
254
272
|
|
|
255
273
|
{showFilters && (
|
|
256
274
|
<OptionsWrapper showFilters={showFilters}>
|
|
257
|
-
<WrapperContainer>
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
275
|
+
<WrapperContainer $sell={showSellSection}>
|
|
276
|
+
{showSellSection && (
|
|
277
|
+
<StyledDropdown
|
|
278
|
+
options={itemTypeOptions}
|
|
279
|
+
onChange={onChangeType}
|
|
280
|
+
width="100%"
|
|
281
|
+
/>
|
|
282
|
+
)}
|
|
263
283
|
<StyledDropdown
|
|
264
284
|
options={itemRarityOptions}
|
|
265
285
|
onChange={value => {
|
|
@@ -268,107 +288,113 @@ export const BuyPanel: React.FC<IBuyPanelProps> = ({
|
|
|
268
288
|
}}
|
|
269
289
|
width="100%"
|
|
270
290
|
/>
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
291
|
+
{showSellSection && (
|
|
292
|
+
<StyledDropdown
|
|
293
|
+
options={orderByOptions}
|
|
294
|
+
onChange={onChangeOrder}
|
|
295
|
+
width="100%"
|
|
296
|
+
/>
|
|
297
|
+
)}
|
|
276
298
|
</WrapperContainer>
|
|
277
299
|
|
|
278
300
|
<FilterInputsWrapper>
|
|
301
|
+
{showSellSection && (
|
|
302
|
+
<div>
|
|
303
|
+
<p>Main level</p>
|
|
304
|
+
<div className="input-group">
|
|
305
|
+
<Input
|
|
306
|
+
onChange={e => {
|
|
307
|
+
setMainLevel([Number(e.target.value), mainLevel[1]]);
|
|
308
|
+
onChangeMainLevelInput([Number(e.target.value), mainLevel[1]]);
|
|
309
|
+
}}
|
|
310
|
+
placeholder="Min"
|
|
311
|
+
type="number"
|
|
312
|
+
min={0}
|
|
313
|
+
onBlur={enableHotkeys}
|
|
314
|
+
onFocus={disableHotkeys}
|
|
315
|
+
/>
|
|
316
|
+
<AiFillCaretRight className="separator-icon" />
|
|
317
|
+
<Input
|
|
318
|
+
onChange={e => {
|
|
319
|
+
setMainLevel([mainLevel[0], Number(e.target.value)]);
|
|
320
|
+
onChangeMainLevelInput([mainLevel[0], Number(e.target.value)]);
|
|
321
|
+
}}
|
|
322
|
+
placeholder="Max"
|
|
323
|
+
type="number"
|
|
324
|
+
min={0}
|
|
325
|
+
onBlur={enableHotkeys}
|
|
326
|
+
onFocus={disableHotkeys}
|
|
327
|
+
/>
|
|
328
|
+
</div>
|
|
329
|
+
</div>
|
|
330
|
+
)}
|
|
331
|
+
|
|
332
|
+
{showSellSection && (
|
|
333
|
+
<div>
|
|
334
|
+
<p>Secondary level</p>
|
|
335
|
+
<div className="input-group">
|
|
336
|
+
<Input
|
|
337
|
+
onChange={e => {
|
|
338
|
+
setSecondaryLevel([Number(e.target.value), secondaryLevel[1]]);
|
|
339
|
+
onChangeSecondaryLevelInput([
|
|
340
|
+
Number(e.target.value),
|
|
341
|
+
secondaryLevel[1],
|
|
342
|
+
]);
|
|
343
|
+
}}
|
|
344
|
+
placeholder="Min"
|
|
345
|
+
type="number"
|
|
346
|
+
min={0}
|
|
347
|
+
onBlur={enableHotkeys}
|
|
348
|
+
onFocus={disableHotkeys}
|
|
349
|
+
/>
|
|
350
|
+
<AiFillCaretRight className="separator-icon" />
|
|
351
|
+
<Input
|
|
352
|
+
onChange={e => {
|
|
353
|
+
setSecondaryLevel([secondaryLevel[0], Number(e.target.value)]);
|
|
354
|
+
onChangeSecondaryLevelInput([
|
|
355
|
+
secondaryLevel[0],
|
|
356
|
+
Number(e.target.value),
|
|
357
|
+
]);
|
|
358
|
+
}}
|
|
359
|
+
placeholder="Max"
|
|
360
|
+
type="number"
|
|
361
|
+
min={0}
|
|
362
|
+
onBlur={enableHotkeys}
|
|
363
|
+
onFocus={disableHotkeys}
|
|
364
|
+
/>
|
|
365
|
+
</div>
|
|
366
|
+
</div>
|
|
367
|
+
)}
|
|
368
|
+
|
|
279
369
|
<div>
|
|
280
|
-
<p>
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
</div>
|
|
307
|
-
|
|
308
|
-
<div>
|
|
309
|
-
<p>Secondary level</p>
|
|
310
|
-
<div className="input-group">
|
|
311
|
-
<Input
|
|
312
|
-
onChange={e => {
|
|
313
|
-
setSecondaryLevel([Number(e.target.value), secondaryLevel[1]]);
|
|
314
|
-
onChangeSecondaryLevelInput([
|
|
315
|
-
Number(e.target.value),
|
|
316
|
-
secondaryLevel[1],
|
|
317
|
-
]);
|
|
318
|
-
}}
|
|
319
|
-
placeholder="Min"
|
|
320
|
-
type="number"
|
|
321
|
-
min={0}
|
|
322
|
-
onBlur={enableHotkeys}
|
|
323
|
-
onFocus={disableHotkeys}
|
|
324
|
-
/>
|
|
325
|
-
<AiFillCaretRight className="separator-icon" />
|
|
326
|
-
<Input
|
|
327
|
-
onChange={e => {
|
|
328
|
-
setSecondaryLevel([secondaryLevel[0], Number(e.target.value)]);
|
|
329
|
-
onChangeSecondaryLevelInput([
|
|
330
|
-
secondaryLevel[0],
|
|
331
|
-
Number(e.target.value),
|
|
332
|
-
]);
|
|
333
|
-
}}
|
|
334
|
-
placeholder="Max"
|
|
335
|
-
type="number"
|
|
336
|
-
min={0}
|
|
337
|
-
onBlur={enableHotkeys}
|
|
338
|
-
onFocus={disableHotkeys}
|
|
339
|
-
/>
|
|
340
|
-
</div>
|
|
341
|
-
</div>
|
|
342
|
-
|
|
343
|
-
<div>
|
|
344
|
-
<p>Price</p>
|
|
345
|
-
<div className="input-group">
|
|
346
|
-
<Input
|
|
347
|
-
onChange={e => {
|
|
348
|
-
setPrice([Number(e.target.value), price[1]]);
|
|
349
|
-
onChangePriceInput([Number(e.target.value), price[1]]);
|
|
350
|
-
}}
|
|
351
|
-
placeholder="Min"
|
|
352
|
-
type="number"
|
|
353
|
-
min={0}
|
|
354
|
-
onBlur={enableHotkeys}
|
|
355
|
-
onFocus={disableHotkeys}
|
|
356
|
-
/>
|
|
357
|
-
<AiFillCaretRight className="separator-icon" />
|
|
358
|
-
<Input
|
|
359
|
-
onChange={e => {
|
|
360
|
-
setPrice([price[0], Number(e.target.value)]);
|
|
361
|
-
onChangePriceInput([price[0], Number(e.target.value)]);
|
|
362
|
-
}}
|
|
363
|
-
placeholder="Max"
|
|
364
|
-
type="number"
|
|
365
|
-
min={0}
|
|
366
|
-
onBlur={enableHotkeys}
|
|
367
|
-
onFocus={disableHotkeys}
|
|
368
|
-
/>
|
|
370
|
+
<p>Price</p>
|
|
371
|
+
<div className="input-group">
|
|
372
|
+
<Input
|
|
373
|
+
onChange={e => {
|
|
374
|
+
setPrice([Number(e.target.value), price[1]]);
|
|
375
|
+
onChangePriceInput([Number(e.target.value), price[1]]);
|
|
376
|
+
}}
|
|
377
|
+
placeholder="Min"
|
|
378
|
+
type="number"
|
|
379
|
+
min={0}
|
|
380
|
+
onBlur={enableHotkeys}
|
|
381
|
+
onFocus={disableHotkeys}
|
|
382
|
+
/>
|
|
383
|
+
<AiFillCaretRight className="separator-icon" />
|
|
384
|
+
<Input
|
|
385
|
+
onChange={e => {
|
|
386
|
+
setPrice([price[0], Number(e.target.value)]);
|
|
387
|
+
onChangePriceInput([price[0], Number(e.target.value)]);
|
|
388
|
+
}}
|
|
389
|
+
placeholder="Max"
|
|
390
|
+
type="number"
|
|
391
|
+
min={0}
|
|
392
|
+
onBlur={enableHotkeys}
|
|
393
|
+
onFocus={disableHotkeys}
|
|
394
|
+
/>
|
|
395
|
+
</div>
|
|
369
396
|
</div>
|
|
370
|
-
</
|
|
371
|
-
</FilterInputsWrapper>
|
|
397
|
+
</FilterInputsWrapper>
|
|
372
398
|
</OptionsWrapper>
|
|
373
399
|
)}
|
|
374
400
|
|
|
@@ -416,19 +442,19 @@ export const BuyPanel: React.FC<IBuyPanelProps> = ({
|
|
|
416
442
|
<MarketSection>
|
|
417
443
|
<SectionHeader>
|
|
418
444
|
<SectionTitle>Buy Requests</SectionTitle>
|
|
419
|
-
<SectionMeta>{
|
|
445
|
+
<SectionMeta>{groupedBuyOrders.length} groups</SectionMeta>
|
|
420
446
|
</SectionHeader>
|
|
421
|
-
{
|
|
447
|
+
{groupedBuyOrders.length === 0 ? (
|
|
422
448
|
<SectionEmpty>No public buy requests found.</SectionEmpty>
|
|
423
449
|
) : (
|
|
424
|
-
|
|
425
|
-
<
|
|
426
|
-
key={
|
|
427
|
-
|
|
450
|
+
groupedBuyOrders.map(({ bestOrder, otherOrders }) => (
|
|
451
|
+
<GroupedBuyOrderRow
|
|
452
|
+
key={bestOrder._id}
|
|
453
|
+
bestOrder={bestOrder}
|
|
454
|
+
otherOrders={otherOrders}
|
|
428
455
|
atlasJSON={atlasJSON}
|
|
429
456
|
atlasIMG={atlasIMG}
|
|
430
457
|
onFulfill={setFulfillingBuyOrderId}
|
|
431
|
-
showRequestTag
|
|
432
458
|
/>
|
|
433
459
|
))
|
|
434
460
|
)}
|
|
@@ -532,7 +558,7 @@ const OptionsWrapper = styled.div<{ showFilters: boolean }>`
|
|
|
532
558
|
|
|
533
559
|
const FilterInputsWrapper = styled.div`
|
|
534
560
|
display: grid;
|
|
535
|
-
grid-template-columns: repeat(
|
|
561
|
+
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
|
536
562
|
gap: 15px;
|
|
537
563
|
color: white;
|
|
538
564
|
|
|
@@ -567,9 +593,9 @@ const FilterInputsWrapper = styled.div`
|
|
|
567
593
|
}
|
|
568
594
|
`;
|
|
569
595
|
|
|
570
|
-
const WrapperContainer = styled.div
|
|
596
|
+
const WrapperContainer = styled.div<{ $sell: boolean }>`
|
|
571
597
|
display: grid;
|
|
572
|
-
grid-template-columns:
|
|
598
|
+
grid-template-columns: ${({ $sell }) => $sell ? 'repeat(3, 1fr)' : 'minmax(0, 200px)'};
|
|
573
599
|
gap: 15px;
|
|
574
600
|
|
|
575
601
|
.rpgui-content .rpgui-dropdown-imp-header {
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import styled from 'styled-components';
|
|
3
|
+
|
|
4
|
+
export interface IGroupedRowContainerProps {
|
|
5
|
+
mainRow: React.ReactNode;
|
|
6
|
+
subRows: React.ReactNode[];
|
|
7
|
+
badgeLabel?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const GroupedRowContainer: React.FC<IGroupedRowContainerProps> = ({
|
|
11
|
+
mainRow,
|
|
12
|
+
subRows,
|
|
13
|
+
badgeLabel = 'offers',
|
|
14
|
+
}) => {
|
|
15
|
+
const [expanded, setExpanded] = useState(false);
|
|
16
|
+
const hasMultiple = subRows.length > 0;
|
|
17
|
+
const totalCount = subRows.length + 1;
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<GroupWrapper>
|
|
21
|
+
<GroupHeader $clickable={hasMultiple} onClick={hasMultiple ? () => setExpanded(e => !e) : undefined}>
|
|
22
|
+
{mainRow}
|
|
23
|
+
{hasMultiple && (
|
|
24
|
+
<GroupMeta>
|
|
25
|
+
<OfferBadge>{totalCount} {badgeLabel}</OfferBadge>
|
|
26
|
+
<Chevron $expanded={expanded}>▸</Chevron>
|
|
27
|
+
</GroupMeta>
|
|
28
|
+
)}
|
|
29
|
+
</GroupHeader>
|
|
30
|
+
|
|
31
|
+
{expanded && (
|
|
32
|
+
<SubRows>
|
|
33
|
+
{subRows}
|
|
34
|
+
</SubRows>
|
|
35
|
+
)}
|
|
36
|
+
</GroupWrapper>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const GroupWrapper = styled.div`
|
|
41
|
+
margin-bottom: 2px;
|
|
42
|
+
`;
|
|
43
|
+
|
|
44
|
+
const GroupHeader = styled.div<{ $clickable: boolean }>`
|
|
45
|
+
position: relative;
|
|
46
|
+
cursor: ${({ $clickable }) => ($clickable ? 'pointer' : 'default')};
|
|
47
|
+
`;
|
|
48
|
+
|
|
49
|
+
const GroupMeta = styled.div`
|
|
50
|
+
position: absolute;
|
|
51
|
+
right: 180px;
|
|
52
|
+
top: 50%;
|
|
53
|
+
transform: translateY(-50%);
|
|
54
|
+
display: flex;
|
|
55
|
+
align-items: center;
|
|
56
|
+
gap: 6px;
|
|
57
|
+
pointer-events: none;
|
|
58
|
+
`;
|
|
59
|
+
|
|
60
|
+
const OfferBadge = styled.span`
|
|
61
|
+
font-family: 'Press Start 2P', cursive;
|
|
62
|
+
font-size: 0.5rem;
|
|
63
|
+
color: rgba(255, 255, 255, 0.5);
|
|
64
|
+
background: rgba(255, 255, 255, 0.08);
|
|
65
|
+
padding: 2px 6px;
|
|
66
|
+
border-radius: 8px;
|
|
67
|
+
white-space: nowrap;
|
|
68
|
+
`;
|
|
69
|
+
|
|
70
|
+
const Chevron = styled.span<{ $expanded: boolean }>`
|
|
71
|
+
display: inline-block;
|
|
72
|
+
font-size: 0.7rem;
|
|
73
|
+
color: rgba(255, 255, 255, 0.4);
|
|
74
|
+
transition: transform 0.2s ease;
|
|
75
|
+
transform: rotate(${({ $expanded }) => ($expanded ? '90deg' : '0deg')});
|
|
76
|
+
`;
|
|
77
|
+
|
|
78
|
+
const SubRows = styled.div`
|
|
79
|
+
margin-left: 12px;
|
|
80
|
+
padding-left: 8px;
|
|
81
|
+
border-left: 2px solid rgba(245, 158, 11, 0.25);
|
|
82
|
+
|
|
83
|
+
> div > div {
|
|
84
|
+
background: rgba(0, 0, 0, 0.4);
|
|
85
|
+
}
|
|
86
|
+
`;
|
|
@@ -7,8 +7,10 @@ import {
|
|
|
7
7
|
} from '@rpg-engine/shared';
|
|
8
8
|
import { Coins } from 'pixelarticons/react/Coins';
|
|
9
9
|
import { Delete } from 'pixelarticons/react/Delete';
|
|
10
|
-
import React
|
|
10
|
+
import React from 'react';
|
|
11
11
|
import styled from 'styled-components';
|
|
12
|
+
import { GroupedRowContainer } from './GroupedRowContainer';
|
|
13
|
+
import { ItemRowWrapper } from '../shared/ItemRowWrapper';
|
|
12
14
|
import { uiFonts } from '../../constants/uiFonts';
|
|
13
15
|
import { ItemInfoWrapper } from '../Item/Cards/ItemInfoWrapper';
|
|
14
16
|
import { onRenderGems } from '../Item/Inventory/ItemGem';
|
|
@@ -50,7 +52,7 @@ export const MarketplaceRows: React.FC<IMarketPlaceRowsPropos> = ({
|
|
|
50
52
|
};
|
|
51
53
|
|
|
52
54
|
return (
|
|
53
|
-
<
|
|
55
|
+
<ItemRowWrapper>
|
|
54
56
|
<ItemSection>
|
|
55
57
|
<SpriteContainer>
|
|
56
58
|
<ItemInfoWrapper
|
|
@@ -138,7 +140,7 @@ export const MarketplaceRows: React.FC<IMarketPlaceRowsPropos> = ({
|
|
|
138
140
|
iconColor={onMarketPlaceItemBuy ? '#f59e0b' : '#ef4444'}
|
|
139
141
|
/>
|
|
140
142
|
</ActionSection>
|
|
141
|
-
</
|
|
143
|
+
</ItemRowWrapper>
|
|
142
144
|
);
|
|
143
145
|
};
|
|
144
146
|
|
|
@@ -167,133 +169,30 @@ export const GroupedMarketplaceRow: React.FC<IGroupedMarketplaceRowProps> = ({
|
|
|
167
169
|
onBuy,
|
|
168
170
|
onDCCoinClick,
|
|
169
171
|
}) => {
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
172
|
+
const makeRow = (listing: IMarketplaceItem) => (
|
|
173
|
+
<MarketplaceRows
|
|
174
|
+
key={listing._id}
|
|
175
|
+
atlasIMG={atlasIMG}
|
|
176
|
+
atlasJSON={atlasJSON}
|
|
177
|
+
item={listing.item}
|
|
178
|
+
itemPrice={listing.price}
|
|
179
|
+
dcEquivalentPrice={dcToGoldSwapRate > 0 ? getDCEquivalentPrice(listing.price) : undefined}
|
|
180
|
+
equipmentSet={equipmentSet}
|
|
181
|
+
onMarketPlaceItemBuy={() => onBuy(listing._id)}
|
|
182
|
+
onDCCoinClick={onDCCoinClick}
|
|
183
|
+
disabled={listing.owner === characterId}
|
|
184
|
+
/>
|
|
185
|
+
);
|
|
173
186
|
|
|
174
187
|
return (
|
|
175
|
-
<
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
<MarketplaceRows
|
|
181
|
-
atlasIMG={atlasIMG}
|
|
182
|
-
atlasJSON={atlasJSON}
|
|
183
|
-
item={bestListing.item}
|
|
184
|
-
itemPrice={bestListing.price}
|
|
185
|
-
dcEquivalentPrice={
|
|
186
|
-
dcToGoldSwapRate > 0
|
|
187
|
-
? getDCEquivalentPrice(bestListing.price)
|
|
188
|
-
: undefined
|
|
189
|
-
}
|
|
190
|
-
equipmentSet={equipmentSet}
|
|
191
|
-
onMarketPlaceItemBuy={() => onBuy(bestListing._id)}
|
|
192
|
-
onDCCoinClick={onDCCoinClick}
|
|
193
|
-
disabled={bestListing.owner === characterId}
|
|
194
|
-
/>
|
|
195
|
-
{hasMultiple && (
|
|
196
|
-
<GroupMeta>
|
|
197
|
-
<OfferBadge>{totalOffers} offers</OfferBadge>
|
|
198
|
-
<Chevron expanded={expanded}>▸</Chevron>
|
|
199
|
-
</GroupMeta>
|
|
200
|
-
)}
|
|
201
|
-
</GroupHeader>
|
|
202
|
-
|
|
203
|
-
{expanded && (
|
|
204
|
-
<SubRows>
|
|
205
|
-
{otherListings.map((listing) => (
|
|
206
|
-
<MarketplaceRows
|
|
207
|
-
key={listing._id}
|
|
208
|
-
atlasIMG={atlasIMG}
|
|
209
|
-
atlasJSON={atlasJSON}
|
|
210
|
-
item={listing.item}
|
|
211
|
-
itemPrice={listing.price}
|
|
212
|
-
dcEquivalentPrice={
|
|
213
|
-
dcToGoldSwapRate > 0
|
|
214
|
-
? getDCEquivalentPrice(listing.price)
|
|
215
|
-
: undefined
|
|
216
|
-
}
|
|
217
|
-
equipmentSet={equipmentSet}
|
|
218
|
-
onMarketPlaceItemBuy={() => onBuy(listing._id)}
|
|
219
|
-
onDCCoinClick={onDCCoinClick}
|
|
220
|
-
disabled={listing.owner === characterId}
|
|
221
|
-
/>
|
|
222
|
-
))}
|
|
223
|
-
</SubRows>
|
|
224
|
-
)}
|
|
225
|
-
</GroupWrapper>
|
|
188
|
+
<GroupedRowContainer
|
|
189
|
+
mainRow={makeRow(bestListing)}
|
|
190
|
+
subRows={otherListings.map(makeRow)}
|
|
191
|
+
badgeLabel="offers"
|
|
192
|
+
/>
|
|
226
193
|
);
|
|
227
194
|
};
|
|
228
195
|
|
|
229
|
-
const GroupWrapper = styled.div`
|
|
230
|
-
margin-bottom: 2px;
|
|
231
|
-
`;
|
|
232
|
-
|
|
233
|
-
const GroupHeader = styled.div<{ clickable: boolean }>`
|
|
234
|
-
position: relative;
|
|
235
|
-
cursor: ${({ clickable }) => (clickable ? 'pointer' : 'default')};
|
|
236
|
-
`;
|
|
237
|
-
|
|
238
|
-
const GroupMeta = styled.div`
|
|
239
|
-
position: absolute;
|
|
240
|
-
right: 180px;
|
|
241
|
-
top: 50%;
|
|
242
|
-
transform: translateY(-50%);
|
|
243
|
-
display: flex;
|
|
244
|
-
align-items: center;
|
|
245
|
-
gap: 6px;
|
|
246
|
-
pointer-events: none;
|
|
247
|
-
`;
|
|
248
|
-
|
|
249
|
-
const OfferBadge = styled.span`
|
|
250
|
-
font-family: 'Press Start 2P', cursive;
|
|
251
|
-
font-size: 0.5rem;
|
|
252
|
-
color: rgba(255, 255, 255, 0.5);
|
|
253
|
-
background: rgba(255, 255, 255, 0.08);
|
|
254
|
-
padding: 2px 6px;
|
|
255
|
-
border-radius: 8px;
|
|
256
|
-
white-space: nowrap;
|
|
257
|
-
`;
|
|
258
|
-
|
|
259
|
-
const Chevron = styled.span<{ expanded: boolean }>`
|
|
260
|
-
display: inline-block;
|
|
261
|
-
font-size: 0.7rem;
|
|
262
|
-
color: rgba(255, 255, 255, 0.4);
|
|
263
|
-
transition: transform 0.2s ease;
|
|
264
|
-
transform: rotate(${({ expanded }) => (expanded ? '90deg' : '0deg')});
|
|
265
|
-
`;
|
|
266
|
-
|
|
267
|
-
const SubRows = styled.div`
|
|
268
|
-
margin-left: 12px;
|
|
269
|
-
padding-left: 8px;
|
|
270
|
-
border-left: 2px solid rgba(245, 158, 11, 0.25);
|
|
271
|
-
|
|
272
|
-
> div > div {
|
|
273
|
-
background: rgba(0, 0, 0, 0.4);
|
|
274
|
-
}
|
|
275
|
-
`;
|
|
276
|
-
|
|
277
|
-
const MarketplaceWrapper = styled.div`
|
|
278
|
-
display: flex;
|
|
279
|
-
align-items: center;
|
|
280
|
-
justify-content: space-between;
|
|
281
|
-
padding: 0.6rem 1rem;
|
|
282
|
-
margin-bottom: 4px;
|
|
283
|
-
background: rgba(0, 0, 0, 0.25);
|
|
284
|
-
border: 1px solid rgba(255, 255, 255, 0.05);
|
|
285
|
-
border-radius: 6px;
|
|
286
|
-
border-left: 4px solid transparent;
|
|
287
|
-
transition: all 0.2s ease-in-out;
|
|
288
|
-
|
|
289
|
-
&:hover {
|
|
290
|
-
background: rgba(245, 158, 11, 0.08);
|
|
291
|
-
border-color: rgba(245, 158, 11, 0.2);
|
|
292
|
-
border-left-color: #f59e0b;
|
|
293
|
-
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
|
294
|
-
transform: translateY(-1px);
|
|
295
|
-
}
|
|
296
|
-
`;
|
|
297
196
|
|
|
298
197
|
const ItemSection = styled.div`
|
|
299
198
|
display: flex;
|