@rpg-engine/long-bow 0.8.187 → 0.8.189
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/MyCharacterListingsPanel.d.ts +4 -1
- package/dist/long-bow.cjs.development.js +242 -224
- 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 +242 -224
- package/dist/long-bow.esm.js.map +1 -1
- package/dist/stories/Features/marketplace/CharacterMarketplace.stories.d.ts +1 -0
- package/package.json +1 -1
- package/src/components/Marketplace/CharacterDetailModal.tsx +32 -24
- package/src/components/Marketplace/CharacterListingModal.tsx +1 -0
- package/src/components/Marketplace/CharacterMarketplacePanel.tsx +2 -0
- package/src/components/Marketplace/Marketplace.tsx +2 -0
- package/src/components/Marketplace/MyCharacterListingsPanel.tsx +142 -112
- package/src/stories/Features/marketplace/CharacterMarketplace.stories.tsx +45 -1
|
@@ -7,4 +7,5 @@ export declare const CharacterBrowseEmpty: Story;
|
|
|
7
7
|
export declare const CharacterBrowseFiltered: Story;
|
|
8
8
|
export declare const MyCharacterListings: Story;
|
|
9
9
|
export declare const MyCharacterListingsEmpty: Story;
|
|
10
|
+
export declare const MyCharacterListingsNoEligible: Story;
|
|
10
11
|
export declare const CharacterListingPending: Story;
|
package/package.json
CHANGED
|
@@ -117,6 +117,7 @@ export const CharacterDetailModal: React.FC<ICharacterDetailModalProps> = ({
|
|
|
117
117
|
imgScale={4}
|
|
118
118
|
height={96}
|
|
119
119
|
width={96}
|
|
120
|
+
centered
|
|
120
121
|
/>
|
|
121
122
|
</SpriteContainer>
|
|
122
123
|
<HeroInfo>
|
|
@@ -135,23 +136,22 @@ export const CharacterDetailModal: React.FC<ICharacterDetailModalProps> = ({
|
|
|
135
136
|
|
|
136
137
|
<Divider />
|
|
137
138
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
<
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
<
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
<Divider />
|
|
139
|
+
<MetaColumns>
|
|
140
|
+
{topSkills.length > 0 && (
|
|
141
|
+
<Section>
|
|
142
|
+
<SectionTitle>Skills</SectionTitle>
|
|
143
|
+
<SkillsList>
|
|
144
|
+
{topSkills.map(([name, value]) => (
|
|
145
|
+
<SkillRow key={name}>
|
|
146
|
+
<SkillName>{name}</SkillName>
|
|
147
|
+
<SkillValue>{value}</SkillValue>
|
|
148
|
+
</SkillRow>
|
|
149
|
+
))}
|
|
150
|
+
</SkillsList>
|
|
151
|
+
</Section>
|
|
152
|
+
)}
|
|
153
|
+
|
|
154
|
+
{snap.equipment?.length > 0 && (
|
|
155
155
|
<Section>
|
|
156
156
|
<SectionTitle>Equipment</SectionTitle>
|
|
157
157
|
<EquipmentList>
|
|
@@ -164,8 +164,8 @@ export const CharacterDetailModal: React.FC<ICharacterDetailModalProps> = ({
|
|
|
164
164
|
))}
|
|
165
165
|
</EquipmentList>
|
|
166
166
|
</Section>
|
|
167
|
-
|
|
168
|
-
|
|
167
|
+
)}
|
|
168
|
+
</MetaColumns>
|
|
169
169
|
|
|
170
170
|
<Divider />
|
|
171
171
|
|
|
@@ -179,6 +179,7 @@ export const CharacterDetailModal: React.FC<ICharacterDetailModalProps> = ({
|
|
|
179
179
|
atlasJSON={atlasJSON}
|
|
180
180
|
spriteKey="others/definya-coin.png"
|
|
181
181
|
imgScale={1}
|
|
182
|
+
centered
|
|
182
183
|
/>
|
|
183
184
|
</DCCoinWrapper>
|
|
184
185
|
<PriceAmount>{formatDCAmount(listing.price)} DC</PriceAmount>
|
|
@@ -227,8 +228,8 @@ const ModalContent = styled.div`
|
|
|
227
228
|
border: 2px solid #f59e0b;
|
|
228
229
|
border-radius: 8px;
|
|
229
230
|
padding: 20px 24px;
|
|
230
|
-
width:
|
|
231
|
-
max-width:
|
|
231
|
+
width: 580px;
|
|
232
|
+
max-width: 96%;
|
|
232
233
|
max-height: 85dvh;
|
|
233
234
|
display: flex;
|
|
234
235
|
flex-direction: column;
|
|
@@ -348,10 +349,17 @@ const SectionTitle = styled.span`
|
|
|
348
349
|
letter-spacing: 1px;
|
|
349
350
|
`;
|
|
350
351
|
|
|
351
|
-
const
|
|
352
|
+
const MetaColumns = styled.div`
|
|
352
353
|
display: grid;
|
|
353
|
-
grid-template-columns:
|
|
354
|
-
gap:
|
|
354
|
+
grid-template-columns: 1fr 1fr;
|
|
355
|
+
gap: 16px;
|
|
356
|
+
align-items: start;
|
|
357
|
+
`;
|
|
358
|
+
|
|
359
|
+
const SkillsList = styled.div`
|
|
360
|
+
display: flex;
|
|
361
|
+
flex-direction: column;
|
|
362
|
+
gap: 4px;
|
|
355
363
|
`;
|
|
356
364
|
|
|
357
365
|
const SkillRow = styled.div`
|
|
@@ -173,6 +173,7 @@ export const CharacterListingModal: React.FC<ICharacterListingModalProps> = ({
|
|
|
173
173
|
atlasJSON={atlasJSON}
|
|
174
174
|
spriteKey="others/definya-coin.png"
|
|
175
175
|
imgScale={1}
|
|
176
|
+
centered
|
|
176
177
|
/>
|
|
177
178
|
</DCCoinWrapper>
|
|
178
179
|
<PricePreviewAmount>{Number(price).toLocaleString()} DC</PricePreviewAmount>
|
|
@@ -81,6 +81,7 @@ export const CharacterMarketplacePanel: React.FC<ICharacterMarketplacePanelProps
|
|
|
81
81
|
imgScale={3}
|
|
82
82
|
height={64}
|
|
83
83
|
width={64}
|
|
84
|
+
centered
|
|
84
85
|
/>
|
|
85
86
|
);
|
|
86
87
|
};
|
|
@@ -147,6 +148,7 @@ export const CharacterMarketplacePanel: React.FC<ICharacterMarketplacePanelProps
|
|
|
147
148
|
atlasJSON={atlasJSON}
|
|
148
149
|
spriteKey="others/definya-coin.png"
|
|
149
150
|
imgScale={1}
|
|
151
|
+
centered
|
|
150
152
|
/>
|
|
151
153
|
</DCCoinWrapper>
|
|
152
154
|
{formatDCAmount(listing.price)} DC
|
|
@@ -342,6 +342,8 @@ export const Marketplace: React.FC<IMarketPlaceProps> = props => {
|
|
|
342
342
|
itemsPerPage={10}
|
|
343
343
|
onPageChange={onMyCharacterListingsPageChange ?? (() => {})}
|
|
344
344
|
onCharacterDelist={onCharacterDelist ?? (() => {})}
|
|
345
|
+
accountCharacters={accountCharacters ?? []}
|
|
346
|
+
onCharacterList={onCharacterList ?? (() => {})}
|
|
345
347
|
atlasJSON={props.atlasJSON}
|
|
346
348
|
atlasIMG={props.atlasIMG}
|
|
347
349
|
characterAtlasJSON={characterAtlasJSON ?? props.atlasJSON}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import { formatDCAmount, ICharacterListing } from '@rpg-engine/shared';
|
|
1
|
+
import { formatDCAmount, ICharacter, ICharacterListing } from '@rpg-engine/shared';
|
|
2
2
|
import { Delete } from 'pixelarticons/react/Delete';
|
|
3
|
+
import { ShoppingBag } from 'pixelarticons/react/ShoppingBag';
|
|
3
4
|
import { User } from 'pixelarticons/react/User';
|
|
4
5
|
import React, { useEffect, useRef, useState } from 'react';
|
|
5
6
|
import styled from 'styled-components';
|
|
6
7
|
import { ConfirmModal } from '../ConfirmModal';
|
|
8
|
+
import { Pagination } from '../shared/Pagination/Pagination';
|
|
7
9
|
import { SpriteFromAtlas } from '../shared/SpriteFromAtlas';
|
|
8
10
|
import { CTAButton } from '../shared/CTAButton/CTAButton';
|
|
11
|
+
import { CharacterListingModal } from './CharacterListingModal';
|
|
9
12
|
|
|
10
13
|
export interface IMyCharacterListingsPanelProps {
|
|
11
14
|
myCharacterListings: ICharacterListing[];
|
|
@@ -14,6 +17,9 @@ export interface IMyCharacterListingsPanelProps {
|
|
|
14
17
|
itemsPerPage: number;
|
|
15
18
|
onPageChange: (page: number) => void;
|
|
16
19
|
onCharacterDelist: (listingId: string) => void;
|
|
20
|
+
/** Characters that can be listed (used for the List a Character modal) */
|
|
21
|
+
accountCharacters: ICharacter[];
|
|
22
|
+
onCharacterList: (characterId: string, price: number) => void;
|
|
17
23
|
/** Items atlas — for UI sprites like the DC coin */
|
|
18
24
|
atlasJSON: any;
|
|
19
25
|
atlasIMG: any;
|
|
@@ -31,60 +37,81 @@ export const MyCharacterListingsPanel: React.FC<IMyCharacterListingsPanelProps>
|
|
|
31
37
|
itemsPerPage,
|
|
32
38
|
onPageChange,
|
|
33
39
|
onCharacterDelist,
|
|
40
|
+
accountCharacters,
|
|
41
|
+
onCharacterList,
|
|
34
42
|
atlasJSON,
|
|
35
43
|
atlasIMG,
|
|
36
44
|
characterAtlasJSON,
|
|
37
45
|
characterAtlasIMG,
|
|
38
46
|
enableHotkeys,
|
|
47
|
+
disableHotkeys,
|
|
39
48
|
}) => {
|
|
40
|
-
const [
|
|
49
|
+
const [delistingId, setDelistingId] = useState<string | null>(null);
|
|
50
|
+
const [isListingModalOpen, setIsListingModalOpen] = useState(false);
|
|
41
51
|
const itemsContainer = useRef<HTMLDivElement>(null);
|
|
42
52
|
|
|
43
53
|
useEffect(() => {
|
|
44
54
|
itemsContainer.current?.scrollTo(0, 0);
|
|
45
55
|
}, [currentPage]);
|
|
46
56
|
|
|
47
|
-
const handleDelistClick = (listingId: string) => {
|
|
48
|
-
setDelistingListingId(listingId);
|
|
49
|
-
};
|
|
50
|
-
|
|
51
57
|
const handleDelistConfirm = () => {
|
|
52
|
-
if (
|
|
53
|
-
onCharacterDelist(
|
|
54
|
-
|
|
58
|
+
if (delistingId) {
|
|
59
|
+
onCharacterDelist(delistingId);
|
|
60
|
+
setDelistingId(null);
|
|
55
61
|
enableHotkeys?.();
|
|
56
62
|
}
|
|
57
63
|
};
|
|
58
64
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
};
|
|
65
|
+
const eligibleCount = accountCharacters.filter(
|
|
66
|
+
c => !c.isListedForSale && !c.tradedAt
|
|
67
|
+
).length;
|
|
63
68
|
|
|
64
69
|
return (
|
|
65
70
|
<>
|
|
66
|
-
{
|
|
71
|
+
{delistingId && (
|
|
67
72
|
<ConfirmModal
|
|
68
|
-
onClose={() => {
|
|
69
|
-
setDelistingListingId(null);
|
|
70
|
-
enableHotkeys?.();
|
|
71
|
-
}}
|
|
73
|
+
onClose={() => { setDelistingId(null); enableHotkeys?.(); }}
|
|
72
74
|
onConfirm={handleDelistConfirm}
|
|
73
75
|
message="Are you sure you want to delist this character?"
|
|
74
76
|
/>
|
|
75
77
|
)}
|
|
76
78
|
|
|
77
|
-
<
|
|
79
|
+
<CharacterListingModal
|
|
80
|
+
isOpen={isListingModalOpen}
|
|
81
|
+
onClose={() => setIsListingModalOpen(false)}
|
|
82
|
+
accountCharacters={accountCharacters}
|
|
83
|
+
atlasJSON={atlasJSON}
|
|
84
|
+
atlasIMG={atlasIMG}
|
|
85
|
+
characterAtlasJSON={characterAtlasJSON}
|
|
86
|
+
characterAtlasIMG={characterAtlasIMG}
|
|
87
|
+
onCharacterList={(id, price) => {
|
|
88
|
+
onCharacterList(id, price);
|
|
89
|
+
setIsListingModalOpen(false);
|
|
90
|
+
}}
|
|
91
|
+
enableHotkeys={enableHotkeys}
|
|
92
|
+
disableHotkeys={disableHotkeys}
|
|
93
|
+
/>
|
|
94
|
+
|
|
95
|
+
<Toolbar>
|
|
96
|
+
<ListCharacterBtn
|
|
97
|
+
icon={<ShoppingBag width={16} height={16} />}
|
|
98
|
+
label={`List a Character${eligibleCount > 0 ? ` (${eligibleCount} eligible)` : ''}`}
|
|
99
|
+
onClick={() => setIsListingModalOpen(true)}
|
|
100
|
+
disabled={eligibleCount === 0}
|
|
101
|
+
/>
|
|
102
|
+
</Toolbar>
|
|
103
|
+
|
|
104
|
+
<ListingsContainer ref={itemsContainer}>
|
|
78
105
|
{myCharacterListings.length === 0 ? (
|
|
79
106
|
<EmptyState>
|
|
80
107
|
<User width={32} height={32} />
|
|
81
|
-
<EmptyText>
|
|
82
|
-
<EmptySubtext>Use the "List Character" tab to
|
|
108
|
+
<EmptyText>No listed characters.</EmptyText>
|
|
109
|
+
<EmptySubtext>Use the "List Character" tab to put a character up for sale.</EmptySubtext>
|
|
83
110
|
</EmptyState>
|
|
84
111
|
) : (
|
|
85
112
|
<ListingsGrid>
|
|
86
113
|
{myCharacterListings.map((listing) => (
|
|
87
|
-
<CharacterListingCard key={listing._id}>
|
|
114
|
+
<CharacterListingCard key={listing._id} $isBeingBought={listing.isBeingBought}>
|
|
88
115
|
<CharacterSprite>
|
|
89
116
|
<SpriteFromAtlas
|
|
90
117
|
atlasIMG={characterAtlasIMG}
|
|
@@ -93,11 +120,14 @@ export const MyCharacterListingsPanel: React.FC<IMyCharacterListingsPanelProps>
|
|
|
93
120
|
imgScale={3}
|
|
94
121
|
height={64}
|
|
95
122
|
width={64}
|
|
123
|
+
centered
|
|
96
124
|
/>
|
|
97
125
|
</CharacterSprite>
|
|
98
126
|
<CharacterInfo>
|
|
99
127
|
<CharacterName>{listing.characterSnapshot.name || 'Unknown'}</CharacterName>
|
|
100
|
-
<CharacterMeta>
|
|
128
|
+
<CharacterMeta>
|
|
129
|
+
Lv.{listing.characterSnapshot.level} · {listing.characterSnapshot.class}
|
|
130
|
+
</CharacterMeta>
|
|
101
131
|
<ListingPrice>
|
|
102
132
|
<DCCoinWrapper>
|
|
103
133
|
<SpriteFromAtlas
|
|
@@ -105,22 +135,24 @@ export const MyCharacterListingsPanel: React.FC<IMyCharacterListingsPanelProps>
|
|
|
105
135
|
atlasJSON={atlasJSON}
|
|
106
136
|
spriteKey="others/definya-coin.png"
|
|
107
137
|
imgScale={1}
|
|
138
|
+
centered
|
|
108
139
|
/>
|
|
109
140
|
</DCCoinWrapper>
|
|
110
141
|
{formatDCAmount(listing.price)} DC
|
|
111
142
|
</ListingPrice>
|
|
112
|
-
<
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
)}
|
|
143
|
+
<ListingDate>
|
|
144
|
+
Listed {new Date(listing.createdAt).toLocaleDateString()}
|
|
145
|
+
</ListingDate>
|
|
116
146
|
</CharacterInfo>
|
|
117
|
-
<
|
|
118
|
-
icon={<Delete width={
|
|
147
|
+
<DelistBtn
|
|
148
|
+
icon={<Delete width={14} height={14} />}
|
|
119
149
|
label="Delist"
|
|
120
|
-
onClick={() =>
|
|
121
|
-
iconColor="#ef4444"
|
|
150
|
+
onClick={() => setDelistingId(listing._id)}
|
|
122
151
|
disabled={listing.isBeingBought}
|
|
123
152
|
/>
|
|
153
|
+
{listing.isBeingBought && (
|
|
154
|
+
<PendingBadge>Pending</PendingBadge>
|
|
155
|
+
)}
|
|
124
156
|
</CharacterListingCard>
|
|
125
157
|
))}
|
|
126
158
|
</ListingsGrid>
|
|
@@ -129,18 +161,11 @@ export const MyCharacterListingsPanel: React.FC<IMyCharacterListingsPanelProps>
|
|
|
129
161
|
|
|
130
162
|
{totalCount > itemsPerPage && (
|
|
131
163
|
<PagerFooter>
|
|
132
|
-
<Pagination
|
|
133
|
-
{
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
type="button"
|
|
138
|
-
onClick={() => onPageChange(page)}
|
|
139
|
-
>
|
|
140
|
-
{page}
|
|
141
|
-
</PageButton>
|
|
142
|
-
))}
|
|
143
|
-
</Pagination>
|
|
164
|
+
<Pagination
|
|
165
|
+
currentPage={currentPage}
|
|
166
|
+
totalPages={Math.ceil(totalCount / itemsPerPage)}
|
|
167
|
+
onPageChange={onPageChange}
|
|
168
|
+
/>
|
|
144
169
|
</PagerFooter>
|
|
145
170
|
)}
|
|
146
171
|
</>
|
|
@@ -169,15 +194,26 @@ const ListingsGrid = styled.div`
|
|
|
169
194
|
padding: 12px;
|
|
170
195
|
`;
|
|
171
196
|
|
|
172
|
-
const CharacterListingCard = styled.div
|
|
197
|
+
const CharacterListingCard = styled.div<{ $isBeingBought?: boolean }>`
|
|
173
198
|
display: flex;
|
|
174
199
|
flex-direction: column;
|
|
175
200
|
align-items: center;
|
|
176
201
|
gap: 8px;
|
|
177
202
|
background: rgba(255, 255, 255, 0.03);
|
|
178
|
-
border: 1px solid
|
|
203
|
+
border: 1px solid ${({ $isBeingBought }) =>
|
|
204
|
+
$isBeingBought ? 'rgba(239,68,68,0.3)' : 'rgba(255,255,255,0.08)'};
|
|
179
205
|
border-radius: 8px;
|
|
180
206
|
padding: 12px;
|
|
207
|
+
position: relative;
|
|
208
|
+
opacity: ${({ $isBeingBought }) => ($isBeingBought ? 0.7 : 1)};
|
|
209
|
+
transition: border-color 0.15s, background 0.15s;
|
|
210
|
+
|
|
211
|
+
&:hover {
|
|
212
|
+
border-color: ${({ $isBeingBought }) =>
|
|
213
|
+
$isBeingBought ? 'rgba(239,68,68,0.5)' : 'rgba(245,158,11,0.3)'};
|
|
214
|
+
background: ${({ $isBeingBought }) =>
|
|
215
|
+
$isBeingBought ? 'rgba(239,68,68,0.04)' : 'rgba(245,158,11,0.04)'};
|
|
216
|
+
}
|
|
181
217
|
`;
|
|
182
218
|
|
|
183
219
|
const CharacterSprite = styled.div`
|
|
@@ -185,6 +221,9 @@ const CharacterSprite = styled.div`
|
|
|
185
221
|
align-items: center;
|
|
186
222
|
justify-content: center;
|
|
187
223
|
image-rendering: pixelated;
|
|
224
|
+
width: 64px;
|
|
225
|
+
height: 64px;
|
|
226
|
+
flex-shrink: 0;
|
|
188
227
|
`;
|
|
189
228
|
|
|
190
229
|
const CharacterInfo = styled.div`
|
|
@@ -193,19 +232,21 @@ const CharacterInfo = styled.div`
|
|
|
193
232
|
align-items: center;
|
|
194
233
|
gap: 4px;
|
|
195
234
|
text-align: center;
|
|
235
|
+
width: 100%;
|
|
196
236
|
`;
|
|
197
237
|
|
|
198
238
|
const CharacterName = styled.span`
|
|
199
|
-
font-family: 'Press Start 2P', cursive;
|
|
200
|
-
font-size: 0.55rem;
|
|
201
|
-
color: #f3f4f6;
|
|
239
|
+
font-family: 'Press Start 2P', cursive !important;
|
|
240
|
+
font-size: 0.55rem !important;
|
|
241
|
+
color: #f3f4f6 !important;
|
|
202
242
|
text-transform: uppercase;
|
|
203
243
|
letter-spacing: 0.5px;
|
|
204
244
|
`;
|
|
205
245
|
|
|
206
246
|
const CharacterMeta = styled.span`
|
|
207
|
-
font-
|
|
208
|
-
|
|
247
|
+
font-family: 'Press Start 2P', cursive !important;
|
|
248
|
+
font-size: 0.45rem !important;
|
|
249
|
+
color: #888 !important;
|
|
209
250
|
text-transform: uppercase;
|
|
210
251
|
letter-spacing: 0.5px;
|
|
211
252
|
`;
|
|
@@ -213,10 +254,12 @@ const CharacterMeta = styled.span`
|
|
|
213
254
|
const ListingPrice = styled.div`
|
|
214
255
|
display: flex;
|
|
215
256
|
align-items: center;
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
font-
|
|
219
|
-
|
|
257
|
+
justify-content: center;
|
|
258
|
+
gap: 4px;
|
|
259
|
+
font-family: 'Press Start 2P', cursive !important;
|
|
260
|
+
font-size: 0.5rem !important;
|
|
261
|
+
color: #fef08a !important;
|
|
262
|
+
line-height: 1;
|
|
220
263
|
`;
|
|
221
264
|
|
|
222
265
|
const DCCoinWrapper = styled.span`
|
|
@@ -224,48 +267,55 @@ const DCCoinWrapper = styled.span`
|
|
|
224
267
|
align-items: center;
|
|
225
268
|
justify-content: center;
|
|
226
269
|
flex-shrink: 0;
|
|
270
|
+
line-height: 0;
|
|
227
271
|
`;
|
|
228
272
|
|
|
229
|
-
const
|
|
230
|
-
font-
|
|
231
|
-
|
|
273
|
+
const ListingDate = styled.span`
|
|
274
|
+
font-family: 'Press Start 2P', cursive !important;
|
|
275
|
+
font-size: 0.35rem !important;
|
|
276
|
+
color: #52525b !important;
|
|
232
277
|
text-transform: uppercase;
|
|
233
278
|
letter-spacing: 0.5px;
|
|
234
279
|
`;
|
|
235
280
|
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
padding:
|
|
281
|
+
const DelistBtn = styled(CTAButton)`
|
|
282
|
+
width: 100%;
|
|
283
|
+
padding: 6px 10px;
|
|
284
|
+
height: 28px;
|
|
285
|
+
margin-top: 2px;
|
|
286
|
+
|
|
287
|
+
span { font-size: 0.48rem; }
|
|
288
|
+
svg { font-size: 0.9rem; }
|
|
289
|
+
`;
|
|
290
|
+
|
|
291
|
+
const PendingBadge = styled.span`
|
|
292
|
+
position: absolute;
|
|
293
|
+
top: 6px;
|
|
294
|
+
right: 6px;
|
|
295
|
+
background: rgba(239, 68, 68, 0.2);
|
|
296
|
+
border: 1px solid rgba(239, 68, 68, 0.4);
|
|
239
297
|
border-radius: 4px;
|
|
298
|
+
padding: 2px 6px;
|
|
299
|
+
font-family: 'Press Start 2P', cursive !important;
|
|
300
|
+
font-size: 0.35rem !important;
|
|
301
|
+
color: #ef4444 !important;
|
|
240
302
|
text-transform: uppercase;
|
|
241
303
|
letter-spacing: 0.5px;
|
|
242
|
-
${({ $status }) =>
|
|
243
|
-
$status === 'pending'
|
|
244
|
-
? `
|
|
245
|
-
background: rgba(239, 68, 68, 0.2);
|
|
246
|
-
border: 1px solid rgba(239, 68, 68, 0.4);
|
|
247
|
-
color: #ef4444;
|
|
248
|
-
`
|
|
249
|
-
: `
|
|
250
|
-
background: rgba(34, 197, 94, 0.2);
|
|
251
|
-
border: 1px solid rgba(34, 197, 94, 0.4);
|
|
252
|
-
color: #22c55e;
|
|
253
|
-
`}
|
|
254
304
|
`;
|
|
255
305
|
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
306
|
+
const Toolbar = styled.div`
|
|
307
|
+
width: 95%;
|
|
308
|
+
margin: 10px auto 0 auto;
|
|
309
|
+
display: flex;
|
|
310
|
+
justify-content: flex-end;
|
|
311
|
+
`;
|
|
261
312
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
313
|
+
const ListCharacterBtn = styled(CTAButton)`
|
|
314
|
+
padding: 8px 16px;
|
|
315
|
+
height: 32px;
|
|
265
316
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
}
|
|
317
|
+
span { font-size: 0.5rem; }
|
|
318
|
+
svg { font-size: 1rem; }
|
|
269
319
|
`;
|
|
270
320
|
|
|
271
321
|
const PagerFooter = styled.div`
|
|
@@ -278,28 +328,6 @@ const PagerFooter = styled.div`
|
|
|
278
328
|
margin: 0 auto;
|
|
279
329
|
`;
|
|
280
330
|
|
|
281
|
-
const Pagination = styled.div`
|
|
282
|
-
display: flex;
|
|
283
|
-
gap: 6px;
|
|
284
|
-
`;
|
|
285
|
-
|
|
286
|
-
const PageButton = styled.button<{ $active: boolean }>`
|
|
287
|
-
padding: 6px 10px;
|
|
288
|
-
font-family: 'Press Start 2P', cursive;
|
|
289
|
-
font-size: 0.5rem;
|
|
290
|
-
border-radius: 4px;
|
|
291
|
-
border: 1px solid ${({ $active }) => ($active ? '#f59e0b' : 'rgba(255,255,255,0.12)')};
|
|
292
|
-
background: ${({ $active }) => ($active ? 'rgba(245,158,11,0.15)' : 'rgba(0,0,0,0.3)')};
|
|
293
|
-
color: ${({ $active }) => ($active ? '#f59e0b' : '#777')};
|
|
294
|
-
cursor: pointer;
|
|
295
|
-
transition: border-color 0.15s, background 0.15s, color 0.15s;
|
|
296
|
-
|
|
297
|
-
&:hover {
|
|
298
|
-
border-color: ${({ $active }) => ($active ? '#f59e0b' : 'rgba(255,255,255,0.3)')};
|
|
299
|
-
color: ${({ $active }) => ($active ? '#f59e0b' : '#bbb')};
|
|
300
|
-
}
|
|
301
|
-
`;
|
|
302
|
-
|
|
303
331
|
const EmptyState = styled.div`
|
|
304
332
|
display: flex;
|
|
305
333
|
flex-direction: column;
|
|
@@ -311,17 +339,19 @@ const EmptyState = styled.div`
|
|
|
311
339
|
`;
|
|
312
340
|
|
|
313
341
|
const EmptyText = styled.span`
|
|
314
|
-
font-
|
|
315
|
-
|
|
342
|
+
font-family: 'Press Start 2P', cursive !important;
|
|
343
|
+
font-size: 0.48rem !important;
|
|
344
|
+
color: #71717a !important;
|
|
316
345
|
text-transform: uppercase;
|
|
317
346
|
letter-spacing: 1px;
|
|
318
347
|
`;
|
|
319
348
|
|
|
320
349
|
const EmptySubtext = styled.span`
|
|
321
|
-
font-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
letter-spacing: 0.5px;
|
|
350
|
+
font-family: 'Press Start 2P', cursive !important;
|
|
351
|
+
font-size: 0.38rem !important;
|
|
352
|
+
color: #52525b !important;
|
|
325
353
|
text-align: center;
|
|
326
|
-
max-width:
|
|
354
|
+
max-width: 260px;
|
|
355
|
+
line-height: 1.6;
|
|
327
356
|
`;
|
|
357
|
+
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ICharacterListing } from '@rpg-engine/shared';
|
|
1
|
+
import { ICharacter, ICharacterListing } from '@rpg-engine/shared';
|
|
2
2
|
import { Meta, Story } from '@storybook/react';
|
|
3
3
|
import React from 'react';
|
|
4
4
|
import { RPGUIRoot } from '../../..';
|
|
@@ -189,6 +189,25 @@ const mockMyCharacterListings: ICharacterListing[] = [
|
|
|
189
189
|
},
|
|
190
190
|
];
|
|
191
191
|
|
|
192
|
+
const mockAccountCharacters: ICharacter[] = [
|
|
193
|
+
{
|
|
194
|
+
_id: 'char-a1',
|
|
195
|
+
name: 'MyRogue',
|
|
196
|
+
textureKey: 'redhair-girl-1',
|
|
197
|
+
isListedForSale: false,
|
|
198
|
+
tradedAt: undefined,
|
|
199
|
+
skills: { level: 22 } as any,
|
|
200
|
+
} as ICharacter,
|
|
201
|
+
{
|
|
202
|
+
_id: 'char-a2',
|
|
203
|
+
name: 'MyPaladin',
|
|
204
|
+
textureKey: 'dragon-knight',
|
|
205
|
+
isListedForSale: false,
|
|
206
|
+
tradedAt: undefined,
|
|
207
|
+
skills: { level: 38 } as any,
|
|
208
|
+
} as ICharacter,
|
|
209
|
+
];
|
|
210
|
+
|
|
192
211
|
export const CharacterBrowse: Story = () => (
|
|
193
212
|
<RPGUIRoot>
|
|
194
213
|
<CharacterMarketplacePanel
|
|
@@ -288,6 +307,8 @@ export const MyCharacterListings: Story = () => (
|
|
|
288
307
|
itemsPerPage={8}
|
|
289
308
|
onPageChange={() => {}}
|
|
290
309
|
onCharacterDelist={() => {}}
|
|
310
|
+
accountCharacters={mockAccountCharacters}
|
|
311
|
+
onCharacterList={() => {}}
|
|
291
312
|
atlasJSON={itemsAtlasJSON}
|
|
292
313
|
atlasIMG={itemsAtlasIMG}
|
|
293
314
|
characterAtlasJSON={entitiesAtlasJSON}
|
|
@@ -307,6 +328,8 @@ export const MyCharacterListingsEmpty: Story = () => (
|
|
|
307
328
|
itemsPerPage={8}
|
|
308
329
|
onPageChange={() => {}}
|
|
309
330
|
onCharacterDelist={() => {}}
|
|
331
|
+
accountCharacters={mockAccountCharacters}
|
|
332
|
+
onCharacterList={() => {}}
|
|
310
333
|
atlasJSON={itemsAtlasJSON}
|
|
311
334
|
atlasIMG={itemsAtlasIMG}
|
|
312
335
|
characterAtlasJSON={entitiesAtlasJSON}
|
|
@@ -317,6 +340,27 @@ export const MyCharacterListingsEmpty: Story = () => (
|
|
|
317
340
|
|
|
318
341
|
MyCharacterListingsEmpty.storyName = 'My Listings - Empty State';
|
|
319
342
|
|
|
343
|
+
export const MyCharacterListingsNoEligible: Story = () => (
|
|
344
|
+
<RPGUIRoot>
|
|
345
|
+
<MyCharacterListingsPanel
|
|
346
|
+
myCharacterListings={mockMyCharacterListings}
|
|
347
|
+
totalCount={2}
|
|
348
|
+
currentPage={1}
|
|
349
|
+
itemsPerPage={8}
|
|
350
|
+
onPageChange={() => {}}
|
|
351
|
+
onCharacterDelist={() => {}}
|
|
352
|
+
accountCharacters={[]}
|
|
353
|
+
onCharacterList={() => {}}
|
|
354
|
+
atlasJSON={itemsAtlasJSON}
|
|
355
|
+
atlasIMG={itemsAtlasIMG}
|
|
356
|
+
characterAtlasJSON={entitiesAtlasJSON}
|
|
357
|
+
characterAtlasIMG={entitiesAtlasIMG}
|
|
358
|
+
/>
|
|
359
|
+
</RPGUIRoot>
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
MyCharacterListingsNoEligible.storyName = 'My Listings - No eligible chars to list';
|
|
363
|
+
|
|
320
364
|
export const CharacterListingPending: Story = () => (
|
|
321
365
|
<RPGUIRoot>
|
|
322
366
|
<CharacterMarketplacePanel
|