@rpg-engine/long-bow 0.8.136 → 0.8.138
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/DCWallet/DCTransferPanel.d.ts +6 -0
- package/dist/components/DCWallet/DCWalletModal.d.ts +4 -0
- package/dist/components/Marketplace/BuyPanel.d.ts +4 -1
- package/dist/components/Marketplace/Marketplace.d.ts +4 -1
- package/dist/components/Marketplace/MarketplaceBuyModal.d.ts +10 -0
- package/dist/components/Marketplace/MarketplaceRows.d.ts +1 -0
- package/dist/components/Store/sections/StorePacksSection.d.ts +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/long-bow.cjs.development.js +427 -140
- 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 +428 -142
- package/dist/long-bow.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/components/DCWallet/DCHistoryPanel.tsx +130 -68
- package/src/components/DCWallet/DCTransferPanel.tsx +102 -10
- package/src/components/DCWallet/DCWalletModal.tsx +46 -7
- package/src/components/Marketplace/BuyPanel.tsx +29 -2
- package/src/components/Marketplace/Marketplace.tsx +4 -1
- package/src/components/Marketplace/MarketplaceBuyModal.tsx +230 -0
- package/src/components/Marketplace/MarketplaceRows.tsx +41 -16
- package/src/components/Store/Store.tsx +5 -1
- package/src/components/Store/sections/StorePacksSection.tsx +17 -1
- package/src/index.tsx +1 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import React, { useCallback, useState } from 'react';
|
|
2
|
+
import { FaTimes } from 'react-icons/fa';
|
|
3
|
+
import styled from 'styled-components';
|
|
4
|
+
import ModalPortal from '../Abstractions/ModalPortal';
|
|
5
|
+
import { Button, ButtonTypes } from '../Button';
|
|
6
|
+
|
|
7
|
+
export type MarketplacePaymentMethod = 'gold' | 'dc';
|
|
8
|
+
|
|
9
|
+
export interface IMarketplaceBuyModalProps {
|
|
10
|
+
goldPrice: number;
|
|
11
|
+
dcEquivalentPrice: number;
|
|
12
|
+
dcBalance: number;
|
|
13
|
+
onConfirm: (paymentMethod: MarketplacePaymentMethod) => void;
|
|
14
|
+
onClose: () => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const MarketplaceBuyModal: React.FC<IMarketplaceBuyModalProps> = ({
|
|
18
|
+
goldPrice,
|
|
19
|
+
dcEquivalentPrice,
|
|
20
|
+
dcBalance,
|
|
21
|
+
onConfirm,
|
|
22
|
+
onClose,
|
|
23
|
+
}) => {
|
|
24
|
+
const [selected, setSelected] = useState<MarketplacePaymentMethod>('gold');
|
|
25
|
+
const hasSufficientDC = dcBalance >= dcEquivalentPrice;
|
|
26
|
+
|
|
27
|
+
const stopPropagation = useCallback(
|
|
28
|
+
(e: React.MouseEvent | React.TouchEvent | React.PointerEvent) => {
|
|
29
|
+
e.stopPropagation();
|
|
30
|
+
},
|
|
31
|
+
[]
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const handleConfirm = useCallback(() => {
|
|
35
|
+
onConfirm(selected);
|
|
36
|
+
}, [selected, onConfirm]);
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<ModalPortal>
|
|
40
|
+
<Overlay onPointerDown={onClose} />
|
|
41
|
+
<ModalContainer>
|
|
42
|
+
<ModalContent
|
|
43
|
+
onClick={stopPropagation as React.MouseEventHandler}
|
|
44
|
+
onTouchStart={stopPropagation as React.TouchEventHandler}
|
|
45
|
+
onPointerDown={stopPropagation as React.PointerEventHandler}
|
|
46
|
+
>
|
|
47
|
+
<Header>
|
|
48
|
+
<Title>Confirm Purchase</Title>
|
|
49
|
+
<CloseButton onPointerDown={onClose} aria-label="Close">
|
|
50
|
+
<FaTimes />
|
|
51
|
+
</CloseButton>
|
|
52
|
+
</Header>
|
|
53
|
+
|
|
54
|
+
<Options>
|
|
55
|
+
<RadioOption
|
|
56
|
+
$selected={selected === 'gold'}
|
|
57
|
+
onPointerDown={() => setSelected('gold')}
|
|
58
|
+
>
|
|
59
|
+
<RadioCircle $selected={selected === 'gold'} />
|
|
60
|
+
<OptionText>
|
|
61
|
+
<OptionLabel>Gold</OptionLabel>
|
|
62
|
+
<OptionSub>{goldPrice.toLocaleString()} gold</OptionSub>
|
|
63
|
+
</OptionText>
|
|
64
|
+
</RadioOption>
|
|
65
|
+
|
|
66
|
+
<RadioOption
|
|
67
|
+
$selected={selected === 'dc'}
|
|
68
|
+
$disabled={!hasSufficientDC}
|
|
69
|
+
onPointerDown={() => hasSufficientDC && setSelected('dc')}
|
|
70
|
+
>
|
|
71
|
+
<RadioCircle $selected={selected === 'dc'} />
|
|
72
|
+
<OptionText>
|
|
73
|
+
<OptionLabel $disabled={!hasSufficientDC}>Definya Coin</OptionLabel>
|
|
74
|
+
<OptionSub>
|
|
75
|
+
{dcEquivalentPrice.toLocaleString()} DC
|
|
76
|
+
{' '}
|
|
77
|
+
<BalanceHint $insufficient={!hasSufficientDC}>
|
|
78
|
+
({dcBalance.toLocaleString()} available)
|
|
79
|
+
</BalanceHint>
|
|
80
|
+
</OptionSub>
|
|
81
|
+
</OptionText>
|
|
82
|
+
</RadioOption>
|
|
83
|
+
</Options>
|
|
84
|
+
|
|
85
|
+
<ConfirmRow>
|
|
86
|
+
<Button buttonType={ButtonTypes.RPGUIButton} onPointerDown={handleConfirm}>
|
|
87
|
+
Confirm
|
|
88
|
+
</Button>
|
|
89
|
+
</ConfirmRow>
|
|
90
|
+
</ModalContent>
|
|
91
|
+
</ModalContainer>
|
|
92
|
+
</ModalPortal>
|
|
93
|
+
);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const Overlay = styled.div`
|
|
97
|
+
position: fixed;
|
|
98
|
+
inset: 0;
|
|
99
|
+
background: rgba(0, 0, 0, 0.65);
|
|
100
|
+
z-index: 1000;
|
|
101
|
+
`;
|
|
102
|
+
|
|
103
|
+
const ModalContainer = styled.div`
|
|
104
|
+
position: fixed;
|
|
105
|
+
inset: 0;
|
|
106
|
+
display: flex;
|
|
107
|
+
align-items: center;
|
|
108
|
+
justify-content: center;
|
|
109
|
+
z-index: 1001;
|
|
110
|
+
pointer-events: none;
|
|
111
|
+
`;
|
|
112
|
+
|
|
113
|
+
const ModalContent = styled.div`
|
|
114
|
+
background: #1a1a2e;
|
|
115
|
+
border: 2px solid #f59e0b;
|
|
116
|
+
border-radius: 8px;
|
|
117
|
+
padding: 20px 24px 24px;
|
|
118
|
+
min-width: 300px;
|
|
119
|
+
max-width: 90%;
|
|
120
|
+
display: flex;
|
|
121
|
+
flex-direction: column;
|
|
122
|
+
gap: 16px;
|
|
123
|
+
pointer-events: auto;
|
|
124
|
+
animation: scaleIn 0.15s ease-out;
|
|
125
|
+
|
|
126
|
+
@keyframes scaleIn {
|
|
127
|
+
from { transform: scale(0.85); opacity: 0; }
|
|
128
|
+
to { transform: scale(1); opacity: 1; }
|
|
129
|
+
}
|
|
130
|
+
`;
|
|
131
|
+
|
|
132
|
+
const Header = styled.div`
|
|
133
|
+
display: flex;
|
|
134
|
+
align-items: center;
|
|
135
|
+
justify-content: space-between;
|
|
136
|
+
`;
|
|
137
|
+
|
|
138
|
+
const Title = styled.h3`
|
|
139
|
+
margin: 0;
|
|
140
|
+
font-family: 'Press Start 2P', cursive;
|
|
141
|
+
font-size: 0.7rem;
|
|
142
|
+
color: #fef08a;
|
|
143
|
+
`;
|
|
144
|
+
|
|
145
|
+
const CloseButton = styled.button`
|
|
146
|
+
background: none;
|
|
147
|
+
border: none;
|
|
148
|
+
color: rgba(255, 255, 255, 0.6);
|
|
149
|
+
cursor: pointer;
|
|
150
|
+
font-size: 1rem;
|
|
151
|
+
padding: 4px;
|
|
152
|
+
display: flex;
|
|
153
|
+
align-items: center;
|
|
154
|
+
|
|
155
|
+
&:hover {
|
|
156
|
+
color: #ffffff;
|
|
157
|
+
}
|
|
158
|
+
`;
|
|
159
|
+
|
|
160
|
+
const Options = styled.div`
|
|
161
|
+
display: flex;
|
|
162
|
+
flex-direction: column;
|
|
163
|
+
gap: 8px;
|
|
164
|
+
`;
|
|
165
|
+
|
|
166
|
+
const RadioOption = styled.div<{ $selected: boolean; $disabled?: boolean }>`
|
|
167
|
+
display: flex;
|
|
168
|
+
align-items: center;
|
|
169
|
+
gap: 12px;
|
|
170
|
+
padding: 10px 12px;
|
|
171
|
+
border: 1px solid ${({ $selected }) => ($selected ? '#f59e0b' : 'rgba(255,255,255,0.15)')};
|
|
172
|
+
border-radius: 6px;
|
|
173
|
+
background: ${({ $selected }) => ($selected ? 'rgba(245,158,11,0.1)' : 'transparent')};
|
|
174
|
+
cursor: ${({ $disabled }) => ($disabled ? 'not-allowed' : 'pointer')};
|
|
175
|
+
opacity: ${({ $disabled }) => ($disabled ? 0.5 : 1)};
|
|
176
|
+
transition: border-color 0.15s, background 0.15s;
|
|
177
|
+
|
|
178
|
+
&:hover {
|
|
179
|
+
border-color: ${({ $disabled }) => ($disabled ? 'rgba(255,255,255,0.15)' : '#f59e0b')};
|
|
180
|
+
}
|
|
181
|
+
`;
|
|
182
|
+
|
|
183
|
+
const RadioCircle = styled.div<{ $selected: boolean }>`
|
|
184
|
+
width: 16px;
|
|
185
|
+
height: 16px;
|
|
186
|
+
border-radius: 50%;
|
|
187
|
+
border: 2px solid ${({ $selected }) => ($selected ? '#f59e0b' : 'rgba(255,255,255,0.4)')};
|
|
188
|
+
display: flex;
|
|
189
|
+
align-items: center;
|
|
190
|
+
justify-content: center;
|
|
191
|
+
flex-shrink: 0;
|
|
192
|
+
|
|
193
|
+
&::after {
|
|
194
|
+
content: '';
|
|
195
|
+
width: 8px;
|
|
196
|
+
height: 8px;
|
|
197
|
+
border-radius: 50%;
|
|
198
|
+
background: #f59e0b;
|
|
199
|
+
opacity: ${({ $selected }) => ($selected ? 1 : 0)};
|
|
200
|
+
transition: opacity 0.15s;
|
|
201
|
+
}
|
|
202
|
+
`;
|
|
203
|
+
|
|
204
|
+
const OptionText = styled.div`
|
|
205
|
+
display: flex;
|
|
206
|
+
flex-direction: column;
|
|
207
|
+
gap: 3px;
|
|
208
|
+
`;
|
|
209
|
+
|
|
210
|
+
const OptionLabel = styled.span<{ $disabled?: boolean }>`
|
|
211
|
+
font-family: 'Press Start 2P', cursive;
|
|
212
|
+
font-size: 0.65rem;
|
|
213
|
+
color: ${({ $disabled }) => ($disabled ? 'rgba(255,255,255,0.4)' : '#ffffff')};
|
|
214
|
+
`;
|
|
215
|
+
|
|
216
|
+
const OptionSub = styled.span`
|
|
217
|
+
font-family: 'Press Start 2P', cursive;
|
|
218
|
+
font-size: 0.5rem;
|
|
219
|
+
color: rgba(255, 255, 255, 0.55);
|
|
220
|
+
`;
|
|
221
|
+
|
|
222
|
+
const BalanceHint = styled.span<{ $insufficient: boolean }>`
|
|
223
|
+
color: ${({ $insufficient }) => ($insufficient ? '#ef4444' : 'rgba(255, 255, 255, 0.4)')};
|
|
224
|
+
`;
|
|
225
|
+
|
|
226
|
+
const ConfirmRow = styled.div`
|
|
227
|
+
display: flex;
|
|
228
|
+
justify-content: center;
|
|
229
|
+
margin-top: 4px;
|
|
230
|
+
`;
|
|
@@ -19,6 +19,7 @@ export interface IMarketPlaceRowsPropos {
|
|
|
19
19
|
atlasIMG: any;
|
|
20
20
|
item: IItem;
|
|
21
21
|
itemPrice: number;
|
|
22
|
+
dcEquivalentPrice?: number;
|
|
22
23
|
equipmentSet?: IEquipmentSet | null;
|
|
23
24
|
scale?: number;
|
|
24
25
|
onMarketPlaceItemBuy?: () => void;
|
|
@@ -31,6 +32,7 @@ export const MarketplaceRows: React.FC<IMarketPlaceRowsPropos> = ({
|
|
|
31
32
|
atlasIMG,
|
|
32
33
|
item,
|
|
33
34
|
itemPrice,
|
|
35
|
+
dcEquivalentPrice,
|
|
34
36
|
equipmentSet,
|
|
35
37
|
scale,
|
|
36
38
|
onMarketPlaceItemBuy,
|
|
@@ -88,23 +90,32 @@ export const MarketplaceRows: React.FC<IMarketPlaceRowsPropos> = ({
|
|
|
88
90
|
</ItemIconContainer>
|
|
89
91
|
|
|
90
92
|
<Flex>
|
|
91
|
-
<
|
|
92
|
-
<
|
|
93
|
-
<
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
<
|
|
102
|
-
<
|
|
103
|
-
|
|
93
|
+
<PriceContainer>
|
|
94
|
+
<ItemIconContainer>
|
|
95
|
+
<GoldContainer>
|
|
96
|
+
<SpriteFromAtlas
|
|
97
|
+
atlasIMG={atlasIMG}
|
|
98
|
+
atlasJSON={atlasJSON}
|
|
99
|
+
spriteKey="others/gold-coin-qty-5.png"
|
|
100
|
+
imgScale={2}
|
|
101
|
+
/>
|
|
102
|
+
</GoldContainer>
|
|
103
|
+
<PriceValue>
|
|
104
|
+
<p>
|
|
105
|
+
<Ellipsis maxLines={1} maxWidth="120px" fontSize="10px">
|
|
106
|
+
${itemPrice}
|
|
107
|
+
</Ellipsis>
|
|
108
|
+
</p>
|
|
109
|
+
</PriceValue>
|
|
110
|
+
</ItemIconContainer>
|
|
111
|
+
{dcEquivalentPrice !== undefined && (
|
|
112
|
+
<DCPriceLabel>
|
|
113
|
+
<Ellipsis maxLines={1} maxWidth="80px" fontSize="9px">
|
|
114
|
+
{dcEquivalentPrice} DC
|
|
104
115
|
</Ellipsis>
|
|
105
|
-
</
|
|
106
|
-
|
|
107
|
-
</
|
|
116
|
+
</DCPriceLabel>
|
|
117
|
+
)}
|
|
118
|
+
</PriceContainer>
|
|
108
119
|
<ButtonContainer>
|
|
109
120
|
<Button
|
|
110
121
|
buttonType={ButtonTypes.RPGUIButton}
|
|
@@ -176,6 +187,20 @@ const SpriteContainer = styled.div`
|
|
|
176
187
|
left: 0.5rem;
|
|
177
188
|
`;
|
|
178
189
|
|
|
190
|
+
const PriceContainer = styled.div`
|
|
191
|
+
display: flex;
|
|
192
|
+
flex-direction: column;
|
|
193
|
+
align-items: flex-start;
|
|
194
|
+
gap: 2px;
|
|
195
|
+
`;
|
|
196
|
+
|
|
197
|
+
const DCPriceLabel = styled.p`
|
|
198
|
+
margin: 0;
|
|
199
|
+
margin-left: 40px;
|
|
200
|
+
color: #fef08a;
|
|
201
|
+
font-size: 0.7rem;
|
|
202
|
+
`;
|
|
203
|
+
|
|
179
204
|
const PriceValue = styled.div`
|
|
180
205
|
margin-left: 40px;
|
|
181
206
|
`;
|
|
@@ -168,6 +168,8 @@ export const Store: React.FC<IStoreProps> = ({
|
|
|
168
168
|
packs={packs.filter(pack => pack.priceUSD >= 9.99)}
|
|
169
169
|
onAddToCart={handleAddPackToCart}
|
|
170
170
|
onSelectPack={setSelectedPack}
|
|
171
|
+
atlasJSON={atlasJSON}
|
|
172
|
+
atlasIMG={atlasIMG}
|
|
171
173
|
/>
|
|
172
174
|
),
|
|
173
175
|
},
|
|
@@ -176,9 +178,11 @@ export const Store: React.FC<IStoreProps> = ({
|
|
|
176
178
|
title: packsTabLabel,
|
|
177
179
|
content: customPacksContent ?? (
|
|
178
180
|
<StorePacksSection
|
|
179
|
-
packs={packs.filter(pack => pack.priceUSD < 9.99)}
|
|
181
|
+
packs={hidePremiumTab ? packs : packs.filter(pack => pack.priceUSD < 9.99)}
|
|
180
182
|
onAddToCart={handleAddPackToCart}
|
|
181
183
|
onSelectPack={setSelectedPack}
|
|
184
|
+
atlasJSON={atlasJSON}
|
|
185
|
+
atlasIMG={atlasIMG}
|
|
182
186
|
/>
|
|
183
187
|
),
|
|
184
188
|
},
|
|
@@ -3,6 +3,7 @@ import React, { useCallback } from 'react';
|
|
|
3
3
|
import { FaCartPlus } from 'react-icons/fa';
|
|
4
4
|
import styled from 'styled-components';
|
|
5
5
|
import { CTAButton } from '../../shared/CTAButton/CTAButton';
|
|
6
|
+
import { SpriteFromAtlas } from '../../shared/SpriteFromAtlas';
|
|
6
7
|
import { ScrollableContent } from '../../shared/ScrollableContent/ScrollableContent';
|
|
7
8
|
import { usePackFiltering } from '../../../hooks/usePackFiltering';
|
|
8
9
|
|
|
@@ -10,12 +11,16 @@ interface IStorePacksSectionProps {
|
|
|
10
11
|
packs: IItemPack[];
|
|
11
12
|
onAddToCart: (pack: IItemPack) => void;
|
|
12
13
|
onSelectPack?: (pack: IItemPack) => void;
|
|
14
|
+
atlasJSON?: any;
|
|
15
|
+
atlasIMG?: string;
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
export const StorePacksSection: React.FC<IStorePacksSectionProps> = ({
|
|
16
19
|
packs,
|
|
17
20
|
onAddToCart,
|
|
18
21
|
onSelectPack,
|
|
22
|
+
atlasJSON,
|
|
23
|
+
atlasIMG,
|
|
19
24
|
}) => {
|
|
20
25
|
const { searchQuery, setSearchQuery, filteredPacks } = usePackFiltering(packs);
|
|
21
26
|
|
|
@@ -24,11 +29,22 @@ export const StorePacksSection: React.FC<IStorePacksSectionProps> = ({
|
|
|
24
29
|
return imageUrl.default || imageUrl.src;
|
|
25
30
|
};
|
|
26
31
|
|
|
32
|
+
const renderPackIcon = useCallback(
|
|
33
|
+
(pack: IItemPack) => {
|
|
34
|
+
const imgSrc = getImageSrc(pack.image);
|
|
35
|
+
if (atlasJSON && atlasIMG && imgSrc && atlasJSON?.frames?.[imgSrc]) {
|
|
36
|
+
return <SpriteFromAtlas atlasJSON={atlasJSON} atlasIMG={atlasIMG} spriteKey={imgSrc} width={40} height={40} imgScale={1.2} centered />;
|
|
37
|
+
}
|
|
38
|
+
return <img src={imgSrc} alt={pack.title} />;
|
|
39
|
+
},
|
|
40
|
+
[atlasJSON, atlasIMG]
|
|
41
|
+
);
|
|
42
|
+
|
|
27
43
|
const renderPack = useCallback(
|
|
28
44
|
(pack: IItemPack) => (
|
|
29
45
|
<PackRow key={pack.key} onClick={() => onSelectPack?.(pack)}>
|
|
30
46
|
<PackIconContainer>
|
|
31
|
-
|
|
47
|
+
{renderPackIcon(pack)}
|
|
32
48
|
</PackIconContainer>
|
|
33
49
|
|
|
34
50
|
<PackDetails>
|
package/src/index.tsx
CHANGED
|
@@ -35,6 +35,7 @@ export * from './components/itemSelector/ItemSelector';
|
|
|
35
35
|
export * from './components/Leaderboard/Leaderboard';
|
|
36
36
|
export * from './components/ListMenu';
|
|
37
37
|
export * from './components/Marketplace/Marketplace';
|
|
38
|
+
export * from './components/Marketplace/MarketplaceBuyModal';
|
|
38
39
|
export * from './components/Marketplace/MarketplaceRows';
|
|
39
40
|
export * from './components/Multitab/TabBody';
|
|
40
41
|
export * from './components/Multitab/TabsContainer';
|