@rpg-engine/long-bow 0.8.160 → 0.8.162
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/Pager.d.ts +1 -0
- package/dist/components/shared/ItemRowWrapper.d.ts +3 -0
- package/dist/long-bow.cjs.development.js +336 -203
- 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 +336 -204
- package/dist/long-bow.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/components/Marketplace/BlueprintSearchModal.tsx +68 -29
- package/src/components/Marketplace/BlueprintTable.tsx +30 -21
- 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/Pager.tsx +13 -5
- package/src/components/Store/Store.tsx +22 -18
- package/src/components/Store/StoreItemRow.tsx +28 -33
- package/src/components/Store/sections/StoreItemsSection.tsx +7 -13
- package/src/components/Store/sections/StorePacksSection.tsx +19 -21
- package/src/components/shared/ItemRowWrapper.tsx +22 -0
- package/src/stories/Features/trading/Marketplace.stories.tsx +18 -5
|
@@ -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;
|
package/src/components/Pager.tsx
CHANGED
|
@@ -8,6 +8,7 @@ interface PagerProps {
|
|
|
8
8
|
currentPage: number;
|
|
9
9
|
itemsPerPage: number;
|
|
10
10
|
onPageChange: (page: number) => void;
|
|
11
|
+
compact?: boolean;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
export const Pager: React.FC<PagerProps> = ({
|
|
@@ -15,13 +16,14 @@ export const Pager: React.FC<PagerProps> = ({
|
|
|
15
16
|
currentPage,
|
|
16
17
|
itemsPerPage,
|
|
17
18
|
onPageChange,
|
|
19
|
+
compact = false,
|
|
18
20
|
}) => {
|
|
19
21
|
const totalPages = Math.ceil(totalItems / itemsPerPage);
|
|
20
22
|
|
|
21
23
|
return (
|
|
22
24
|
<Container>
|
|
23
|
-
<p>Total items: {totalItems}</p>
|
|
24
|
-
<PagerContainer>
|
|
25
|
+
{!compact && <p>Total items: {totalItems}</p>}
|
|
26
|
+
<PagerContainer $compact={compact}>
|
|
25
27
|
<button
|
|
26
28
|
disabled={currentPage === 1}
|
|
27
29
|
onPointerDown={() => onPageChange(Math.max(currentPage - 1, 1))}
|
|
@@ -55,7 +57,7 @@ const Container = styled.div`
|
|
|
55
57
|
}
|
|
56
58
|
`;
|
|
57
59
|
|
|
58
|
-
const PagerContainer = styled.div
|
|
60
|
+
const PagerContainer = styled.div<{ $compact: boolean }>`
|
|
59
61
|
display: flex;
|
|
60
62
|
justify-content: center;
|
|
61
63
|
align-items: center;
|
|
@@ -67,11 +69,17 @@ const PagerContainer = styled.div`
|
|
|
67
69
|
|
|
68
70
|
div {
|
|
69
71
|
color: white;
|
|
72
|
+
${({ $compact }) => $compact && `
|
|
73
|
+
font-size: 0.55rem !important;
|
|
74
|
+
padding: 2px 6px !important;
|
|
75
|
+
min-width: unset !important;
|
|
76
|
+
`}
|
|
70
77
|
}
|
|
71
78
|
|
|
72
79
|
button {
|
|
73
|
-
width: 40px;
|
|
74
|
-
height: 40px;
|
|
80
|
+
width: ${({ $compact }) => ($compact ? '24px' : '40px')} !important;
|
|
81
|
+
height: ${({ $compact }) => ($compact ? '24px' : '40px')} !important;
|
|
82
|
+
font-size: ${({ $compact }) => ($compact ? '0.55rem' : 'inherit')} !important;
|
|
75
83
|
background-color: ${uiColors.darkGray};
|
|
76
84
|
border: none;
|
|
77
85
|
border-radius: 5px;
|