@rpg-engine/long-bow 0.8.150 → 0.8.153
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/long-bow.cjs.development.js +7 -1
- 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 +7 -1
- package/dist/long-bow.esm.js.map +1 -1
- package/package.json +2 -2
- package/src/components/Marketplace/BuyOrderRows.tsx +14 -0
- package/src/components/Marketplace/__test__/BuyPanel.spec.tsx +129 -0
- package/src/stories/Features/marketplace/BuyOrderPanel.stories.tsx +3 -0
- package/src/stories/Features/marketplace/BuyOrderRows.stories.tsx +1 -0
- package/src/stories/Features/trading/Marketplace.stories.tsx +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rpg-engine/long-bow",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.153",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"typings": "dist/index.d.ts",
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
"dependencies": {
|
|
85
85
|
"@capacitor/core": "^6.1.0",
|
|
86
86
|
"@rollup/plugin-image": "^2.1.1",
|
|
87
|
-
"@rpg-engine/shared": "^0.10.
|
|
87
|
+
"@rpg-engine/shared": "^0.10.92",
|
|
88
88
|
"dayjs": "^1.11.2",
|
|
89
89
|
"font-awesome": "^4.7.0",
|
|
90
90
|
"fs-extra": "^10.1.0",
|
|
@@ -4,6 +4,7 @@ import { ShoppingBag } from 'pixelarticons/react/ShoppingBag';
|
|
|
4
4
|
import React from 'react';
|
|
5
5
|
import styled from 'styled-components';
|
|
6
6
|
import { uiColors } from '../../constants/uiColors';
|
|
7
|
+
import { uiFonts } from '../../constants/uiFonts';
|
|
7
8
|
import { resolveAtlasSpriteKey } from '../../utils/atlasUtils';
|
|
8
9
|
import { LabelPill } from '../shared/LabelPill';
|
|
9
10
|
import { CTAButton } from '../shared/CTAButton/CTAButton';
|
|
@@ -23,6 +24,7 @@ export interface IBuyOrderRowProps {
|
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
const BUY_ORDER_DURATION_WEEKS = 4;
|
|
27
|
+
type BuyOrderWithQuantityFallback = IMarketplaceBuyOrderItem & { quantity?: number };
|
|
26
28
|
|
|
27
29
|
// Format "Active" → "ACTIVE", "Fulfilled" → "FULFILLED"
|
|
28
30
|
const formatStatusLabel = (status: string): string => {
|
|
@@ -68,6 +70,7 @@ export const BuyOrderRow: React.FC<IBuyOrderRowProps> = ({
|
|
|
68
70
|
showRequestTag = false,
|
|
69
71
|
requestTagLabel = 'Buy Request',
|
|
70
72
|
}) => {
|
|
73
|
+
const stackQty = (buyOrder as BuyOrderWithQuantityFallback).stackQty ?? (buyOrder as BuyOrderWithQuantityFallback).quantity;
|
|
71
74
|
const timeRemaining =
|
|
72
75
|
buyOrder.status === MarketplaceBuyOrderStatus.Active ? getTimeRemaining(buyOrder.createdAt) : null;
|
|
73
76
|
const spriteKey = resolveAtlasSpriteKey(atlasJSON, buyOrder.itemBlueprintKey);
|
|
@@ -90,6 +93,9 @@ export const BuyOrderRow: React.FC<IBuyOrderRowProps> = ({
|
|
|
90
93
|
<SpritePlaceholder />
|
|
91
94
|
)}
|
|
92
95
|
</RarityGlow>
|
|
96
|
+
{stackQty && stackQty > 1 && (
|
|
97
|
+
<QuantityOverlay>x{stackQty}</QuantityOverlay>
|
|
98
|
+
)}
|
|
93
99
|
</SpriteContainer>
|
|
94
100
|
|
|
95
101
|
<ItemDetails>
|
|
@@ -285,3 +291,11 @@ const ActionSection = styled.div`
|
|
|
285
291
|
flex-shrink: 0;
|
|
286
292
|
margin-left: 0.75rem;
|
|
287
293
|
`;
|
|
294
|
+
|
|
295
|
+
const QuantityOverlay = styled.p`
|
|
296
|
+
position: absolute;
|
|
297
|
+
display: block;
|
|
298
|
+
top: 15px;
|
|
299
|
+
left: -8px;
|
|
300
|
+
font-size: ${uiFonts.size.xsmall} !important;
|
|
301
|
+
`;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jest-environment jsdom
|
|
3
|
+
*/
|
|
4
|
+
// @ts-nocheck
|
|
5
|
+
import { MarketplaceBuyOrderStatus } from '@rpg-engine/shared';
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import ReactDOM from 'react-dom';
|
|
8
|
+
import { act } from 'react-dom/test-utils';
|
|
9
|
+
import { BuyPanel } from '../BuyPanel';
|
|
10
|
+
|
|
11
|
+
jest.mock('../../ConfirmModal', () => ({
|
|
12
|
+
ConfirmModal: ({ message, onConfirm, onClose }) => (
|
|
13
|
+
<div data-testid="confirm-modal">
|
|
14
|
+
<div>{message}</div>
|
|
15
|
+
<button onClick={onConfirm}>Confirm fulfill</button>
|
|
16
|
+
<button onClick={onClose}>Cancel fulfill</button>
|
|
17
|
+
</div>
|
|
18
|
+
),
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
jest.mock('../../shared/CTAButton/CTAButton', () => ({
|
|
22
|
+
CTAButton: ({ label, onClick }) => <button onClick={onClick}>{label}</button>,
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
describe('BuyPanel fulfill flow', () => {
|
|
26
|
+
let container;
|
|
27
|
+
|
|
28
|
+
const openBuyOrder = {
|
|
29
|
+
_id: 'buy-order-123',
|
|
30
|
+
owner: 'buyer-id',
|
|
31
|
+
itemBlueprintKey: 'short-sword',
|
|
32
|
+
maxPrice: 1000,
|
|
33
|
+
status: MarketplaceBuyOrderStatus.Active,
|
|
34
|
+
createdAt: new Date().toISOString(),
|
|
35
|
+
expiresAt: new Date(Date.now() + 60_000).toISOString(),
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const renderBuyPanel = (onFulfillBuyOrder = jest.fn()) => {
|
|
39
|
+
act(() => {
|
|
40
|
+
ReactDOM.render(
|
|
41
|
+
<BuyPanel
|
|
42
|
+
items={[]}
|
|
43
|
+
atlasIMG=""
|
|
44
|
+
atlasJSON={{}}
|
|
45
|
+
onClose={jest.fn()}
|
|
46
|
+
onChangeType={jest.fn()}
|
|
47
|
+
onChangeRarity={jest.fn()}
|
|
48
|
+
onChangeOrder={jest.fn()}
|
|
49
|
+
onChangeNameInput={jest.fn()}
|
|
50
|
+
onChangeMainLevelInput={jest.fn()}
|
|
51
|
+
onChangeSecondaryLevelInput={jest.fn()}
|
|
52
|
+
onChangePriceInput={jest.fn()}
|
|
53
|
+
characterId="seller-id"
|
|
54
|
+
totalItems={0}
|
|
55
|
+
currentPage={1}
|
|
56
|
+
itemsPerPage={10}
|
|
57
|
+
onPageChange={jest.fn()}
|
|
58
|
+
openBuyOrders={[openBuyOrder]}
|
|
59
|
+
openBuyOrdersTotal={1}
|
|
60
|
+
openBuyOrdersPage={1}
|
|
61
|
+
onOpenBuyOrdersPageChange={jest.fn()}
|
|
62
|
+
onFulfillBuyOrder={onFulfillBuyOrder}
|
|
63
|
+
/>,
|
|
64
|
+
container
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return onFulfillBuyOrder;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
beforeEach(() => {
|
|
72
|
+
container = document.createElement('div');
|
|
73
|
+
document.body.appendChild(container);
|
|
74
|
+
HTMLElement.prototype.scrollTo = jest.fn();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
afterEach(() => {
|
|
78
|
+
ReactDOM.unmountComponentAtNode(container);
|
|
79
|
+
container.remove();
|
|
80
|
+
jest.clearAllMocks();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('waits for confirmation before calling onFulfillBuyOrder', () => {
|
|
84
|
+
const onFulfillBuyOrder = renderBuyPanel();
|
|
85
|
+
|
|
86
|
+
const fulfillButton = Array.from(container.querySelectorAll('button')).find(
|
|
87
|
+
(button) => button.textContent === 'Fulfill'
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
act(() => {
|
|
91
|
+
fulfillButton.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
expect(onFulfillBuyOrder).not.toHaveBeenCalled();
|
|
95
|
+
expect(container.textContent).toContain('Try to fulfill this buy request with a matching item from your inventory or depot?');
|
|
96
|
+
|
|
97
|
+
const confirmButton = Array.from(container.querySelectorAll('button')).find(
|
|
98
|
+
(button) => button.textContent === 'Confirm fulfill'
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
act(() => {
|
|
102
|
+
confirmButton.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
expect(onFulfillBuyOrder).toHaveBeenCalledWith('buy-order-123');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('does not call onFulfillBuyOrder when the confirm modal is cancelled', () => {
|
|
109
|
+
const onFulfillBuyOrder = renderBuyPanel();
|
|
110
|
+
|
|
111
|
+
const fulfillButton = Array.from(container.querySelectorAll('button')).find(
|
|
112
|
+
(button) => button.textContent === 'Fulfill'
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
act(() => {
|
|
116
|
+
fulfillButton.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const cancelButton = Array.from(container.querySelectorAll('button')).find(
|
|
120
|
+
(button) => button.textContent === 'Cancel fulfill'
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
act(() => {
|
|
124
|
+
cancelButton.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
expect(onFulfillBuyOrder).not.toHaveBeenCalled();
|
|
128
|
+
});
|
|
129
|
+
});
|
|
@@ -83,6 +83,7 @@ const mockBuyOrders: IMarketplaceBuyOrderItem[] = [
|
|
|
83
83
|
itemBlueprintKey: 'items/broad-sword',
|
|
84
84
|
itemRarity: 'Rare',
|
|
85
85
|
maxPrice: 500,
|
|
86
|
+
stackQty: 5,
|
|
86
87
|
escrowedGold: 500,
|
|
87
88
|
fee: 25,
|
|
88
89
|
status: MarketplaceBuyOrderStatus.Active,
|
|
@@ -151,6 +152,7 @@ const DefaultWrapper = () => {
|
|
|
151
152
|
itemBlueprintKey: selectedBlueprint.key,
|
|
152
153
|
itemRarity: rarity as any,
|
|
153
154
|
maxPrice,
|
|
155
|
+
stackQty: quantity,
|
|
154
156
|
escrowedGold: maxPrice,
|
|
155
157
|
fee: Math.floor(maxPrice * 0.05),
|
|
156
158
|
status: MarketplaceBuyOrderStatus.Active,
|
|
@@ -222,6 +224,7 @@ const WithBlueprintWrapper = () => {
|
|
|
222
224
|
itemBlueprintKey: selectedBlueprint.key,
|
|
223
225
|
itemRarity: rarity as any,
|
|
224
226
|
maxPrice,
|
|
227
|
+
stackQty: quantity,
|
|
225
228
|
escrowedGold: maxPrice,
|
|
226
229
|
fee: Math.floor(maxPrice * 0.05),
|
|
227
230
|
status: MarketplaceBuyOrderStatus.Active,
|
|
@@ -141,6 +141,7 @@ const Template: Story = () => {
|
|
|
141
141
|
itemBlueprintKey: selectedBlueprint.key,
|
|
142
142
|
itemRarity: selectedRarity as any,
|
|
143
143
|
maxPrice,
|
|
144
|
+
stackQty: quantity,
|
|
144
145
|
escrowedGold: maxPrice,
|
|
145
146
|
fee: Math.floor(maxPrice * 0.05),
|
|
146
147
|
status: MarketplaceBuyOrderStatus.Active,
|