@rpg-engine/long-bow 0.8.182 → 0.8.184

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.
Files changed (29) hide show
  1. package/dist/components/Marketplace/CharacterListingForm.d.ts +4 -2
  2. package/dist/components/Marketplace/CharacterListingModal.d.ts +17 -0
  3. package/dist/components/Marketplace/CharacterMarketplacePanel.d.ts +4 -0
  4. package/dist/components/Marketplace/CharacterMarketplaceRows.d.ts +6 -0
  5. package/dist/components/Marketplace/Marketplace.d.ts +3 -2
  6. package/dist/components/Marketplace/MyCharacterListingsPanel.d.ts +4 -0
  7. package/dist/components/shared/DCRateStrip.d.ts +2 -0
  8. package/dist/components/shared/RadioOption.d.ts +22 -0
  9. package/dist/long-bow.cjs.development.js +476 -371
  10. package/dist/long-bow.cjs.development.js.map +1 -1
  11. package/dist/long-bow.cjs.production.min.js +1 -1
  12. package/dist/long-bow.cjs.production.min.js.map +1 -1
  13. package/dist/long-bow.esm.js +477 -372
  14. package/dist/long-bow.esm.js.map +1 -1
  15. package/dist/stories/Features/marketplace/CharacterListingModal.stories.d.ts +8 -0
  16. package/dist/stories/shared/RadioOption.stories.d.ts +8 -0
  17. package/package.json +1 -1
  18. package/src/components/DCWallet/DCWalletContent.tsx +5 -47
  19. package/src/components/Marketplace/CharacterListingForm.tsx +56 -351
  20. package/src/components/Marketplace/CharacterListingModal.tsx +404 -0
  21. package/src/components/Marketplace/CharacterMarketplacePanel.tsx +112 -67
  22. package/src/components/Marketplace/CharacterMarketplaceRows.tsx +21 -9
  23. package/src/components/Marketplace/Marketplace.tsx +11 -6
  24. package/src/components/Marketplace/MyCharacterListingsPanel.tsx +15 -14
  25. package/src/components/shared/DCRateStrip.tsx +67 -0
  26. package/src/components/shared/RadioOption.tsx +93 -0
  27. package/src/stories/Features/marketplace/CharacterListingModal.stories.tsx +131 -0
  28. package/src/stories/Features/marketplace/CharacterMarketplace.stories.tsx +39 -23
  29. package/src/stories/shared/RadioOption.stories.tsx +93 -0
@@ -0,0 +1,8 @@
1
+ import { Meta, Story } from '@storybook/react';
2
+ import { ICharacterListingModalProps } from '../../../components/Marketplace/CharacterListingModal';
3
+ declare const meta: Meta<ICharacterListingModalProps>;
4
+ export default meta;
5
+ export declare const WithMultipleCharacters: Story;
6
+ export declare const WithAlreadyListedFiltered: Story;
7
+ export declare const NoEligibleCharacters: Story;
8
+ export declare const SingleCharacter: Story;
@@ -0,0 +1,8 @@
1
+ import { Meta, Story } from '@storybook/react';
2
+ import { IRadioOptionProps } from '../../components/shared/RadioOption';
3
+ declare const meta: Meta;
4
+ export default meta;
5
+ export declare const Unselected: Story<IRadioOptionProps>;
6
+ export declare const Selected: Story<IRadioOptionProps>;
7
+ export declare const Disabled: Story<IRadioOptionProps>;
8
+ export declare const InteractiveGroup: Story;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rpg-engine/long-bow",
3
- "version": "0.8.182",
3
+ "version": "0.8.184",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "typings": "dist/index.d.ts",
@@ -2,6 +2,7 @@ import React from 'react';
2
2
  import { FaShoppingCart } from 'react-icons/fa';
3
3
  import styled from 'styled-components';
4
4
  import { InternalTabs } from '../InternalTabs/InternalTabs';
5
+ import { DCRateStrip } from '../shared/DCRateStrip';
5
6
  import { DCHistoryPanel, IDCTransaction } from './DCHistoryPanel';
6
7
  import { DCTransferPanel, IDCTransferCharacterResult } from './DCTransferPanel';
7
8
 
@@ -22,7 +23,8 @@ export interface IDCWalletContentProps {
22
23
  searchResults?: IDCTransferCharacterResult[];
23
24
  }
24
25
 
25
- const DC_TO_GOLD = 5500;
26
+ import { DC_TO_GOLD_SWAP_RATE } from '@rpg-engine/shared';
27
+
26
28
  const DC_TO_USD = 100;
27
29
 
28
30
  export const DCWalletContent: React.FC<IDCWalletContentProps> = ({
@@ -42,7 +44,7 @@ export const DCWalletContent: React.FC<IDCWalletContentProps> = ({
42
44
  searchResults,
43
45
  }) => {
44
46
  const usdValue = (dcBalance / DC_TO_USD).toFixed(2);
45
- const goldValue = (dcBalance * DC_TO_GOLD).toLocaleString();
47
+ const goldValue = (dcBalance * DC_TO_GOLD_SWAP_RATE).toLocaleString();
46
48
 
47
49
  const tabs = [
48
50
  {
@@ -94,13 +96,7 @@ export const DCWalletContent: React.FC<IDCWalletContentProps> = ({
94
96
  </BuyButton>
95
97
  )}
96
98
  </BalanceTop>
97
- <RateStrip>
98
- <RateItem><RateNum>1 DC</RateNum><RateSep>=</RateSep><RateVal>5,500 Gold</RateVal></RateItem>
99
- <RateDivider />
100
- <RateItem><RateNum>100 DC</RateNum><RateSep>=</RateSep><RateVal>1 USD</RateVal></RateItem>
101
- <RateDivider />
102
- <RateItem><RateNum>1 USD</RateNum><RateSep>=</RateSep><RateVal>550K Gold</RateVal></RateItem>
103
- </RateStrip>
99
+ <DCRateStrip />
104
100
  </BalanceHeader>
105
101
 
106
102
  <TabsWrapper>
@@ -207,44 +203,6 @@ const BuyButtonLabel = styled.span`
207
203
  text-shadow: none !important;
208
204
  `;
209
205
 
210
- const RateStrip = styled.div`
211
- display: flex;
212
- align-items: center;
213
- border-top: 1px solid rgba(245, 158, 11, 0.1);
214
- padding-top: 8px;
215
- `;
216
-
217
- const RateItem = styled.div`
218
- display: flex;
219
- align-items: center;
220
- gap: 5px;
221
- flex: 1;
222
- justify-content: center;
223
- `;
224
-
225
- const RateDivider = styled.div`
226
- width: 1px;
227
- height: 12px;
228
- background: rgba(245, 158, 11, 0.2);
229
- `;
230
-
231
- const RateNum = styled.span`
232
- font-family: 'Press Start 2P', cursive !important;
233
- font-size: 6px !important;
234
- color: rgba(255, 255, 255, 0.5) !important;
235
- `;
236
-
237
- const RateSep = styled.span`
238
- font-family: 'Press Start 2P', cursive !important;
239
- font-size: 6px !important;
240
- color: rgba(255, 255, 255, 0.2) !important;
241
- `;
242
-
243
- const RateVal = styled.span`
244
- font-family: 'Press Start 2P', cursive !important;
245
- font-size: 6px !important;
246
- color: rgba(254, 240, 138, 0.6) !important;
247
- `;
248
206
 
249
207
  const TabsWrapper = styled.div`
250
208
  padding: 0 2.5%;
@@ -2,396 +2,101 @@ import { ICharacter } from '@rpg-engine/shared';
2
2
  import { ShoppingBag } from 'pixelarticons/react/ShoppingBag';
3
3
  import React, { useState } from 'react';
4
4
  import styled from 'styled-components';
5
- import { ConfirmModal } from '../ConfirmModal';
6
- import { Input } from '../Input';
7
- import { SpriteFromAtlas } from '../shared/SpriteFromAtlas';
8
5
  import { CTAButton } from '../shared/CTAButton/CTAButton';
6
+ import { CharacterListingModal } from './CharacterListingModal';
9
7
 
10
8
  export interface ICharacterListingFormProps {
11
9
  accountCharacters: ICharacter[];
12
- selectedCharacterToList: ICharacter | null;
13
- onCharacterSelectToList: (character: ICharacter) => void;
14
10
  onCharacterList: (characterId: string, price: number) => void;
11
+ /** Items atlas — for UI sprites like the DC coin */
15
12
  atlasJSON: any;
16
13
  atlasIMG: any;
14
+ /** Entities atlas — for character sprites */
15
+ characterAtlasJSON: any;
16
+ characterAtlasIMG: any;
17
17
  enableHotkeys?: () => void;
18
18
  disableHotkeys?: () => void;
19
19
  }
20
20
 
21
21
  export const CharacterListingForm: React.FC<ICharacterListingFormProps> = ({
22
22
  accountCharacters,
23
- selectedCharacterToList,
24
- onCharacterSelectToList,
25
23
  onCharacterList,
26
24
  atlasJSON,
27
25
  atlasIMG,
26
+ characterAtlasJSON,
27
+ characterAtlasIMG,
28
28
  enableHotkeys,
29
29
  disableHotkeys,
30
30
  }) => {
31
- const [price, setPrice] = useState('');
32
- const [isCreatingOffer, setIsCreatingOffer] = useState(false);
31
+ const [isModalOpen, setIsModalOpen] = useState(false);
33
32
 
34
- const eligibleCharacters = accountCharacters.filter(
35
- char => !char.isListedForSale && !char.tradedAt
36
- );
37
-
38
- const handleListClick = () => {
39
- if (selectedCharacterToList && price && Number(price) > 0) {
40
- setIsCreatingOffer(true);
41
- }
42
- };
43
-
44
- const handleListConfirm = () => {
45
- if (selectedCharacterToList && price && Number(price) > 0) {
46
- onCharacterList(selectedCharacterToList._id!, Number(price));
47
- setPrice('');
48
- onCharacterSelectToList({} as ICharacter); // Clear selection
49
- setIsCreatingOffer(false);
50
- enableHotkeys?.();
51
- }
52
- };
53
-
54
- const getCharacterLevel = (character: ICharacter): number => {
55
- return character.skills?.level || 1;
56
- };
33
+ const eligibleCount = accountCharacters.filter(
34
+ c => !c.isListedForSale && !c.tradedAt
35
+ ).length;
57
36
 
58
37
  return (
59
- <>
60
- {isCreatingOffer && (
61
- <ConfirmModal
62
- onClose={() => {
63
- setIsCreatingOffer(false);
64
- enableHotkeys?.();
65
- }}
66
- onConfirm={handleListConfirm}
67
- message="Are you sure you want to list this character for sale?"
68
- />
38
+ <Wrapper>
39
+ <CharacterListingModal
40
+ isOpen={isModalOpen}
41
+ onClose={() => setIsModalOpen(false)}
42
+ accountCharacters={accountCharacters}
43
+ atlasJSON={atlasJSON}
44
+ atlasIMG={atlasIMG}
45
+ characterAtlasJSON={characterAtlasJSON}
46
+ characterAtlasIMG={characterAtlasIMG}
47
+ onCharacterList={onCharacterList}
48
+ enableHotkeys={enableHotkeys}
49
+ disableHotkeys={disableHotkeys}
50
+ />
51
+
52
+ <Description>
53
+ List one of your offline characters on the marketplace. Prices are set in DC.
54
+ </Description>
55
+
56
+ <OpenButton
57
+ icon={<ShoppingBag width={20} height={20} />}
58
+ label={`List a Character${eligibleCount > 0 ? ` (${eligibleCount} eligible)` : ''}`}
59
+ disabled={eligibleCount === 0}
60
+ onClick={() => setIsModalOpen(true)}
61
+ />
62
+
63
+ {eligibleCount === 0 && (
64
+ <NoEligible>No eligible characters to list.</NoEligible>
69
65
  )}
70
-
71
- <FormContainer>
72
- <SectionHeader>
73
- <SectionLabel>List Character for Sale</SectionLabel>
74
- <SectionDescription>
75
- Select an offline character to list on the marketplace.
76
- </SectionDescription>
77
- </SectionHeader>
78
-
79
- <CharacterSelectionRow>
80
- <CharacterSlotWrapper>
81
- {selectedCharacterToList ? (
82
- <CharacterSpriteLarge>
83
- <SpriteFromAtlas
84
- atlasIMG={atlasIMG}
85
- atlasJSON={atlasJSON}
86
- spriteKey={`${selectedCharacterToList.textureKey}/down/standing/0.png`}
87
- imgScale={3}
88
- height={64}
89
- width={64}
90
- />
91
- </CharacterSpriteLarge>
92
- ) : (
93
- <EmptyCharacterSlot>
94
- <EmptySlotIcon>?</EmptySlotIcon>
95
- </EmptyCharacterSlot>
96
- )}
97
- </CharacterSlotWrapper>
98
-
99
- <CharacterDetails>
100
- {selectedCharacterToList ? (
101
- <>
102
- <CharacterName>{selectedCharacterToList.name || 'Unknown'}</CharacterName>
103
- <CharacterMeta>Level {getCharacterLevel(selectedCharacterToList)}</CharacterMeta>
104
- </>
105
- ) : (
106
- <EmptySelection>Select a character</EmptySelection>
107
- )}
108
- </CharacterDetails>
109
- </CharacterSelectionRow>
110
-
111
- <PriceInputWrapper>
112
- <PriceLabel>Set Price (Gold)</PriceLabel>
113
- <PriceInputRow>
114
- <Input
115
- onChange={e => setPrice(e.target.value)}
116
- value={price}
117
- placeholder="Amount..."
118
- type="number"
119
- disabled={!selectedCharacterToList}
120
- onBlur={enableHotkeys}
121
- onFocus={disableHotkeys}
122
- className="price-input"
123
- />
124
- <ListButton
125
- icon={<ShoppingBag width={18} height={18} />}
126
- label="List"
127
- disabled={!selectedCharacterToList || !price || Number(price) <= 0}
128
- onClick={handleListClick}
129
- />
130
- </PriceInputRow>
131
- </PriceInputWrapper>
132
-
133
- {eligibleCharacters.length > 0 && (
134
- <AvailableCharactersSection>
135
- <AvailableCharactersLabel>Available Characters</AvailableCharactersLabel>
136
- <AvailableCharactersList>
137
- {eligibleCharacters.map((character) => (
138
- <AvailableCharacterItem
139
- key={character._id}
140
- $selected={selectedCharacterToList?._id === character._id}
141
- onClick={() => onCharacterSelectToList(character)}
142
- type="button"
143
- >
144
- <MiniSprite>
145
- <SpriteFromAtlas
146
- atlasIMG={atlasIMG}
147
- atlasJSON={atlasJSON}
148
- spriteKey={`${character.textureKey}/down/standing/0.png`}
149
- imgScale={2}
150
- height={32}
151
- width={32}
152
- />
153
- </MiniSprite>
154
- <MiniCharacterInfo>
155
- <MiniCharacterName>{character.name}</MiniCharacterName>
156
- <MiniCharacterMeta>Lvl {getCharacterLevel(character)}</MiniCharacterMeta>
157
- </MiniCharacterInfo>
158
- </AvailableCharacterItem>
159
- ))}
160
- </AvailableCharactersList>
161
- </AvailableCharactersSection>
162
- )}
163
-
164
- {eligibleCharacters.length === 0 && (
165
- <EmptyState>
166
- <EmptyText>No eligible characters to list.</EmptyText>
167
- </EmptyState>
168
- )}
169
- </FormContainer>
170
- </>
66
+ </Wrapper>
171
67
  );
172
68
  };
173
69
 
174
- const FormContainer = styled.div`
175
- width: 95%;
176
- margin: 0 auto;
177
- background: rgba(0, 0, 0, 0.15);
178
- border-radius: 4px;
179
- border: 1px solid rgba(255, 255, 255, 0.05);
180
- padding: 16px;
181
- display: flex;
182
- flex-direction: column;
183
- gap: 16px;
184
- `;
185
-
186
- const SectionHeader = styled.div`
70
+ const Wrapper = styled.div`
187
71
  display: flex;
188
72
  flex-direction: column;
189
- gap: 4px;
190
- `;
191
-
192
- const SectionLabel = styled.p`
193
- margin: 0;
194
- font-size: 0.65rem;
195
- color: #aaa;
196
- text-transform: uppercase;
197
- letter-spacing: 1px;
198
- `;
199
-
200
- const SectionDescription = styled.p`
201
- margin: 0;
202
- font-size: 0.5rem;
203
- color: #666;
204
- line-height: 1.4;
205
- `;
206
-
207
- const CharacterSelectionRow = styled.div`
208
- display: flex;
209
- gap: 12px;
210
- align-items: center;
211
- padding: 12px;
212
- background: rgba(0, 0, 0, 0.2);
213
- border-radius: 4px;
214
- border: 1px solid rgba(255, 255, 255, 0.05);
215
- `;
216
-
217
- const CharacterSlotWrapper = styled.div`
218
- flex-shrink: 0;
219
- `;
220
-
221
- const CharacterSpriteLarge = styled.div`
222
- display: flex;
223
73
  align-items: center;
224
74
  justify-content: center;
225
- image-rendering: pixelated;
226
- width: 64px;
227
- height: 64px;
228
- background: rgba(255, 255, 255, 0.03);
229
- border: 1px solid rgba(255, 255, 255, 0.08);
230
- border-radius: 8px;
231
- `;
232
-
233
- const EmptyCharacterSlot = styled.div`
234
- width: 64px;
235
- height: 64px;
236
- background: rgba(0, 0, 0, 0.3);
237
- border: 2px dashed rgba(255, 255, 255, 0.1);
238
- border-radius: 8px;
239
- display: flex;
240
- align-items: center;
241
- justify-content: center;
242
- `;
243
-
244
- const EmptySlotIcon = styled.span`
245
- font-size: 1.5rem;
246
- color: #666;
247
- font-family: 'Press Start 2P', cursive;
248
- `;
249
-
250
- const CharacterDetails = styled.div`
251
- display: flex;
252
- flex-direction: column;
253
- gap: 4px;
254
- flex: 1;
255
- `;
256
-
257
- const CharacterName = styled.span`
258
- font-family: 'Press Start 2P', cursive;
259
- font-size: 0.6rem;
260
- color: #f3f4f6;
261
- `;
262
-
263
- const CharacterMeta = styled.span`
264
- font-size: 0.5rem;
265
- color: #888;
266
- text-transform: uppercase;
267
- letter-spacing: 0.5px;
75
+ gap: 20px;
76
+ padding: 40px 24px;
77
+ width: 95%;
78
+ margin: 0 auto;
268
79
  `;
269
80
 
270
- const EmptySelection = styled.span`
81
+ const Description = styled.p`
82
+ margin: 0;
271
83
  font-size: 0.5rem;
272
84
  color: #666;
273
- font-style: italic;
274
- `;
275
-
276
- const PriceInputWrapper = styled.div`
277
- display: flex;
278
- flex-direction: column;
279
- gap: 8px;
280
- `;
281
-
282
- const PriceLabel = styled.p`
283
- margin: 0;
284
- font-size: 0.55rem;
285
- color: #888;
286
- text-transform: uppercase;
287
- letter-spacing: 0.5px;
288
- `;
289
-
290
- const PriceInputRow = styled.div`
291
- display: flex;
292
- gap: 8px;
293
- align-items: center;
294
-
295
- .price-input {
296
- flex: 1;
297
- height: 12px;
298
- }
299
- `;
300
-
301
- const ListButton = styled(CTAButton)`
302
- padding: 10px 16px;
303
- height: 32px;
304
- flex-shrink: 0;
305
-
306
- span {
307
- font-size: 0.6rem;
308
- }
309
-
310
- svg {
311
- font-size: 1.1rem;
312
- }
313
- `;
314
-
315
- const AvailableCharactersSection = styled.div`
316
- display: flex;
317
- flex-direction: column;
318
- gap: 8px;
319
- `;
320
-
321
- const AvailableCharactersLabel = styled.p`
322
- margin: 0;
323
- font-size: 0.55rem;
324
- color: #888;
325
- text-transform: uppercase;
326
- letter-spacing: 0.5px;
327
- `;
328
-
329
- const AvailableCharactersList = styled.div`
330
- display: flex;
331
- flex-direction: column;
332
- gap: 6px;
333
- max-height: 200px;
334
- overflow-y: auto;
335
- padding: 4px;
336
- `;
337
-
338
- const AvailableCharacterItem = styled.button<{ $selected: boolean }>`
339
- display: flex;
340
- align-items: center;
341
- gap: 10px;
342
- padding: 8px;
343
- background: ${({ $selected }) => $selected ? 'rgba(245, 158, 11, 0.1)' : 'rgba(255, 255, 255, 0.02)'};
344
- border: 1px solid ${({ $selected }) => $selected ? 'rgba(245, 158, 11, 0.4)' : 'rgba(255, 255, 255, 0.08)'};
345
- border-radius: 6px;
346
- cursor: pointer;
347
- transition: border-color 0.15s, background 0.15s;
348
-
349
- &:hover {
350
- border-color: rgba(245, 158, 11, 0.3);
351
- background: rgba(245, 158, 11, 0.05);
352
- }
85
+ text-align: center;
86
+ line-height: 1.6;
87
+ max-width: 320px;
353
88
  `;
354
89
 
355
- const MiniSprite = styled.div`
356
- display: flex;
357
- align-items: center;
358
- justify-content: center;
359
- image-rendering: pixelated;
360
- flex-shrink: 0;
361
- `;
90
+ const OpenButton = styled(CTAButton)`
91
+ padding: 14px 24px;
362
92
 
363
- const MiniCharacterInfo = styled.div`
364
- display: flex;
365
- flex-direction: column;
366
- gap: 2px;
367
- align-items: flex-start;
93
+ span { font-size: 0.65rem; }
94
+ svg { font-size: 1.2rem; }
368
95
  `;
369
96
 
370
- const MiniCharacterName = styled.span`
371
- font-family: 'Press Start 2P', cursive;
97
+ const NoEligible = styled.span`
372
98
  font-size: 0.45rem;
373
- color: #f3f4f6;
374
- `;
375
-
376
- const MiniCharacterMeta = styled.span`
377
- font-size: 0.4rem;
378
- color: #888;
99
+ color: #52525b;
379
100
  text-transform: uppercase;
380
- letter-spacing: 0.5px;
381
- `;
382
-
383
- const EmptyState = styled.div`
384
- display: flex;
385
- align-items: center;
386
- justify-content: center;
387
- padding: 20px;
388
- background: rgba(255, 255, 255, 0.02);
389
- border-radius: 6px;
390
- `;
391
-
392
- const EmptyText = styled.span`
393
- font-size: 0.45rem;
394
- color: #666;
395
- text-transform: uppercase;
396
- letter-spacing: 0.5px;
101
+ letter-spacing: 1px;
397
102
  `;