@rpg-engine/long-bow 0.8.140 → 0.8.145
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/BlueprintSearchModal.d.ts +17 -0
- package/dist/components/Marketplace/BuyOrderDetailsModal.d.ts +17 -0
- package/dist/components/Marketplace/BuyOrderPanel.d.ts +24 -0
- package/dist/components/Marketplace/BuyOrderRows.d.ts +13 -0
- package/dist/components/Marketplace/BuyPanel.d.ts +9 -1
- package/dist/components/Marketplace/HistoryPanel.d.ts +18 -0
- package/dist/components/Marketplace/ManagmentPanel.d.ts +4 -3
- package/dist/components/Marketplace/Marketplace.d.ts +38 -2
- package/dist/components/Marketplace/MarketplaceRows.d.ts +13 -1
- package/dist/components/Marketplace/MarketplaceSettingsPanel.d.ts +8 -0
- package/dist/components/Store/PaymentMethodModal.d.ts +1 -0
- package/dist/components/shared/LabelPill/LabelPill.d.ts +9 -0
- package/dist/components/shared/LabelPill/index.d.ts +1 -0
- package/dist/components/shared/SegmentedToggle/SegmentedToggle.d.ts +12 -0
- package/dist/components/shared/SegmentedToggle/index.d.ts +1 -0
- package/dist/components/shared/Tabs/Tabs.d.ts +13 -0
- package/dist/components/shared/Tabs/index.d.ts +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/long-bow.cjs.development.js +12074 -1449
- 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 +12060 -1450
- package/dist/long-bow.esm.js.map +1 -1
- package/dist/stories/Features/marketplace/BlueprintSearchModal.stories.d.ts +1 -0
- package/dist/stories/Features/marketplace/BuyOrderPanel.stories.d.ts +1 -0
- package/dist/stories/Features/marketplace/BuyOrderRows.stories.d.ts +1 -0
- package/dist/stories/Features/marketplace/HistoryPanel.stories.d.ts +1 -0
- package/dist/stories/Features/trading/MarketplaceRows.stories.d.ts +2 -1
- package/dist/stories/UI/buttonsAndInputs/SegmentedToggle.stories.d.ts +6 -0
- package/dist/stories/UI/text/LabelPill.stories.d.ts +7 -0
- package/dist/utils/atlasUtils.d.ts +2 -0
- package/package.json +3 -2
- package/src/components/ConfirmModal.tsx +50 -27
- package/src/components/Marketplace/BlueprintSearchModal.tsx +418 -0
- package/src/components/Marketplace/BuyOrderDetailsModal.tsx +307 -0
- package/src/components/Marketplace/BuyOrderPanel.tsx +266 -0
- package/src/components/Marketplace/BuyOrderRows.tsx +287 -0
- package/src/components/Marketplace/BuyPanel.tsx +486 -170
- package/src/components/Marketplace/HistoryPanel.tsx +422 -0
- package/src/components/Marketplace/ManagmentPanel.tsx +176 -98
- package/src/components/Marketplace/Marketplace.tsx +227 -40
- package/src/components/Marketplace/MarketplaceBuyModal.tsx +1 -0
- package/src/components/Marketplace/MarketplaceRows.tsx +274 -80
- package/src/components/Marketplace/MarketplaceSettingsPanel.tsx +128 -0
- package/src/components/Store/CartView.tsx +11 -0
- package/src/components/Store/PaymentMethodModal.tsx +26 -9
- package/src/components/shared/LabelPill/LabelPill.tsx +45 -0
- package/src/components/shared/LabelPill/index.ts +1 -0
- package/src/components/shared/SegmentedToggle/SegmentedToggle.tsx +61 -0
- package/src/components/shared/SegmentedToggle/index.ts +1 -0
- package/src/components/shared/SpriteFromAtlas.tsx +7 -2
- package/src/components/shared/Tabs/Tabs.tsx +60 -0
- package/src/components/shared/Tabs/index.ts +1 -0
- package/src/index.tsx +5 -0
- package/src/mocks/atlas/items/items.json +33998 -25238
- package/src/mocks/atlas/items/items.png +0 -0
- package/src/mocks/itemContainer.mocks.ts +31 -0
- package/src/stories/Features/marketplace/BlueprintSearchModal.stories.tsx +145 -0
- package/src/stories/Features/marketplace/BuyOrderPanel.stories.tsx +207 -0
- package/src/stories/Features/marketplace/BuyOrderRows.stories.tsx +116 -0
- package/src/stories/Features/marketplace/HistoryPanel.stories.tsx +157 -0
- package/src/stories/Features/trading/Marketplace.stories.tsx +109 -0
- package/src/stories/Features/trading/MarketplaceRows.stories.tsx +11 -0
- package/src/stories/UI/buttonsAndInputs/SegmentedToggle.stories.tsx +54 -0
- package/src/stories/UI/text/LabelPill.stories.tsx +43 -0
- package/src/utils/__test__/atlasUtils.spec.ts +26 -0
- package/src/utils/atlasUtils.ts +80 -0
|
@@ -3,16 +3,19 @@ import {
|
|
|
3
3
|
getItemTextureKeyPath,
|
|
4
4
|
IEquipmentSet,
|
|
5
5
|
IItem,
|
|
6
|
+
IMarketplaceItem,
|
|
6
7
|
} from '@rpg-engine/shared';
|
|
7
|
-
import
|
|
8
|
+
import { Coins } from 'pixelarticons/react/Coins';
|
|
9
|
+
import { Delete } from 'pixelarticons/react/Delete';
|
|
10
|
+
import React, { useState } from 'react';
|
|
8
11
|
import styled from 'styled-components';
|
|
9
|
-
import { uiColors } from '../../constants/uiColors';
|
|
10
12
|
import { uiFonts } from '../../constants/uiFonts';
|
|
11
|
-
import { Button, ButtonTypes } from '../Button';
|
|
12
13
|
import { ItemInfoWrapper } from '../Item/Cards/ItemInfoWrapper';
|
|
13
14
|
import { onRenderGems } from '../Item/Inventory/ItemGem';
|
|
14
15
|
import { rarityColor } from '../Item/Inventory/ItemSlotRarity';
|
|
16
|
+
import { CTAButton } from '../shared/CTAButton/CTAButton';
|
|
15
17
|
import { Ellipsis } from '../shared/Ellipsis';
|
|
18
|
+
import { SimpleTooltip } from '../shared/SimpleTooltip';
|
|
16
19
|
import { SpriteFromAtlas } from '../shared/SpriteFromAtlas';
|
|
17
20
|
|
|
18
21
|
export interface IMarketPlaceRowsPropos {
|
|
@@ -46,7 +49,7 @@ export const MarketplaceRows: React.FC<IMarketPlaceRowsPropos> = ({
|
|
|
46
49
|
|
|
47
50
|
return (
|
|
48
51
|
<MarketplaceWrapper>
|
|
49
|
-
<
|
|
52
|
+
<ItemSection>
|
|
50
53
|
<SpriteContainer>
|
|
51
54
|
<ItemInfoWrapper
|
|
52
55
|
item={item}
|
|
@@ -73,7 +76,6 @@ export const MarketplaceRows: React.FC<IMarketPlaceRowsPropos> = ({
|
|
|
73
76
|
imgClassname="sprite-from-atlas-img--item"
|
|
74
77
|
/>
|
|
75
78
|
</RarityContainer>
|
|
76
|
-
|
|
77
79
|
<QuantityContainer>
|
|
78
80
|
{item.stackQty &&
|
|
79
81
|
item.stackQty > 1 &&
|
|
@@ -81,120 +83,312 @@ export const MarketplaceRows: React.FC<IMarketPlaceRowsPropos> = ({
|
|
|
81
83
|
</QuantityContainer>
|
|
82
84
|
</ItemInfoWrapper>
|
|
83
85
|
</SpriteContainer>
|
|
84
|
-
|
|
85
|
-
|
|
86
|
+
|
|
87
|
+
<ItemDetails>
|
|
88
|
+
<ItemName>
|
|
86
89
|
<Ellipsis maxLines={1} maxWidth="200px" fontSize="10px">
|
|
87
90
|
{item.name}
|
|
88
91
|
</Ellipsis>
|
|
89
|
-
</
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
92
|
+
</ItemName>
|
|
93
|
+
<PriceRow>
|
|
94
|
+
<GoldPriceRow>
|
|
95
|
+
<GoldIcon>
|
|
96
|
+
<SimpleTooltip content="Gold Coin" direction="top">
|
|
97
|
+
<SpriteFromAtlas
|
|
98
|
+
atlasIMG={atlasIMG}
|
|
99
|
+
atlasJSON={atlasJSON}
|
|
100
|
+
spriteKey="others/gold-coin-qty-5.png"
|
|
101
|
+
imgScale={1}
|
|
102
|
+
/>
|
|
103
|
+
</SimpleTooltip>
|
|
104
|
+
</GoldIcon>
|
|
105
|
+
<GoldPrice>{itemPrice}</GoldPrice>
|
|
106
|
+
</GoldPriceRow>
|
|
107
|
+
{dcEquivalentPrice !== undefined && (
|
|
108
|
+
<DCPriceRow>
|
|
109
|
+
<DCCoinWrapper>
|
|
110
|
+
<SimpleTooltip content="Definya Coin" direction="top">
|
|
111
|
+
<SpriteFromAtlas
|
|
112
|
+
atlasIMG={atlasIMG}
|
|
113
|
+
atlasJSON={atlasJSON}
|
|
114
|
+
spriteKey="others/definya-coin.png"
|
|
115
|
+
imgScale={1}
|
|
116
|
+
/>
|
|
117
|
+
</SimpleTooltip>
|
|
118
|
+
</DCCoinWrapper>
|
|
119
|
+
<DCPrice>{formatDCAmount(dcEquivalentPrice)}</DCPrice>
|
|
120
|
+
</DCPriceRow>
|
|
121
|
+
)}
|
|
122
|
+
</PriceRow>
|
|
123
|
+
</ItemDetails>
|
|
124
|
+
</ItemSection>
|
|
125
|
+
|
|
126
|
+
<ActionSection>
|
|
127
|
+
<CTAButton
|
|
128
|
+
icon={onMarketPlaceItemBuy ? <Coins width={18} height={18} /> : <Delete width={18} height={18} />}
|
|
129
|
+
label={onMarketPlaceItemBuy ? 'Buy' : 'Remove'}
|
|
130
|
+
disabled={disabled}
|
|
131
|
+
onClick={() => {
|
|
132
|
+
if (disabled) return;
|
|
133
|
+
onMarketPlaceItemBuy?.();
|
|
134
|
+
onMarketPlaceItemRemove?.();
|
|
135
|
+
}}
|
|
136
|
+
iconColor={onMarketPlaceItemBuy ? '#f59e0b' : '#ef4444'}
|
|
137
|
+
/>
|
|
138
|
+
</ActionSection>
|
|
139
|
+
</MarketplaceWrapper>
|
|
140
|
+
);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
export interface IGroupedMarketplaceRowProps {
|
|
144
|
+
bestListing: IMarketplaceItem;
|
|
145
|
+
otherListings: IMarketplaceItem[];
|
|
146
|
+
atlasJSON: any;
|
|
147
|
+
atlasIMG: any;
|
|
148
|
+
equipmentSet?: IEquipmentSet | null;
|
|
149
|
+
dcToGoldSwapRate?: number;
|
|
150
|
+
getDCEquivalentPrice: (goldPrice: number) => number;
|
|
151
|
+
characterId: string;
|
|
152
|
+
onBuy: (id: string) => void;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export const GroupedMarketplaceRow: React.FC<IGroupedMarketplaceRowProps> = ({
|
|
156
|
+
bestListing,
|
|
157
|
+
otherListings,
|
|
158
|
+
atlasJSON,
|
|
159
|
+
atlasIMG,
|
|
160
|
+
equipmentSet,
|
|
161
|
+
dcToGoldSwapRate = 0,
|
|
162
|
+
getDCEquivalentPrice,
|
|
163
|
+
characterId,
|
|
164
|
+
onBuy,
|
|
165
|
+
}) => {
|
|
166
|
+
const [expanded, setExpanded] = useState(false);
|
|
167
|
+
const totalOffers = otherListings.length + 1;
|
|
168
|
+
const hasMultiple = otherListings.length > 0;
|
|
169
|
+
|
|
170
|
+
return (
|
|
171
|
+
<GroupWrapper>
|
|
172
|
+
<GroupHeader
|
|
173
|
+
onClick={hasMultiple ? () => setExpanded(!expanded) : undefined}
|
|
174
|
+
clickable={hasMultiple}
|
|
175
|
+
>
|
|
176
|
+
<MarketplaceRows
|
|
177
|
+
atlasIMG={atlasIMG}
|
|
178
|
+
atlasJSON={atlasJSON}
|
|
179
|
+
item={bestListing.item}
|
|
180
|
+
itemPrice={bestListing.price}
|
|
181
|
+
dcEquivalentPrice={
|
|
182
|
+
dcToGoldSwapRate > 0
|
|
183
|
+
? getDCEquivalentPrice(bestListing.price)
|
|
184
|
+
: undefined
|
|
185
|
+
}
|
|
186
|
+
equipmentSet={equipmentSet}
|
|
187
|
+
onMarketPlaceItemBuy={() => onBuy(bestListing._id)}
|
|
188
|
+
disabled={bestListing.owner === characterId}
|
|
189
|
+
/>
|
|
190
|
+
{hasMultiple && (
|
|
191
|
+
<GroupMeta>
|
|
192
|
+
<OfferBadge>{totalOffers} offers</OfferBadge>
|
|
193
|
+
<Chevron expanded={expanded}>▸</Chevron>
|
|
194
|
+
</GroupMeta>
|
|
195
|
+
)}
|
|
196
|
+
</GroupHeader>
|
|
197
|
+
|
|
198
|
+
{expanded && (
|
|
199
|
+
<SubRows>
|
|
200
|
+
{otherListings.map((listing) => (
|
|
201
|
+
<MarketplaceRows
|
|
202
|
+
key={listing._id}
|
|
97
203
|
atlasIMG={atlasIMG}
|
|
98
204
|
atlasJSON={atlasJSON}
|
|
99
|
-
|
|
100
|
-
|
|
205
|
+
item={listing.item}
|
|
206
|
+
itemPrice={listing.price}
|
|
207
|
+
dcEquivalentPrice={
|
|
208
|
+
dcToGoldSwapRate > 0
|
|
209
|
+
? getDCEquivalentPrice(listing.price)
|
|
210
|
+
: undefined
|
|
211
|
+
}
|
|
212
|
+
equipmentSet={equipmentSet}
|
|
213
|
+
onMarketPlaceItemBuy={() => onBuy(listing._id)}
|
|
214
|
+
disabled={listing.owner === characterId}
|
|
101
215
|
/>
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
${itemPrice}
|
|
107
|
-
</Ellipsis>
|
|
108
|
-
</p>
|
|
109
|
-
</PriceValue>
|
|
110
|
-
{dcEquivalentPrice !== undefined && (
|
|
111
|
-
<DCPriceLabel>{formatDCAmount(dcEquivalentPrice)} DC</DCPriceLabel>
|
|
112
|
-
)}
|
|
113
|
-
</ItemIconContainer>
|
|
114
|
-
<ButtonContainer>
|
|
115
|
-
<Button
|
|
116
|
-
buttonType={ButtonTypes.RPGUIButton}
|
|
117
|
-
disabled={disabled}
|
|
118
|
-
onPointerDown={() => {
|
|
119
|
-
if (disabled) return;
|
|
120
|
-
|
|
121
|
-
onMarketPlaceItemBuy?.();
|
|
122
|
-
onMarketPlaceItemRemove?.();
|
|
123
|
-
}}
|
|
124
|
-
>
|
|
125
|
-
{onMarketPlaceItemBuy ? 'Buy' : 'Remove'}
|
|
126
|
-
</Button>
|
|
127
|
-
</ButtonContainer>
|
|
128
|
-
</Flex>
|
|
129
|
-
</MarketplaceWrapper>
|
|
216
|
+
))}
|
|
217
|
+
</SubRows>
|
|
218
|
+
)}
|
|
219
|
+
</GroupWrapper>
|
|
130
220
|
);
|
|
131
221
|
};
|
|
132
222
|
|
|
223
|
+
const GroupWrapper = styled.div`
|
|
224
|
+
margin-bottom: 2px;
|
|
225
|
+
`;
|
|
226
|
+
|
|
227
|
+
const GroupHeader = styled.div<{ clickable: boolean }>`
|
|
228
|
+
position: relative;
|
|
229
|
+
cursor: ${({ clickable }) => (clickable ? 'pointer' : 'default')};
|
|
230
|
+
`;
|
|
231
|
+
|
|
232
|
+
const GroupMeta = styled.div`
|
|
233
|
+
position: absolute;
|
|
234
|
+
right: 180px;
|
|
235
|
+
top: 50%;
|
|
236
|
+
transform: translateY(-50%);
|
|
237
|
+
display: flex;
|
|
238
|
+
align-items: center;
|
|
239
|
+
gap: 6px;
|
|
240
|
+
pointer-events: none;
|
|
241
|
+
`;
|
|
242
|
+
|
|
243
|
+
const OfferBadge = styled.span`
|
|
244
|
+
font-family: 'Press Start 2P', cursive;
|
|
245
|
+
font-size: 0.5rem;
|
|
246
|
+
color: rgba(255, 255, 255, 0.5);
|
|
247
|
+
background: rgba(255, 255, 255, 0.08);
|
|
248
|
+
padding: 2px 6px;
|
|
249
|
+
border-radius: 8px;
|
|
250
|
+
white-space: nowrap;
|
|
251
|
+
`;
|
|
252
|
+
|
|
253
|
+
const Chevron = styled.span<{ expanded: boolean }>`
|
|
254
|
+
display: inline-block;
|
|
255
|
+
font-size: 0.7rem;
|
|
256
|
+
color: rgba(255, 255, 255, 0.4);
|
|
257
|
+
transition: transform 0.2s ease;
|
|
258
|
+
transform: rotate(${({ expanded }) => (expanded ? '90deg' : '0deg')});
|
|
259
|
+
`;
|
|
260
|
+
|
|
261
|
+
const SubRows = styled.div`
|
|
262
|
+
margin-left: 12px;
|
|
263
|
+
padding-left: 8px;
|
|
264
|
+
border-left: 2px solid rgba(245, 158, 11, 0.25);
|
|
265
|
+
|
|
266
|
+
> div > div {
|
|
267
|
+
background: rgba(0, 0, 0, 0.4);
|
|
268
|
+
}
|
|
269
|
+
`;
|
|
270
|
+
|
|
133
271
|
const MarketplaceWrapper = styled.div`
|
|
134
|
-
margin: auto;
|
|
135
272
|
display: flex;
|
|
273
|
+
align-items: center;
|
|
136
274
|
justify-content: space-between;
|
|
137
|
-
padding: 0.
|
|
275
|
+
padding: 0.6rem 1rem;
|
|
276
|
+
margin-bottom: 4px;
|
|
277
|
+
background: rgba(0, 0, 0, 0.25);
|
|
278
|
+
border: 1px solid rgba(255, 255, 255, 0.05);
|
|
279
|
+
border-radius: 6px;
|
|
280
|
+
border-left: 4px solid transparent;
|
|
281
|
+
transition: all 0.2s ease-in-out;
|
|
138
282
|
|
|
139
283
|
&:hover {
|
|
140
|
-
background
|
|
284
|
+
background: rgba(245, 158, 11, 0.08);
|
|
285
|
+
border-color: rgba(245, 158, 11, 0.2);
|
|
286
|
+
border-left-color: #f59e0b;
|
|
287
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
|
288
|
+
transform: translateY(-1px);
|
|
141
289
|
}
|
|
290
|
+
`;
|
|
142
291
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
292
|
+
const ItemSection = styled.div`
|
|
293
|
+
display: flex;
|
|
294
|
+
align-items: center;
|
|
295
|
+
gap: 0.75rem;
|
|
296
|
+
flex: 1;
|
|
297
|
+
min-width: 0;
|
|
146
298
|
`;
|
|
147
299
|
|
|
148
|
-
const
|
|
149
|
-
position:
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
left: 25px;
|
|
153
|
-
font-size: ${uiFonts.size.xsmall} !important;
|
|
300
|
+
const SpriteContainer = styled.div`
|
|
301
|
+
position: relative;
|
|
302
|
+
flex-shrink: 0;
|
|
303
|
+
min-width: 44px; /* Ensure wide stack quantities don't overlap ItemDetails */
|
|
154
304
|
`;
|
|
155
305
|
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
306
|
+
const ItemDetails = styled.div`
|
|
307
|
+
display: flex;
|
|
308
|
+
flex-direction: column;
|
|
309
|
+
gap: 0.2rem;
|
|
310
|
+
min-width: 0;
|
|
311
|
+
margin-left: 1rem;
|
|
312
|
+
`;
|
|
313
|
+
|
|
314
|
+
const ItemName = styled.div`
|
|
315
|
+
font-family: 'Press Start 2P', cursive;
|
|
316
|
+
font-size: 0.65rem;
|
|
317
|
+
color: #ffffff;
|
|
162
318
|
`;
|
|
163
319
|
|
|
164
|
-
const
|
|
320
|
+
const PriceRow = styled.div`
|
|
165
321
|
display: flex;
|
|
166
|
-
|
|
322
|
+
flex-direction: row;
|
|
323
|
+
align-items: center;
|
|
324
|
+
gap: 0.5rem;
|
|
325
|
+
margin-top: 0.2rem;
|
|
167
326
|
`;
|
|
168
327
|
|
|
169
|
-
const
|
|
328
|
+
const GoldPriceRow = styled.div`
|
|
170
329
|
display: flex;
|
|
171
|
-
justify-content: flex-start;
|
|
172
330
|
align-items: center;
|
|
331
|
+
gap: 0.3rem;
|
|
173
332
|
`;
|
|
174
333
|
|
|
175
|
-
const
|
|
334
|
+
const GoldIcon = styled.span`
|
|
335
|
+
display: flex;
|
|
336
|
+
align-items: center;
|
|
337
|
+
justify-content: center;
|
|
176
338
|
position: relative;
|
|
177
|
-
top: -0.
|
|
178
|
-
left: 0.5rem;
|
|
339
|
+
top: -0.6rem;
|
|
340
|
+
left: -0.5rem;
|
|
179
341
|
`;
|
|
180
|
-
|
|
342
|
+
|
|
343
|
+
const GoldPrice = styled.span`
|
|
344
|
+
font-family: 'Press Start 2P', cursive;
|
|
345
|
+
font-size: 0.6rem !important;
|
|
346
|
+
color: #fef08a;
|
|
347
|
+
line-height: 1;
|
|
348
|
+
`;
|
|
349
|
+
|
|
350
|
+
const DCPriceRow = styled.div`
|
|
351
|
+
display: flex;
|
|
352
|
+
align-items: center;
|
|
353
|
+
gap: 0.3rem;
|
|
354
|
+
margin-left: 0.5rem;
|
|
355
|
+
`;
|
|
356
|
+
|
|
357
|
+
const DCCoinWrapper = styled.span`
|
|
358
|
+
display: flex;
|
|
359
|
+
align-items: center;
|
|
360
|
+
justify-content: center;
|
|
181
361
|
position: relative;
|
|
182
|
-
|
|
362
|
+
top: -0.6rem;
|
|
363
|
+
left: -0.5rem;
|
|
183
364
|
`;
|
|
184
365
|
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
366
|
+
const DCPrice = styled.span`
|
|
367
|
+
font-family: 'Press Start 2P', cursive;
|
|
368
|
+
font-size: 0.6rem !important;
|
|
369
|
+
color: rgba(254, 240, 138, 0.65);
|
|
189
370
|
white-space: nowrap;
|
|
190
371
|
`;
|
|
191
372
|
|
|
192
|
-
const
|
|
193
|
-
|
|
373
|
+
const ActionSection = styled.div`
|
|
374
|
+
flex-shrink: 0;
|
|
375
|
+
margin-left: 0.75rem;
|
|
376
|
+
`;
|
|
377
|
+
|
|
378
|
+
const QuantityContainer = styled.p`
|
|
379
|
+
position: absolute;
|
|
380
|
+
display: block;
|
|
381
|
+
top: 15px;
|
|
382
|
+
left: -8px;
|
|
383
|
+
font-size: ${uiFonts.size.xsmall} !important;
|
|
194
384
|
`;
|
|
195
385
|
|
|
196
|
-
const
|
|
197
|
-
|
|
386
|
+
const GemContainer = styled.p`
|
|
387
|
+
position: absolute;
|
|
388
|
+
display: block;
|
|
389
|
+
top: -5px;
|
|
390
|
+
left: -10px;
|
|
391
|
+
font-size: ${uiFonts.size.xsmall} !important;
|
|
198
392
|
`;
|
|
199
393
|
|
|
200
394
|
const RarityContainer = styled.div<{ item: IItem }>`
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { MarketplaceAcceptedCurrency } from '@rpg-engine/shared';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import styled from 'styled-components';
|
|
4
|
+
|
|
5
|
+
export { MarketplaceAcceptedCurrency };
|
|
6
|
+
|
|
7
|
+
export interface IMarketplaceSettingsPanelProps {
|
|
8
|
+
acceptedCurrency: MarketplaceAcceptedCurrency;
|
|
9
|
+
onAcceptedCurrencyChange: (value: MarketplaceAcceptedCurrency) => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const CURRENCY_OPTIONS: { value: MarketplaceAcceptedCurrency; label: string; description: string }[] = [
|
|
13
|
+
{
|
|
14
|
+
value: MarketplaceAcceptedCurrency.GoldOrDc,
|
|
15
|
+
label: 'Gold or DC',
|
|
16
|
+
description: 'Accept both Gold and Definya Coin as payment',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
value: MarketplaceAcceptedCurrency.Gold,
|
|
20
|
+
label: 'Gold only',
|
|
21
|
+
description: 'Only accept Gold as payment',
|
|
22
|
+
},
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
export const MarketplaceSettingsPanel: React.FC<IMarketplaceSettingsPanelProps> = ({
|
|
26
|
+
acceptedCurrency,
|
|
27
|
+
onAcceptedCurrencyChange,
|
|
28
|
+
}) => {
|
|
29
|
+
return (
|
|
30
|
+
<Wrapper>
|
|
31
|
+
<Section>
|
|
32
|
+
<SectionLabel>Accepted Currency</SectionLabel>
|
|
33
|
+
<OptionsGrid>
|
|
34
|
+
{CURRENCY_OPTIONS.map(option => (
|
|
35
|
+
<OptionCard
|
|
36
|
+
key={option.value}
|
|
37
|
+
$active={acceptedCurrency === option.value}
|
|
38
|
+
onClick={() => onAcceptedCurrencyChange(option.value)}
|
|
39
|
+
>
|
|
40
|
+
<OptionLabel $active={acceptedCurrency === option.value}>{option.label}</OptionLabel>
|
|
41
|
+
<OptionDescription>{option.description}</OptionDescription>
|
|
42
|
+
{acceptedCurrency === option.value && <ActiveBadge>Active</ActiveBadge>}
|
|
43
|
+
</OptionCard>
|
|
44
|
+
))}
|
|
45
|
+
</OptionsGrid>
|
|
46
|
+
<Hint>Buyers will only be able to pay using the currency you accept.</Hint>
|
|
47
|
+
</Section>
|
|
48
|
+
</Wrapper>
|
|
49
|
+
);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const Wrapper = styled.div`
|
|
53
|
+
width: 95%;
|
|
54
|
+
margin: 0 auto;
|
|
55
|
+
padding-top: 4px;
|
|
56
|
+
`;
|
|
57
|
+
|
|
58
|
+
const Section = styled.div`
|
|
59
|
+
background: rgba(0, 0, 0, 0.15);
|
|
60
|
+
border-radius: 4px;
|
|
61
|
+
border: 1px solid rgba(255, 255, 255, 0.05);
|
|
62
|
+
padding: 16px 18px;
|
|
63
|
+
`;
|
|
64
|
+
|
|
65
|
+
const SectionLabel = styled.p`
|
|
66
|
+
margin: 0 0 14px 0;
|
|
67
|
+
font-size: 0.65rem;
|
|
68
|
+
color: #aaa;
|
|
69
|
+
text-transform: uppercase;
|
|
70
|
+
letter-spacing: 1px;
|
|
71
|
+
`;
|
|
72
|
+
|
|
73
|
+
const OptionsGrid = styled.div`
|
|
74
|
+
display: grid;
|
|
75
|
+
grid-template-columns: 1fr 1fr;
|
|
76
|
+
gap: 12px;
|
|
77
|
+
`;
|
|
78
|
+
|
|
79
|
+
const OptionCard = styled.button<{ $active: boolean }>`
|
|
80
|
+
position: relative;
|
|
81
|
+
display: flex;
|
|
82
|
+
flex-direction: column;
|
|
83
|
+
align-items: flex-start;
|
|
84
|
+
gap: 6px;
|
|
85
|
+
padding: 14px 16px;
|
|
86
|
+
background: ${({ $active }) => ($active ? 'rgba(245, 158, 11, 0.12)' : 'rgba(0, 0, 0, 0.25)')};
|
|
87
|
+
border: 2px solid ${({ $active }) => ($active ? '#f59e0b' : 'rgba(255,255,255,0.08)')};
|
|
88
|
+
border-radius: 6px;
|
|
89
|
+
cursor: pointer;
|
|
90
|
+
text-align: left;
|
|
91
|
+
transition: border-color 0.15s, background 0.15s;
|
|
92
|
+
|
|
93
|
+
&:hover {
|
|
94
|
+
border-color: ${({ $active }) => ($active ? '#f59e0b' : 'rgba(255,255,255,0.25)')};
|
|
95
|
+
background: ${({ $active }) => ($active ? 'rgba(245, 158, 11, 0.15)' : 'rgba(255,255,255,0.04)')};
|
|
96
|
+
}
|
|
97
|
+
`;
|
|
98
|
+
|
|
99
|
+
const OptionLabel = styled.span<{ $active: boolean }>`
|
|
100
|
+
font-family: 'Press Start 2P', cursive;
|
|
101
|
+
font-size: 0.6rem;
|
|
102
|
+
color: ${({ $active }) => ($active ? '#f59e0b' : '#cccccc')};
|
|
103
|
+
letter-spacing: 0.5px;
|
|
104
|
+
`;
|
|
105
|
+
|
|
106
|
+
const OptionDescription = styled.span`
|
|
107
|
+
font-size: 0.5rem;
|
|
108
|
+
color: #888;
|
|
109
|
+
line-height: 1.5;
|
|
110
|
+
`;
|
|
111
|
+
|
|
112
|
+
const ActiveBadge = styled.span`
|
|
113
|
+
position: absolute;
|
|
114
|
+
top: 8px;
|
|
115
|
+
right: 10px;
|
|
116
|
+
font-size: 0.45rem;
|
|
117
|
+
color: #f59e0b;
|
|
118
|
+
text-transform: uppercase;
|
|
119
|
+
letter-spacing: 0.5px;
|
|
120
|
+
opacity: 0.8;
|
|
121
|
+
`;
|
|
122
|
+
|
|
123
|
+
const Hint = styled.p`
|
|
124
|
+
margin: 14px 0 0 0;
|
|
125
|
+
font-size: 0.48rem;
|
|
126
|
+
color: #666;
|
|
127
|
+
line-height: 1.6;
|
|
128
|
+
`;
|
|
@@ -61,6 +61,11 @@ export const CartView: React.FC<ICartViewProps> = ({
|
|
|
61
61
|
0
|
|
62
62
|
);
|
|
63
63
|
|
|
64
|
+
const dcTotal = cartItems.reduce(
|
|
65
|
+
(sum, ci) => sum + (ci.item.dcPrice ?? 0) * ci.quantity,
|
|
66
|
+
0
|
|
67
|
+
);
|
|
68
|
+
|
|
64
69
|
const formatPrice = (price: number) => price.toFixed(2);
|
|
65
70
|
|
|
66
71
|
const handlePurchase = async () => {
|
|
@@ -154,6 +159,12 @@ export const CartView: React.FC<ICartViewProps> = ({
|
|
|
154
159
|
<span>Total:</span>
|
|
155
160
|
<span>${formatPrice(total)}</span>
|
|
156
161
|
</TotalRow>
|
|
162
|
+
{dcTotal > 0 && (
|
|
163
|
+
<TotalRow>
|
|
164
|
+
<span>DC:</span>
|
|
165
|
+
<span>{dcTotal.toLocaleString()} DC</span>
|
|
166
|
+
</TotalRow>
|
|
167
|
+
)}
|
|
157
168
|
{error && <ErrorMessage>{error}</ErrorMessage>}
|
|
158
169
|
</TotalInfo>
|
|
159
170
|
<CTAButton
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useCallback, useState } from 'react';
|
|
1
|
+
import React, { useCallback, useEffect, useState } from 'react';
|
|
2
2
|
import { FaTimes } from 'react-icons/fa';
|
|
3
3
|
import styled from 'styled-components';
|
|
4
4
|
import ModalPortal from '../Abstractions/ModalPortal';
|
|
@@ -6,6 +6,7 @@ import { Button, ButtonTypes } from '../Button';
|
|
|
6
6
|
|
|
7
7
|
export interface IPaymentMethodModalProps {
|
|
8
8
|
dcBalance: number;
|
|
9
|
+
dcRequired?: number;
|
|
9
10
|
onPayWithDC: () => void;
|
|
10
11
|
onPayWithCard: () => void;
|
|
11
12
|
onClose: () => void;
|
|
@@ -15,12 +16,21 @@ type PaymentMethod = 'dc' | 'card';
|
|
|
15
16
|
|
|
16
17
|
export const PaymentMethodModal: React.FC<IPaymentMethodModalProps> = ({
|
|
17
18
|
dcBalance,
|
|
19
|
+
dcRequired,
|
|
18
20
|
onPayWithDC,
|
|
19
21
|
onPayWithCard,
|
|
20
22
|
onClose,
|
|
21
23
|
}) => {
|
|
22
24
|
const [selected, setSelected] = useState<PaymentMethod>('card');
|
|
23
25
|
|
|
26
|
+
const dcDisabled = dcRequired !== undefined && dcBalance < dcRequired;
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (dcDisabled && selected === 'dc') {
|
|
30
|
+
setSelected('card');
|
|
31
|
+
}
|
|
32
|
+
}, [dcDisabled, selected]);
|
|
33
|
+
|
|
24
34
|
const stopPropagation = useCallback(
|
|
25
35
|
(e: React.MouseEvent | React.TouchEvent | React.PointerEvent) => {
|
|
26
36
|
e.stopPropagation();
|
|
@@ -29,12 +39,17 @@ export const PaymentMethodModal: React.FC<IPaymentMethodModalProps> = ({
|
|
|
29
39
|
);
|
|
30
40
|
|
|
31
41
|
const handleConfirm = useCallback(() => {
|
|
32
|
-
if (selected === 'dc') {
|
|
42
|
+
if (selected === 'dc' && !dcDisabled) {
|
|
33
43
|
onPayWithDC();
|
|
34
44
|
} else {
|
|
35
45
|
onPayWithCard();
|
|
36
46
|
}
|
|
37
|
-
}, [selected, onPayWithDC, onPayWithCard]);
|
|
47
|
+
}, [selected, dcDisabled, onPayWithDC, onPayWithCard]);
|
|
48
|
+
|
|
49
|
+
const dcSubText =
|
|
50
|
+
dcRequired !== undefined
|
|
51
|
+
? `${dcBalance.toLocaleString()} DC available · ${dcRequired.toLocaleString()} DC needed`
|
|
52
|
+
: `${dcBalance.toLocaleString()} DC available`;
|
|
38
53
|
|
|
39
54
|
return (
|
|
40
55
|
<ModalPortal>
|
|
@@ -66,12 +81,13 @@ export const PaymentMethodModal: React.FC<IPaymentMethodModalProps> = ({
|
|
|
66
81
|
|
|
67
82
|
<RadioOption
|
|
68
83
|
$selected={selected === 'dc'}
|
|
69
|
-
|
|
84
|
+
$disabled={dcDisabled}
|
|
85
|
+
onPointerDown={dcDisabled ? undefined : () => setSelected('dc')}
|
|
70
86
|
>
|
|
71
87
|
<RadioCircle $selected={selected === 'dc'} />
|
|
72
88
|
<OptionText>
|
|
73
89
|
<OptionLabel>Definya Coin</OptionLabel>
|
|
74
|
-
<OptionSub>{
|
|
90
|
+
<OptionSub>{dcSubText}</OptionSub>
|
|
75
91
|
</OptionText>
|
|
76
92
|
</RadioOption>
|
|
77
93
|
</Options>
|
|
@@ -157,7 +173,7 @@ const Options = styled.div`
|
|
|
157
173
|
gap: 8px;
|
|
158
174
|
`;
|
|
159
175
|
|
|
160
|
-
const RadioOption = styled.div<{ $selected: boolean }>`
|
|
176
|
+
const RadioOption = styled.div<{ $selected: boolean; $disabled?: boolean }>`
|
|
161
177
|
display: flex;
|
|
162
178
|
align-items: center;
|
|
163
179
|
gap: 12px;
|
|
@@ -165,11 +181,12 @@ const RadioOption = styled.div<{ $selected: boolean }>`
|
|
|
165
181
|
border: 1px solid ${({ $selected }) => ($selected ? '#f59e0b' : 'rgba(255,255,255,0.15)')};
|
|
166
182
|
border-radius: 6px;
|
|
167
183
|
background: ${({ $selected }) => ($selected ? 'rgba(245,158,11,0.1)' : 'transparent')};
|
|
168
|
-
cursor: pointer;
|
|
169
|
-
|
|
184
|
+
cursor: ${({ $disabled }) => ($disabled ? 'not-allowed' : 'pointer')};
|
|
185
|
+
opacity: ${({ $disabled }) => ($disabled ? 0.4 : 1)};
|
|
186
|
+
transition: border-color 0.15s, background 0.15s, opacity 0.15s;
|
|
170
187
|
|
|
171
188
|
&:hover {
|
|
172
|
-
border-color: #f59e0b;
|
|
189
|
+
border-color: ${({ $disabled }) => ($disabled ? 'rgba(255,255,255,0.15)' : '#f59e0b')};
|
|
173
190
|
}
|
|
174
191
|
`;
|
|
175
192
|
|