@rpg-engine/long-bow 0.8.202 → 0.8.205
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/Item/Inventory/ErrorBoundary.d.ts +2 -0
- package/dist/components/Marketplace/BuyOrderPanel.d.ts +2 -2
- package/dist/components/Marketplace/BuyOrderRows.d.ts +4 -4
- package/dist/components/Marketplace/HistoryPanel.d.ts +2 -2
- package/dist/components/Marketplace/MarketplaceRows.d.ts +2 -0
- package/dist/components/Tutorial/TutorialStepper.d.ts +2 -1
- package/dist/components/shared/MMORPGNumber.d.ts +6 -0
- package/dist/long-bow.cjs.development.js +794 -34640
- 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 +795 -34641
- package/dist/long-bow.esm.js.map +1 -1
- package/dist/utils/numberUtils.d.ts +7 -0
- package/package.json +1 -1
- package/src/components/DCWallet/DCWalletContent.tsx +5 -3
- package/src/components/Item/Inventory/ErrorBoundary.tsx +25 -8
- package/src/components/Item/Inventory/ItemPropertyColorSelector.tsx +3 -1
- package/src/components/Marketplace/BuyOrderPanel.tsx +2 -2
- package/src/components/Marketplace/BuyOrderRows.tsx +13 -7
- package/src/components/Marketplace/CharacterDetailModal.tsx +99 -17
- package/src/components/Marketplace/CharacterListingModal.tsx +2 -1
- package/src/components/Marketplace/GroupedRowContainer.tsx +10 -10
- package/src/components/Marketplace/HistoryPanel.tsx +5 -5
- package/src/components/Marketplace/MarketplaceBuyModal.tsx +4 -4
- package/src/components/Marketplace/MarketplaceRows.tsx +13 -7
- package/src/components/Marketplace/__test__/CharacterDetailModal.spec.tsx +137 -0
- package/src/components/Store/CartView.tsx +2 -1
- package/src/components/Store/FeaturedBanner.tsx +1 -0
- package/src/components/Store/PaymentMethodModal.tsx +11 -3
- package/src/components/Store/StoreCharacterSkinRow.tsx +9 -3
- package/src/components/Store/StoreItemDetails.tsx +9 -1
- package/src/components/Store/StoreItemRow.tsx +13 -3
- package/src/components/Store/sections/StorePacksSection.tsx +7 -0
- package/src/components/Store/sections/StoreRedeemSection.tsx +2 -1
- package/src/components/Tutorial/TutorialStepper.tsx +16 -3
- package/src/components/shared/DCRateStrip.tsx +3 -2
- package/src/components/shared/MMORPGNumber.tsx +22 -0
- package/src/stories/Features/marketplace/CharacterDetailModal.stories.tsx +69 -15
- package/src/stories/Features/marketplace/CharacterMarketplace.stories.tsx +105 -11
- package/src/stories/Features/store/Store.stories.tsx +125 -35
- package/src/stories/Features/trading/Marketplace.stories.tsx +8 -4
- package/src/utils/__test__/atlasUtils.spec.ts +15 -0
- package/src/utils/atlasUtils.ts +78 -0
- package/src/utils/numberUtils.ts +31 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jest-environment jsdom
|
|
3
|
+
*/
|
|
4
|
+
// @ts-nocheck
|
|
5
|
+
import { ICharacterListing } 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 { CharacterDetailModal } from '../CharacterDetailModal';
|
|
10
|
+
|
|
11
|
+
jest.mock('../../Abstractions/ModalPortal', () => ({
|
|
12
|
+
__esModule: true,
|
|
13
|
+
default: ({ children }) => <>{children}</>,
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
jest.mock('../../ConfirmModal', () => ({
|
|
17
|
+
ConfirmModal: ({ message }) => (
|
|
18
|
+
<div data-testid="confirm-modal">{message}</div>
|
|
19
|
+
),
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
jest.mock('../../shared/CTAButton/CTAButton', () => ({
|
|
23
|
+
CTAButton: ({ label, onClick, disabled }) => (
|
|
24
|
+
<button disabled={disabled} onClick={onClick}>
|
|
25
|
+
{label}
|
|
26
|
+
</button>
|
|
27
|
+
),
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
jest.mock('../../shared/SpriteFromAtlas', () => ({
|
|
31
|
+
SpriteFromAtlas: ({ spriteKey }) => (
|
|
32
|
+
<div data-testid="sprite-from-atlas" data-sprite-key={spriteKey} />
|
|
33
|
+
),
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
jest.mock('pixelarticons/react/ShoppingBag', () => ({
|
|
37
|
+
ShoppingBag: () => <svg data-testid="shopping-bag-icon" />,
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
jest.mock('react-icons/fa', () => ({
|
|
41
|
+
FaTimes: () => <svg data-testid="close-icon" />,
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
describe('CharacterDetailModal', () => {
|
|
45
|
+
let container;
|
|
46
|
+
|
|
47
|
+
const listing: ICharacterListing = {
|
|
48
|
+
_id: 'listing-1',
|
|
49
|
+
character: 'character-1',
|
|
50
|
+
seller: 'seller-1',
|
|
51
|
+
listedByCharacterName: 'TraderJoe',
|
|
52
|
+
price: 42000,
|
|
53
|
+
isBeingBought: false,
|
|
54
|
+
characterSnapshot: {
|
|
55
|
+
name: 'Sir Galahad',
|
|
56
|
+
level: 25,
|
|
57
|
+
class: 'Warrior',
|
|
58
|
+
race: 'Human',
|
|
59
|
+
faction: 'Alliance',
|
|
60
|
+
mode: 'Standard',
|
|
61
|
+
skills: { level: 25, sword: 10, shielding: 8 },
|
|
62
|
+
equipment: [
|
|
63
|
+
{
|
|
64
|
+
slot: 'rightHand',
|
|
65
|
+
itemName: 'Broad Sword',
|
|
66
|
+
itemKey: 'broad-sword',
|
|
67
|
+
rarity: 'Common',
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
slot: 'head',
|
|
71
|
+
itemName: 'Iron Helmet',
|
|
72
|
+
itemKey: 'iron-helmet',
|
|
73
|
+
rarity: 'Rare',
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
slot: 'armor',
|
|
77
|
+
itemName: 'Jacket',
|
|
78
|
+
itemKey: 'jacket',
|
|
79
|
+
rarity: 'Uncommon',
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
textureKey: 'black-knight',
|
|
83
|
+
},
|
|
84
|
+
createdAt: new Date(),
|
|
85
|
+
updatedAt: new Date(),
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
beforeEach(() => {
|
|
89
|
+
container = document.createElement('div');
|
|
90
|
+
document.body.appendChild(container);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
afterEach(() => {
|
|
94
|
+
ReactDOM.unmountComponentAtNode(container);
|
|
95
|
+
container.remove();
|
|
96
|
+
jest.clearAllMocks();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('renders equipment rows from the API snapshot and requests atlas sprites for each item', () => {
|
|
100
|
+
act(() => {
|
|
101
|
+
ReactDOM.render(
|
|
102
|
+
<CharacterDetailModal
|
|
103
|
+
listing={listing}
|
|
104
|
+
isOpen
|
|
105
|
+
onClose={jest.fn()}
|
|
106
|
+
onBuy={jest.fn()}
|
|
107
|
+
atlasJSON={{}}
|
|
108
|
+
atlasIMG=""
|
|
109
|
+
characterAtlasJSON={{}}
|
|
110
|
+
characterAtlasIMG=""
|
|
111
|
+
/>,
|
|
112
|
+
container
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
expect(container.textContent).toContain('Broad Sword');
|
|
117
|
+
expect(container.textContent).toContain('Iron Helmet');
|
|
118
|
+
expect(container.textContent).toContain('Jacket');
|
|
119
|
+
expect(container.textContent).toContain('Right Hand');
|
|
120
|
+
expect(container.textContent).toContain('Head');
|
|
121
|
+
expect(container.textContent).toContain('Armor');
|
|
122
|
+
|
|
123
|
+
const spriteKeys = Array.from(
|
|
124
|
+
container.querySelectorAll('[data-testid="sprite-from-atlas"]')
|
|
125
|
+
).map((node) => node.getAttribute('data-sprite-key'));
|
|
126
|
+
|
|
127
|
+
expect(spriteKeys).toEqual(
|
|
128
|
+
expect.arrayContaining([
|
|
129
|
+
'black-knight/down/standing/0.png',
|
|
130
|
+
'broad-sword',
|
|
131
|
+
'iron-helmet',
|
|
132
|
+
'jacket',
|
|
133
|
+
'others/definya-coin.png',
|
|
134
|
+
])
|
|
135
|
+
);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
@@ -8,6 +8,7 @@ import { CTAButton } from '../shared/CTAButton/CTAButton';
|
|
|
8
8
|
import { SpriteFromAtlas } from '../shared/SpriteFromAtlas';
|
|
9
9
|
import { ITrustSignal, TrustBar } from './TrustBar';
|
|
10
10
|
import { PurchaseSuccess } from './PurchaseSuccess';
|
|
11
|
+
import { MMORPGNumber } from '../../components/shared/MMORPGNumber';
|
|
11
12
|
|
|
12
13
|
// Local cart item interface
|
|
13
14
|
interface ICartItem {
|
|
@@ -228,7 +229,7 @@ export const CartView: React.FC<ICartViewProps> = ({
|
|
|
228
229
|
{dcTotal > 0 && (
|
|
229
230
|
<TotalRow>
|
|
230
231
|
<span>DC:</span>
|
|
231
|
-
<span
|
|
232
|
+
<span><MMORPGNumber value={dcTotal} /> DC</span>
|
|
232
233
|
</TotalRow>
|
|
233
234
|
)}
|
|
234
235
|
<TotalRow $isTotal>
|
|
@@ -3,6 +3,7 @@ import { FaTimes } from 'react-icons/fa';
|
|
|
3
3
|
import styled from 'styled-components';
|
|
4
4
|
import ModalPortal from '../Abstractions/ModalPortal';
|
|
5
5
|
import { Button, ButtonTypes } from '../Button';
|
|
6
|
+
import { MMORPGNumber } from '../../components/shared/MMORPGNumber';
|
|
6
7
|
|
|
7
8
|
export interface IPaymentMethodModalProps {
|
|
8
9
|
dcBalance: number;
|
|
@@ -53,9 +54,16 @@ export const PaymentMethodModal: React.FC<IPaymentMethodModalProps> = ({
|
|
|
53
54
|
}, [selected, dcDisabled, onPayWithDC, onPayWithCard, onPayWithPix]);
|
|
54
55
|
|
|
55
56
|
const dcSubText =
|
|
56
|
-
dcRequired !== undefined
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
dcRequired !== undefined ? (
|
|
58
|
+
<>
|
|
59
|
+
<MMORPGNumber value={dcBalance} /> DC available ·{' '}
|
|
60
|
+
<MMORPGNumber value={dcRequired} /> DC needed
|
|
61
|
+
</>
|
|
62
|
+
) : (
|
|
63
|
+
<>
|
|
64
|
+
<MMORPGNumber value={dcBalance} /> DC available
|
|
65
|
+
</>
|
|
66
|
+
);
|
|
59
67
|
|
|
60
68
|
return (
|
|
61
69
|
<ModalPortal>
|
|
@@ -10,6 +10,7 @@ import { SelectArrow } from '../Arrow/SelectArrow';
|
|
|
10
10
|
import { ICharacterProps } from '../Character/CharacterSelection';
|
|
11
11
|
import { CTAButton } from '../shared/CTAButton/CTAButton';
|
|
12
12
|
import { SpriteFromAtlas } from '../shared/SpriteFromAtlas';
|
|
13
|
+
import { MMORPGNumber } from '../../components/shared/MMORPGNumber';
|
|
13
14
|
import { SimpleTooltip } from '../shared/SimpleTooltip';
|
|
14
15
|
import { useCharacterSkinNavigation } from '../../hooks/useCharacterSkinNavigation';
|
|
15
16
|
|
|
@@ -128,7 +129,7 @@ export const StoreCharacterSkinRow: React.FC<IStoreCharacterSkinRowProps> = ({
|
|
|
128
129
|
<DCCoinWrapper $scale={0.8}>
|
|
129
130
|
<SpriteFromAtlas atlasIMG={atlasIMG} atlasJSON={atlasJSON} spriteKey="others/definya-coin.png" imgScale={0.8} />
|
|
130
131
|
</DCCoinWrapper>
|
|
131
|
-
{originalPrice
|
|
132
|
+
<MMORPGNumber value={originalPrice} />
|
|
132
133
|
</>
|
|
133
134
|
) : `$${originalPrice.toFixed(2)}`}
|
|
134
135
|
</OriginalPrice>
|
|
@@ -140,7 +141,7 @@ export const StoreCharacterSkinRow: React.FC<IStoreCharacterSkinRowProps> = ({
|
|
|
140
141
|
<SpriteFromAtlas atlasIMG={atlasIMG} atlasJSON={atlasJSON} spriteKey="others/definya-coin.png" imgScale={1} />
|
|
141
142
|
</SimpleTooltip>
|
|
142
143
|
</DCCoinWrapper>
|
|
143
|
-
{item.price
|
|
144
|
+
<MMORPGNumber value={item.price} />
|
|
144
145
|
</ItemPrice>
|
|
145
146
|
) : (
|
|
146
147
|
<ItemPrice $onSale={originalPrice != null} style={{ display: 'flex', alignItems: 'center', gap: '2px' }}>
|
|
@@ -153,7 +154,7 @@ export const StoreCharacterSkinRow: React.FC<IStoreCharacterSkinRowProps> = ({
|
|
|
153
154
|
<SpriteFromAtlas atlasIMG={atlasIMG} atlasJSON={atlasJSON} spriteKey="others/definya-coin.png" imgScale={0.9} />
|
|
154
155
|
</SimpleTooltip>
|
|
155
156
|
</DCCoinWrapper>
|
|
156
|
-
{(
|
|
157
|
+
<MMORPGNumber value={(item as any).dcPrice as number} />
|
|
157
158
|
</>
|
|
158
159
|
) : ''}
|
|
159
160
|
</ItemPrice>
|
|
@@ -226,6 +227,7 @@ const ItemDetails = styled.div`
|
|
|
226
227
|
display: flex;
|
|
227
228
|
flex-direction: column;
|
|
228
229
|
gap: 0.35rem;
|
|
230
|
+
min-width: 0;
|
|
229
231
|
`;
|
|
230
232
|
|
|
231
233
|
const ItemName = styled.div`
|
|
@@ -233,6 +235,9 @@ const ItemName = styled.div`
|
|
|
233
235
|
font-size: 0.8125rem;
|
|
234
236
|
color: #ffffff;
|
|
235
237
|
text-shadow: 1px 1px 0 rgba(0,0,0,0.5);
|
|
238
|
+
white-space: nowrap;
|
|
239
|
+
overflow: hidden;
|
|
240
|
+
text-overflow: ellipsis;
|
|
236
241
|
`;
|
|
237
242
|
|
|
238
243
|
const PriceRow = styled.div`
|
|
@@ -268,6 +273,7 @@ const Controls = styled.div`
|
|
|
268
273
|
align-items: center;
|
|
269
274
|
gap: 0.5rem;
|
|
270
275
|
min-width: fit-content;
|
|
276
|
+
flex-shrink: 0;
|
|
271
277
|
`;
|
|
272
278
|
|
|
273
279
|
const DCCoinWrapper = styled.span<{ $scale?: number }>`
|
|
@@ -3,6 +3,7 @@ import React from 'react';
|
|
|
3
3
|
import { FaArrowLeft, FaCartPlus } from 'react-icons/fa';
|
|
4
4
|
import styled from 'styled-components';
|
|
5
5
|
import { CTAButton } from '../shared/CTAButton/CTAButton';
|
|
6
|
+
import { MMORPGNumber } from '../../components/shared/MMORPGNumber';
|
|
6
7
|
|
|
7
8
|
interface IStoreItemDetailsProps {
|
|
8
9
|
item: IProductBlueprint | (IItemPack & { name: string; texturePath: string });
|
|
@@ -46,7 +47,14 @@ export const StoreItemDetails: React.FC<IStoreItemDetailsProps> = ({
|
|
|
46
47
|
<ItemName>{item.name}</ItemName>
|
|
47
48
|
<ItemPrice>
|
|
48
49
|
{currencySymbol}{'priceUSD' in item ? item.priceUSD : ((item as any).regionalPrice ?? item.price)}
|
|
49
|
-
{(item as any).dcPrice ?
|
|
50
|
+
{(item as any).dcPrice ? (
|
|
51
|
+
<>
|
|
52
|
+
{' '}
|
|
53
|
+
· <MMORPGNumber value={(item as any).dcPrice as number} /> DC
|
|
54
|
+
</>
|
|
55
|
+
) : (
|
|
56
|
+
''
|
|
57
|
+
)}
|
|
50
58
|
</ItemPrice>
|
|
51
59
|
<Description>{item.description}</Description>
|
|
52
60
|
</ItemInfo>
|
|
@@ -9,6 +9,7 @@ import { SpriteFromAtlas } from '../shared/SpriteFromAtlas';
|
|
|
9
9
|
import { SimpleTooltip } from '../shared/SimpleTooltip';
|
|
10
10
|
import { useQuantityControl } from '../../hooks/useQuantityControl';
|
|
11
11
|
import { IStoreBadge, StoreBadges } from './StoreBadges';
|
|
12
|
+
import { MMORPGNumber } from '../../components/shared/MMORPGNumber';
|
|
12
13
|
|
|
13
14
|
interface IStoreItemRowProps {
|
|
14
15
|
item: IProductBlueprint;
|
|
@@ -118,7 +119,7 @@ export const StoreItemRow: React.FC<IStoreItemRowProps> = ({
|
|
|
118
119
|
<DCCoinWrapper $scale={0.8}>
|
|
119
120
|
<SpriteFromAtlas atlasIMG={atlasIMG} atlasJSON={atlasJSON} spriteKey="others/definya-coin.png" imgScale={0.8} />
|
|
120
121
|
</DCCoinWrapper>
|
|
121
|
-
{originalPrice
|
|
122
|
+
<MMORPGNumber value={originalPrice} />
|
|
122
123
|
</>
|
|
123
124
|
) : `${currencySymbol}${originalPrice.toFixed(2)}`}
|
|
124
125
|
</OriginalPrice>
|
|
@@ -130,7 +131,7 @@ export const StoreItemRow: React.FC<IStoreItemRowProps> = ({
|
|
|
130
131
|
<SpriteFromAtlas atlasIMG={atlasIMG} atlasJSON={atlasJSON} spriteKey="others/definya-coin.png" imgScale={1} />
|
|
131
132
|
</SimpleTooltip>
|
|
132
133
|
</DCCoinWrapper>
|
|
133
|
-
{item.price
|
|
134
|
+
<MMORPGNumber value={item.price} />
|
|
134
135
|
</ItemPrice>
|
|
135
136
|
) : (
|
|
136
137
|
<ItemPrice $onSale={originalPrice != null} style={{ display: 'flex', alignItems: 'center', gap: '2px' }}>
|
|
@@ -143,7 +144,7 @@ export const StoreItemRow: React.FC<IStoreItemRowProps> = ({
|
|
|
143
144
|
<SpriteFromAtlas atlasIMG={atlasIMG} atlasJSON={atlasJSON} spriteKey="others/definya-coin.png" imgScale={0.9} />
|
|
144
145
|
</SimpleTooltip>
|
|
145
146
|
</DCCoinWrapper>
|
|
146
|
-
{(
|
|
147
|
+
<MMORPGNumber value={(item as any).dcPrice as number} />
|
|
147
148
|
</>
|
|
148
149
|
) : ''}
|
|
149
150
|
</ItemPrice>
|
|
@@ -244,6 +245,7 @@ const ItemDetails = styled.div`
|
|
|
244
245
|
display: flex;
|
|
245
246
|
flex-direction: column;
|
|
246
247
|
gap: 0.35rem;
|
|
248
|
+
min-width: 0;
|
|
247
249
|
`;
|
|
248
250
|
|
|
249
251
|
const ItemName = styled.div`
|
|
@@ -251,6 +253,9 @@ const ItemName = styled.div`
|
|
|
251
253
|
font-size: 0.8125rem;
|
|
252
254
|
color: #ffffff;
|
|
253
255
|
text-shadow: 1px 1px 0 rgba(0,0,0,0.5);
|
|
256
|
+
white-space: nowrap;
|
|
257
|
+
overflow: hidden;
|
|
258
|
+
text-overflow: ellipsis;
|
|
254
259
|
`;
|
|
255
260
|
|
|
256
261
|
const PriceRow = styled.div`
|
|
@@ -281,6 +286,10 @@ const ItemDescription = styled.div`
|
|
|
281
286
|
color: rgba(255, 255, 255, 0.85);
|
|
282
287
|
line-height: 1.4;
|
|
283
288
|
margin-top: 2px;
|
|
289
|
+
display: -webkit-box;
|
|
290
|
+
-webkit-line-clamp: 2;
|
|
291
|
+
-webkit-box-orient: vertical;
|
|
292
|
+
overflow: hidden;
|
|
284
293
|
`;
|
|
285
294
|
|
|
286
295
|
const DCCoinWrapper = styled.span<{ $scale?: number }>`
|
|
@@ -298,6 +307,7 @@ const Controls = styled.div`
|
|
|
298
307
|
align-items: center;
|
|
299
308
|
gap: 0.75rem;
|
|
300
309
|
min-width: fit-content;
|
|
310
|
+
flex-shrink: 0;
|
|
301
311
|
`;
|
|
302
312
|
|
|
303
313
|
const ArrowsContainer = styled.div`
|
|
@@ -214,6 +214,9 @@ const PackName = styled.div`
|
|
|
214
214
|
color: #ffffff;
|
|
215
215
|
text-shadow: 1px 1px 0 rgba(0,0,0,0.5);
|
|
216
216
|
letter-spacing: 0.5px;
|
|
217
|
+
white-space: nowrap;
|
|
218
|
+
overflow: hidden;
|
|
219
|
+
text-overflow: ellipsis;
|
|
217
220
|
`;
|
|
218
221
|
|
|
219
222
|
const PackPriceRow = styled.div`
|
|
@@ -242,6 +245,10 @@ const PackDescription = styled.div`
|
|
|
242
245
|
color: rgba(255, 255, 255, 0.85);
|
|
243
246
|
line-height: 1.5;
|
|
244
247
|
margin-top: 2px;
|
|
248
|
+
display: -webkit-box;
|
|
249
|
+
-webkit-line-clamp: 2;
|
|
250
|
+
-webkit-box-orient: vertical;
|
|
251
|
+
overflow: hidden;
|
|
245
252
|
`;
|
|
246
253
|
|
|
247
254
|
const Controls = styled.div`
|
|
@@ -3,6 +3,7 @@ import { FaCheckCircle, FaExclamationCircle, FaTicketAlt } from 'react-icons/fa'
|
|
|
3
3
|
import styled, { keyframes } from 'styled-components';
|
|
4
4
|
import { uiColors } from '../../../constants/uiColors';
|
|
5
5
|
import { CTAButton } from '../../shared/CTAButton/CTAButton';
|
|
6
|
+
import { MMORPGNumber } from '../../../components/shared/MMORPGNumber';
|
|
6
7
|
|
|
7
8
|
type RedeemStatus = 'idle' | 'loading' | 'success' | 'error';
|
|
8
9
|
|
|
@@ -68,7 +69,7 @@ export const StoreRedeemSection: React.FC<IStoreRedeemSectionProps> = ({
|
|
|
68
69
|
</SuccessIcon>
|
|
69
70
|
<SuccessTitle>Code Redeemed!</SuccessTitle>
|
|
70
71
|
{dcAmount != null && (
|
|
71
|
-
<DCAmountDisplay
|
|
72
|
+
<DCAmountDisplay>+<MMORPGNumber value={dcAmount} /> DC</DCAmountDisplay>
|
|
72
73
|
)}
|
|
73
74
|
<SuccessHint>Your wallet balance has been updated.</SuccessHint>
|
|
74
75
|
<ButtonWrapper>
|
|
@@ -18,10 +18,11 @@ export interface ITutorialStepperProps {
|
|
|
18
18
|
onLessonFinish: () => void;
|
|
19
19
|
onStepChange?: (stepIndex: number) => void;
|
|
20
20
|
imageStyle?: CSSProperties;
|
|
21
|
+
isTranslated?: boolean;
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
export const TutorialStepper = React.memo(
|
|
24
|
-
({ lessons, onLessonFinish, onStepChange, imageStyle }: ITutorialStepperProps) => {
|
|
25
|
+
({ lessons, onLessonFinish, onStepChange, imageStyle, isTranslated = false }: ITutorialStepperProps) => {
|
|
25
26
|
const generateLessons = useMemo(
|
|
26
27
|
() =>
|
|
27
28
|
lessons.map((lesson, index) => ({
|
|
@@ -58,13 +59,17 @@ export const TutorialStepper = React.memo(
|
|
|
58
59
|
dangerouslySetInnerHTML={{ __html: lesson.body as string }}
|
|
59
60
|
></LessonBody>
|
|
60
61
|
)}
|
|
61
|
-
{lesson.text &&
|
|
62
|
+
{lesson.text && (
|
|
63
|
+
isTranslated
|
|
64
|
+
? <LessonText>{lesson.text}</LessonText>
|
|
65
|
+
: <DynamicText text={lesson.text} />
|
|
66
|
+
)}
|
|
62
67
|
</LessonFooter>
|
|
63
68
|
</LessonContainer>
|
|
64
69
|
),
|
|
65
70
|
id: index,
|
|
66
71
|
})),
|
|
67
|
-
[lessons, imageStyle]
|
|
72
|
+
[lessons, imageStyle, isTranslated]
|
|
68
73
|
);
|
|
69
74
|
|
|
70
75
|
return (
|
|
@@ -93,6 +98,14 @@ const LessonImageWrapper = styled.div`
|
|
|
93
98
|
|
|
94
99
|
const LessonBody = styled.div``;
|
|
95
100
|
|
|
101
|
+
const LessonText = styled.p`
|
|
102
|
+
font-size: 0.7rem !important;
|
|
103
|
+
color: white;
|
|
104
|
+
text-shadow: 1px 1px 0px #000000;
|
|
105
|
+
letter-spacing: 1.2px;
|
|
106
|
+
word-break: normal;
|
|
107
|
+
`;
|
|
108
|
+
|
|
96
109
|
const Container = styled.div`
|
|
97
110
|
width: 80%;
|
|
98
111
|
max-width: 600px;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { DC_TO_GOLD_SWAP_RATE } from '@rpg-engine/shared';
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import styled from 'styled-components';
|
|
4
|
+
import { MMORPGNumber } from '../../components/shared/MMORPGNumber';
|
|
4
5
|
|
|
5
6
|
/** 100 DC = $1 USD */
|
|
6
7
|
const DC_TO_USD = 100;
|
|
@@ -10,7 +11,7 @@ export const DCRateStrip: React.FC = () => (
|
|
|
10
11
|
<RateItem>
|
|
11
12
|
<RateNum>1 DC</RateNum>
|
|
12
13
|
<RateSep>=</RateSep>
|
|
13
|
-
<RateVal
|
|
14
|
+
<RateVal><MMORPGNumber value={DC_TO_GOLD_SWAP_RATE} /> Gold</RateVal>
|
|
14
15
|
</RateItem>
|
|
15
16
|
<RateDivider />
|
|
16
17
|
<RateItem>
|
|
@@ -22,7 +23,7 @@ export const DCRateStrip: React.FC = () => (
|
|
|
22
23
|
<RateItem>
|
|
23
24
|
<RateNum>1 USD</RateNum>
|
|
24
25
|
<RateSep>=</RateSep>
|
|
25
|
-
<RateVal
|
|
26
|
+
<RateVal><MMORPGNumber value={DC_TO_USD * DC_TO_GOLD_SWAP_RATE} /> Gold</RateVal>
|
|
26
27
|
</RateItem>
|
|
27
28
|
</RateStrip>
|
|
28
29
|
);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { formatMMORPGNumber } from '../../utils/numberUtils';
|
|
3
|
+
import { SimpleTooltip } from './SimpleTooltip';
|
|
4
|
+
|
|
5
|
+
interface IMMORPGNumberProps {
|
|
6
|
+
value: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const MMORPGNumber: React.FC<IMMORPGNumberProps> = ({ value }) => {
|
|
10
|
+
const formatted = formatMMORPGNumber(value);
|
|
11
|
+
const isShortened = formatted !== value.toString();
|
|
12
|
+
|
|
13
|
+
if (!isShortened) {
|
|
14
|
+
return <>{formatted}</>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<SimpleTooltip content={value.toLocaleString()}>
|
|
19
|
+
<span style={{ cursor: 'help' }}>{formatted}</span>
|
|
20
|
+
</SimpleTooltip>
|
|
21
|
+
);
|
|
22
|
+
};
|
|
@@ -2,7 +2,10 @@ import { ICharacterListing } from '@rpg-engine/shared';
|
|
|
2
2
|
import { Meta, Story } from '@storybook/react';
|
|
3
3
|
import React, { useState } from 'react';
|
|
4
4
|
import { RPGUIRoot } from '../../..';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
CharacterDetailModal,
|
|
7
|
+
ICharacterDetailModalProps,
|
|
8
|
+
} from '../../../components/Marketplace/CharacterDetailModal';
|
|
6
9
|
import entitiesAtlasJSON from '../../../mocks/atlas/entities/entities.json';
|
|
7
10
|
import entitiesAtlasIMG from '../../../mocks/atlas/entities/entities.png';
|
|
8
11
|
import itemsAtlasJSON from '../../../mocks/atlas/items/items.json';
|
|
@@ -31,11 +34,28 @@ const warriorListing: ICharacterListing = {
|
|
|
31
34
|
race: 'Human',
|
|
32
35
|
faction: 'Alliance',
|
|
33
36
|
mode: 'Standard',
|
|
34
|
-
skills: {
|
|
37
|
+
skills: {
|
|
38
|
+
level: 25,
|
|
39
|
+
sword: 10,
|
|
40
|
+
shielding: 8,
|
|
41
|
+
strength: 18,
|
|
42
|
+
dexterity: 7,
|
|
43
|
+
resistance: 15,
|
|
44
|
+
},
|
|
35
45
|
equipment: [
|
|
36
|
-
{
|
|
37
|
-
|
|
38
|
-
|
|
46
|
+
{
|
|
47
|
+
slot: 'rightHand',
|
|
48
|
+
itemName: 'Broad Sword',
|
|
49
|
+
itemKey: 'broad-sword',
|
|
50
|
+
rarity: 'Common',
|
|
51
|
+
},
|
|
52
|
+
{ slot: 'armor', itemName: 'Jacket', itemKey: 'jacket', rarity: 'Rare' },
|
|
53
|
+
{
|
|
54
|
+
slot: 'head',
|
|
55
|
+
itemName: 'Iron Helmet',
|
|
56
|
+
itemKey: 'iron-helmet',
|
|
57
|
+
rarity: 'Uncommon',
|
|
58
|
+
},
|
|
39
59
|
],
|
|
40
60
|
textureKey: 'black-knight',
|
|
41
61
|
},
|
|
@@ -57,11 +77,27 @@ const hardcoreListing: ICharacterListing = {
|
|
|
57
77
|
race: 'Elf',
|
|
58
78
|
faction: 'Horde',
|
|
59
79
|
mode: 'Hardcore',
|
|
60
|
-
skills: {
|
|
80
|
+
skills: {
|
|
81
|
+
level: 40,
|
|
82
|
+
magic: 20,
|
|
83
|
+
magicResistance: 18,
|
|
84
|
+
dexterity: 12,
|
|
85
|
+
stamina: 15,
|
|
86
|
+
},
|
|
61
87
|
equipment: [
|
|
62
|
-
{
|
|
63
|
-
|
|
64
|
-
|
|
88
|
+
{
|
|
89
|
+
slot: 'rightHand',
|
|
90
|
+
itemName: 'Fire Staff',
|
|
91
|
+
itemKey: 'fire-staff',
|
|
92
|
+
rarity: 'Epic',
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
slot: 'armor',
|
|
96
|
+
itemName: 'Mystic Cape',
|
|
97
|
+
itemKey: 'mystic-cape',
|
|
98
|
+
rarity: 'Legendary',
|
|
99
|
+
},
|
|
100
|
+
{ slot: 'neck', itemName: 'Bandana', itemKey: 'bandana', rarity: 'Rare' },
|
|
65
101
|
],
|
|
66
102
|
textureKey: 'pink-mage-1',
|
|
67
103
|
},
|
|
@@ -83,13 +119,22 @@ const pendingListing: ICharacterListing = {
|
|
|
83
119
|
},
|
|
84
120
|
};
|
|
85
121
|
|
|
86
|
-
const ModalWrapper: React.FC<{ listing: ICharacterListing }> = ({
|
|
122
|
+
const ModalWrapper: React.FC<{ listing: ICharacterListing }> = ({
|
|
123
|
+
listing,
|
|
124
|
+
}) => {
|
|
87
125
|
const [isOpen, setIsOpen] = useState(true);
|
|
88
126
|
const [lastAction, setLastAction] = useState<string | null>(null);
|
|
89
127
|
|
|
90
128
|
return (
|
|
91
129
|
<RPGUIRoot>
|
|
92
|
-
<div
|
|
130
|
+
<div
|
|
131
|
+
style={{
|
|
132
|
+
padding: '20px',
|
|
133
|
+
fontFamily: 'monospace',
|
|
134
|
+
fontSize: '12px',
|
|
135
|
+
color: '#fff',
|
|
136
|
+
}}
|
|
137
|
+
>
|
|
93
138
|
{lastAction ? (
|
|
94
139
|
<p>✅ {lastAction}</p>
|
|
95
140
|
) : (
|
|
@@ -97,7 +142,10 @@ const ModalWrapper: React.FC<{ listing: ICharacterListing }> = ({ listing }) =>
|
|
|
97
142
|
)}
|
|
98
143
|
<button
|
|
99
144
|
style={{ marginTop: 8, padding: '6px 12px', cursor: 'pointer' }}
|
|
100
|
-
onClick={() => {
|
|
145
|
+
onClick={() => {
|
|
146
|
+
setIsOpen(true);
|
|
147
|
+
setLastAction(null);
|
|
148
|
+
}}
|
|
101
149
|
>
|
|
102
150
|
Reopen Modal
|
|
103
151
|
</button>
|
|
@@ -119,11 +167,17 @@ const ModalWrapper: React.FC<{ listing: ICharacterListing }> = ({ listing }) =>
|
|
|
119
167
|
);
|
|
120
168
|
};
|
|
121
169
|
|
|
122
|
-
export const WarriorListing: Story = () =>
|
|
170
|
+
export const WarriorListing: Story = () => (
|
|
171
|
+
<ModalWrapper listing={warriorListing} />
|
|
172
|
+
);
|
|
123
173
|
WarriorListing.storyName = 'Standard warrior listing';
|
|
124
174
|
|
|
125
|
-
export const HardcoreMageListing: Story = () =>
|
|
175
|
+
export const HardcoreMageListing: Story = () => (
|
|
176
|
+
<ModalWrapper listing={hardcoreListing} />
|
|
177
|
+
);
|
|
126
178
|
HardcoreMageListing.storyName = 'Hardcore mage — high price';
|
|
127
179
|
|
|
128
|
-
export const PendingPurchase: Story = () =>
|
|
180
|
+
export const PendingPurchase: Story = () => (
|
|
181
|
+
<ModalWrapper listing={pendingListing} />
|
|
182
|
+
);
|
|
129
183
|
PendingPurchase.storyName = 'Listing with pending purchase (buy disabled)';
|