@rpg-engine/long-bow 0.8.139 → 0.8.141

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.
@@ -1,21 +1,23 @@
1
1
  import { IEquipmentSet, IItem, IMarketplaceItem } from '@rpg-engine/shared';
2
+ import { Coins } from 'pixelarticons/react/Coins';
3
+ import { ShoppingBag } from 'pixelarticons/react/ShoppingBag';
2
4
  import React, { useEffect, useRef, useState } from 'react';
3
5
  import styled from 'styled-components';
4
6
  import { uiColors } from '../../constants/uiColors';
5
- import { uiFonts } from '../../constants/uiFonts';
6
- import { Button, ButtonTypes } from '../Button';
7
7
  import { ConfirmModal } from '../ConfirmModal';
8
8
  import { Input } from '../Input';
9
9
  import { ItemSlot } from '../Item/Inventory/ItemSlot';
10
+ import { CTAButton } from '../shared/CTAButton/CTAButton';
11
+ import { SpriteFromAtlas } from '../shared/SpriteFromAtlas';
10
12
  import { MarketplaceRows } from './MarketplaceRows';
11
13
 
12
14
  export interface IManagmentPanelProps {
13
15
  items: IMarketplaceItem[];
14
16
  atlasJSON: any;
15
17
  atlasIMG: any;
16
- onChangeNameInput: (value: string) => void;
17
18
  equipmentSet?: IEquipmentSet | null;
18
19
  availableGold: number;
20
+ dcBalance?: number;
19
21
  onMarketPlaceItemRemove?: (marketPlaceItemId: string) => void;
20
22
  selectedItemToSell: IItem | null;
21
23
  onSelectedItemToSellRemove: (item: IItem) => void;
@@ -30,9 +32,9 @@ export const ManagmentPanel: React.FC<IManagmentPanelProps> = ({
30
32
  items,
31
33
  atlasIMG,
32
34
  atlasJSON,
33
- onChangeNameInput,
34
35
  equipmentSet,
35
36
  availableGold,
37
+ dcBalance,
36
38
  onMarketPlaceItemRemove,
37
39
  selectedItemToSell,
38
40
  onSelectedItemToSellRemove,
@@ -42,7 +44,6 @@ export const ManagmentPanel: React.FC<IManagmentPanelProps> = ({
42
44
  onMoneyWithdraw,
43
45
  currentPage,
44
46
  }) => {
45
- const [name, setName] = useState('');
46
47
  const [price, setPrice] = useState('');
47
48
  const [isCreatingOffer, setIsCreatingOffer] = useState(false);
48
49
  const [removingItemId, setRemovingItemId] = useState<string | null>(null);
@@ -81,26 +82,14 @@ export const ManagmentPanel: React.FC<IManagmentPanelProps> = ({
81
82
  message="Are you sure to remove this item?"
82
83
  />
83
84
  )}
84
- <InputWrapper>
85
- <p>Search By Name</p>
86
- <Input
87
- onChange={e => {
88
- setName(e.target.value);
89
- onChangeNameInput(e.target.value);
90
- }}
91
- value={name}
92
- placeholder="Enter name..."
93
- onBlur={enableHotkeys}
94
- onFocus={disableHotkeys}
95
- />
96
- </InputWrapper>
97
85
 
98
86
  <OptionsWrapper>
99
87
  <InnerOptionsWrapper>
100
- <SellDescription>
101
- Click on item in inventory to sell it
102
- </SellDescription>
103
- <Flex>
88
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '12px' }}>
89
+ <SectionLabel style={{ marginBottom: 0 }}>Create Offer</SectionLabel>
90
+ {!selectedItemToSell && <SellDescription>Select item in inventory to sell</SellDescription>}
91
+ </div>
92
+ <Flex style={{ gap: '15px', alignItems: 'center' }}>
104
93
  <ItemSlot
105
94
  slotIndex={0}
106
95
  atlasIMG={atlasIMG}
@@ -108,52 +97,70 @@ export const ManagmentPanel: React.FC<IManagmentPanelProps> = ({
108
97
  onPointerDown={(_, __, item) => onSelectedItemToSellRemove(item)}
109
98
  item={selectedItemToSell}
110
99
  />
100
+
111
101
  <PriceInputWrapper>
112
- <p>Enter price</p>
113
- <Flex>
102
+ <p>SET PRICE (GOLD)</p>
103
+ <Flex style={{ width: '100%', alignItems: 'center', gap: '8px' }}>
114
104
  <Input
115
- onChange={e => {
116
- setPrice(e.target.value);
117
- }}
105
+ onChange={e => setPrice(e.target.value)}
118
106
  value={price}
119
- placeholder="Enter price..."
107
+ placeholder="Amount..."
120
108
  type="number"
121
109
  disabled={!selectedItemToSell}
122
110
  onBlur={enableHotkeys}
123
111
  onFocus={disableHotkeys}
112
+ className="price-input"
124
113
  />
125
- <Button
126
- buttonType={ButtonTypes.RPGUIButton}
114
+ <SmallCTAButton
115
+ icon={<ShoppingBag width={18} height={18} />}
116
+ label="Offer"
127
117
  disabled={!selectedItemToSell || !price}
128
- onPointerDown={() => {
118
+ onClick={() => {
129
119
  if (selectedItemToSell && price && Number(price)) {
130
120
  setIsCreatingOffer(true);
131
121
  }
132
122
  }}
133
- >
134
- Create offer
135
- </Button>
123
+ />
136
124
  </Flex>
137
125
  </PriceInputWrapper>
138
126
  </Flex>
139
127
  </InnerOptionsWrapper>
128
+
140
129
  <InnerOptionsWrapper>
141
- <AvailableGold $disabled={availableGold === 0}>
142
- <p>Available gold</p>
143
- <p className="center">${availableGold}</p>
144
- <Button
145
- buttonType={ButtonTypes.RPGUIButton}
130
+ <SectionLabel style={{ marginBottom: '8px' }}>Your Wealth</SectionLabel>
131
+ <WealthWrapper>
132
+ <BalanceRow>
133
+ <GoldIcon>
134
+ <SpriteFromAtlas
135
+ atlasIMG={atlasIMG}
136
+ atlasJSON={atlasJSON}
137
+ spriteKey="others/gold-coin-qty-5.png"
138
+ imgScale={1.2}
139
+ />
140
+ </GoldIcon>
141
+ <BalanceAmount $disabled={availableGold === 0}>{availableGold}</BalanceAmount>
142
+ <BalanceCurrencyLabel>GOLD</BalanceCurrencyLabel>
143
+ </BalanceRow>
144
+ {dcBalance !== undefined && (
145
+ <BalanceRow>
146
+ <DCIcon>DC</DCIcon>
147
+ <BalanceAmount $disabled={false}>{dcBalance}</BalanceAmount>
148
+ <BalanceCurrencyLabel>DC</BalanceCurrencyLabel>
149
+ </BalanceRow>
150
+ )}
151
+ <SmallCTAButton
152
+ icon={<Coins width={18} height={18} />}
153
+ label="Withdraw Gold"
146
154
  disabled={availableGold === 0}
147
- onPointerDown={() => availableGold > 0 && onMoneyWithdraw()}
148
- >
149
- Withdraw
150
- </Button>
151
- </AvailableGold>
155
+ onClick={() => availableGold > 0 && onMoneyWithdraw()}
156
+ fullWidth
157
+ />
158
+ </WealthWrapper>
152
159
  </InnerOptionsWrapper>
153
160
  </OptionsWrapper>
154
161
 
155
162
  <ItemComponentScrollWrapper id="MarketContainer" ref={itemsContainer}>
156
- {items?.map(({ item, price, _id }, index) => (
163
+ {items?.map(({ item, price, _id }: IMarketplaceItem, index: number) => (
157
164
  <MarketplaceRows
158
165
  key={`${item.key}_${index}`}
159
166
  atlasIMG={atlasIMG}
@@ -175,81 +182,154 @@ const Flex = styled.div`
175
182
  align-items: center;
176
183
  `;
177
184
 
178
- const InputWrapper = styled.div`
185
+ const OptionsWrapper = styled.div`
179
186
  width: 95%;
180
- display: flex !important;
181
- justify-content: flex-start;
182
- align-items: center;
183
- margin: auto;
187
+ margin: 0 auto;
188
+ display: grid;
189
+ grid-template-columns: 2fr 1fr;
190
+ gap: 15px;
191
+ align-items: stretch;
192
+ `;
193
+
194
+ const InnerOptionsWrapper = styled.div`
195
+ background: rgba(0, 0, 0, 0.15);
196
+ border-radius: 4px;
197
+ border: 1px solid rgba(255, 255, 255, 0.05);
198
+ padding: 12px 15px;
199
+ display: flex;
200
+ flex-direction: column;
201
+ `;
202
+
203
+ const SectionLabel = styled.p`
204
+ margin: 0 0 12px 0;
205
+ font-size: 0.65rem;
206
+ color: #aaa;
207
+ text-transform: uppercase;
208
+ letter-spacing: 1px;
209
+ `;
210
+
211
+
212
+ const SellDescription = styled.p`
213
+ margin: 0;
214
+ font-size: 0.5rem;
215
+ color: #888;
216
+ text-align: right;
217
+ line-height: 1.4;
218
+ `;
219
+
220
+ const PriceInputWrapper = styled.div`
221
+ display: flex;
222
+ flex-direction: column;
223
+ flex-grow: 1;
184
224
 
185
225
  p {
186
- width: auto;
187
- margin-right: 20px;
226
+ margin: 0 0 8px 0;
227
+ font-size: 0.6rem;
228
+ color: #888;
229
+ text-transform: uppercase;
230
+ letter-spacing: 0.5px;
188
231
  }
189
232
 
190
- input {
191
- width: 68%;
192
- height: 10px;
233
+ .price-input {
234
+ width: 60px;
235
+ height: 32px;
236
+ flex-grow: 1;
237
+ font-size: 0.7rem;
238
+ }
239
+
240
+ .offer-btn {
241
+ padding: 10px 14px;
242
+ height: 32px;
193
243
  }
194
244
  `;
195
245
 
196
- const OptionsWrapper = styled.div`
197
- width: 100%;
198
- height: 100px;
246
+ const GoldIcon = styled.div`
247
+ position: relative;
199
248
  display: flex;
200
249
  align-items: center;
201
- justify-content: space-around;
250
+ justify-content: center;
251
+ width: 20px;
252
+ height: 20px;
253
+ margin-right: 4px;
254
+ flex-shrink: 0;
255
+ top: -0.5rem;
256
+ left: -0.5rem;
202
257
  `;
203
258
 
204
- const InnerOptionsWrapper = styled.div`
259
+ const WealthWrapper = styled.div`
205
260
  display: flex;
206
- justify-content: space-between;
207
261
  flex-direction: column;
208
- height: 100%;
209
- `;
262
+ gap: 8px;
263
+ flex-grow: 1;
210
264
 
211
- const ItemComponentScrollWrapper = styled.div`
212
- overflow-y: scroll;
213
- height: 390px;
214
- width: 100%;
215
- margin-top: 1rem;
216
-
217
- @media (max-width: 950px) {
218
- height: 250px;
265
+ & > :last-child {
266
+ margin-top: auto;
219
267
  }
220
268
  `;
221
269
 
222
- const PriceInputWrapper = styled.div`
223
- p {
224
- margin: 0;
225
- }
270
+ const BalanceRow = styled.div`
271
+ display: flex;
272
+ align-items: center;
273
+ justify-content: flex-end;
274
+ gap: 10px;
275
+ background: rgba(0, 0, 0, 0.3);
276
+ border-radius: 4px;
277
+ border: 1px solid rgba(255, 255, 255, 0.05);
278
+ padding: 8px 12px;
279
+ `;
226
280
 
227
- input {
228
- width: 200px;
229
- }
281
+ const BalanceAmount = styled.span<{ $disabled: boolean }>`
282
+ font-family: 'Press Start 2P', cursive;
283
+ font-size: 0.8rem;
284
+ color: ${props => props.$disabled ? uiColors.lightGray : '#fef08a'};
285
+ text-shadow: 0 2px 4px rgba(0,0,0,0.5);
286
+ margin-left: auto;
230
287
  `;
231
288
 
232
- const SellDescription = styled.p`
233
- margin: 0;
234
- font-size: ${uiFonts.size.xsmall} !important;
289
+ const BalanceCurrencyLabel = styled.span`
290
+ font-size: 0.5rem;
291
+ color: #888;
292
+ text-transform: uppercase;
293
+ letter-spacing: 0.5px;
294
+ min-width: 45px;
295
+ text-align: right;
235
296
  `;
236
297
 
237
- const AvailableGold = styled.div<{ $disabled: boolean }>`
238
- height: 100%;
239
- display: flex;
240
- flex-direction: column;
241
- justify-content: space-between;
298
+ const DCIcon = styled.div`
299
+ font-family: 'Press Start 2P', cursive;
300
+ font-size: 0.5rem;
301
+ color: #a78bfa;
302
+ background: rgba(167, 139, 250, 0.15);
303
+ border: 1px solid rgba(167, 139, 250, 0.3);
304
+ border-radius: 3px;
305
+ padding: 3px 5px;
306
+ line-height: 1;
307
+ margin-right: 4px;
308
+ `;
242
309
 
243
- p {
244
- margin: 0;
245
- color: ${props =>
246
- props.$disabled ? uiColors.lightGray : 'white'} !important;
310
+ const ItemComponentScrollWrapper = styled.div`
311
+ overflow-y: scroll;
312
+ height: 335px;
313
+ width: 95%;
314
+ margin: 1rem auto 0 auto;
315
+ background: rgba(0, 0, 0, 0.2);
316
+ border: 1px solid rgba(255, 255, 255, 0.05);
317
+ border-radius: 4px;
318
+
319
+ @media (max-width: 950px) {
320
+ height: 195px;
247
321
  }
322
+ `;
248
323
 
249
- .center {
250
- text-align: center;
251
- font-size: ${uiFonts.size.large} !important;
252
- color: ${props =>
253
- props.$disabled ? uiColors.lightGray : uiColors.lightGreen} !important;
324
+ const SmallCTAButton = styled(CTAButton)`
325
+ padding: 8px 12px;
326
+ height: 32px;
327
+
328
+ span {
329
+ font-size: 0.65rem;
330
+ }
331
+
332
+ svg {
333
+ font-size: 1.1rem;
254
334
  }
255
335
  `;
@@ -1,13 +1,17 @@
1
1
  import { IEquipmentSet, IItem, IMarketplaceItem } from '@rpg-engine/shared';
2
+ import { Settings2 } from 'pixelarticons/react/Settings2';
3
+ import { Store } from 'pixelarticons/react/Store';
4
+ import { User } from 'pixelarticons/react/User';
2
5
  import React, { useState } from 'react';
3
6
  import styled from 'styled-components';
4
- import { Button, ButtonTypes } from '../Button';
5
7
  import { DraggableContainer } from '../DraggableContainer';
6
8
  import { Pager } from '../Pager';
7
9
  import { RPGUIContainerTypes } from '../RPGUI/RPGUIContainer';
10
+ import { Tabs } from '../shared/Tabs';
8
11
  import { MarketplacePaymentMethod } from './MarketplaceBuyModal';
9
12
  import { BuyPanel } from './BuyPanel';
10
13
  import { ManagmentPanel } from './ManagmentPanel';
14
+ import { MarketplaceAcceptedCurrency, MarketplaceSettingsPanel } from './MarketplaceSettingsPanel';
11
15
 
12
16
  export interface IMarketPlaceProps {
13
17
  items: IMarketplaceItem[];
@@ -44,12 +48,28 @@ export interface IMarketPlaceProps {
44
48
  onPageChange: (page: number) => void;
45
49
  dcBalance?: number;
46
50
  dcToGoldSwapRate?: number;
51
+ acceptedCurrency?: MarketplaceAcceptedCurrency;
52
+ onAcceptedCurrencyChange?: (value: MarketplaceAcceptedCurrency) => void;
47
53
  }
48
54
 
55
+ type ActiveTab = 'marketplace' | 'your-panel' | 'settings';
56
+
49
57
  export const Marketplace: React.FC<IMarketPlaceProps> = props => {
50
- const { onClose, scale, onYourPanelToggle } = props;
58
+ const { onClose, scale, onYourPanelToggle, acceptedCurrency: acceptedCurrencyProp, onAcceptedCurrencyChange } = props;
59
+
60
+ const [activeTab, setActiveTab] = useState<ActiveTab>('marketplace');
61
+ const [acceptedCurrency, setAcceptedCurrency] = useState<MarketplaceAcceptedCurrency>(acceptedCurrencyProp ?? 'gold_or_dc');
62
+
63
+ const handleCurrencyChange = (value: MarketplaceAcceptedCurrency) => {
64
+ setAcceptedCurrency(value);
65
+ onAcceptedCurrencyChange?.(value);
66
+ };
51
67
 
52
- const [isYourPanel, setIsYourPanel] = useState(false);
68
+ const handleTabChange = (tabId: string) => {
69
+ const tab = tabId as ActiveTab;
70
+ setActiveTab(tab);
71
+ onYourPanelToggle(tab === 'your-panel');
72
+ };
53
73
 
54
74
  return (
55
75
  <DraggableContainer
@@ -61,49 +81,65 @@ export const Marketplace: React.FC<IMarketPlaceProps> = props => {
61
81
  cancelDrag="#MarketContainer, .rpgui-dropdown-imp, input, .empty-slot, button"
62
82
  scale={scale}
63
83
  >
64
- {isYourPanel && (
84
+ <Tabs
85
+ options={[
86
+ {
87
+ id: 'marketplace',
88
+ label: 'Marketplace',
89
+ icon: <Store width={18} height={18} />,
90
+ },
91
+ {
92
+ id: 'your-panel',
93
+ label: 'Your Panel',
94
+ icon: <User width={18} height={18} />,
95
+ },
96
+ {
97
+ id: 'settings',
98
+ label: 'Settings',
99
+ icon: <Settings2 width={18} height={18} />,
100
+ },
101
+ ]}
102
+ activeTabId={activeTab}
103
+ onTabChange={handleTabChange}
104
+ />
105
+
106
+ {activeTab === 'your-panel' && (
65
107
  <>
66
108
  <ManagmentPanel {...props} />
67
109
 
68
110
  <PagerContainer>
69
- <Button
70
- buttonType={ButtonTypes.RPGUIButton}
71
- onPointerDown={() => {
72
- onYourPanelToggle(false);
73
- setIsYourPanel(false);
74
- }}
75
- >
76
- Go to marketplace
77
- </Button>
78
111
  <Pager {...props} />
79
112
  </PagerContainer>
80
113
  </>
81
114
  )}
82
- {!isYourPanel && (
115
+ {activeTab === 'marketplace' && (
83
116
  <>
84
117
  <BuyPanel {...props} />
85
118
 
86
119
  <PagerContainer>
87
- <Button
88
- buttonType={ButtonTypes.RPGUIButton}
89
- onPointerDown={() => {
90
- onYourPanelToggle(true);
91
- setIsYourPanel(true);
92
- }}
93
- >
94
- Go to your panel
95
- </Button>
96
120
  <Pager {...props} />
97
121
  </PagerContainer>
98
122
  </>
99
123
  )}
124
+ {activeTab === 'settings' && (
125
+ <MarketplaceSettingsPanel
126
+ acceptedCurrency={acceptedCurrency}
127
+ onAcceptedCurrencyChange={handleCurrencyChange}
128
+ />
129
+ )}
100
130
  </DraggableContainer>
101
131
  );
102
132
  };
103
133
 
104
134
  const PagerContainer = styled.div`
105
135
  display: flex;
106
- justify-content: space-between;
136
+ justify-content: center;
107
137
  align-items: center;
108
- width: calc(100% - 30px);
138
+ width: 95%;
139
+ margin: 15px auto 0 auto;
140
+ padding: 12px 16px;
141
+ background: rgba(0, 0, 0, 0.3);
142
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
143
+ border-radius: 6px;
144
+ box-sizing: border-box;
109
145
  `;