@rpg-engine/long-bow 0.8.160 → 0.8.162

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,11 +1,15 @@
1
1
  import { IItemPack, IPurchase, IProductBlueprint, ItemRarities, ItemSubType, ItemType, UserAccountTypes, PaymentCurrency, PurchaseType } from '@rpg-engine/shared';
2
+ import { Box } from 'pixelarticons/react/Box';
3
+ import { Crown } from 'pixelarticons/react/Crown';
4
+ import { Gift } from 'pixelarticons/react/Gift';
5
+ import { Wallet } from 'pixelarticons/react/Wallet';
2
6
  import React, { ReactNode, useMemo, useState } from 'react';
3
7
  import { FaHistory, FaShoppingCart, FaWallet } from 'react-icons/fa';
4
8
  import styled from 'styled-components';
5
9
  import { uiColors } from '../../constants/uiColors';
6
10
  import { DraggableContainer } from '../DraggableContainer';
7
- import { InternalTabs } from '../InternalTabs/InternalTabs';
8
11
  import { RPGUIContainerTypes } from '../RPGUI/RPGUIContainer';
12
+ import { Tabs } from '../shared/Tabs';
9
13
  import { CTAButton } from '../shared/CTAButton/CTAButton';
10
14
  import { CartView } from './CartView';
11
15
  import { useStoreCart } from './hooks/useStoreCart';
@@ -159,10 +163,11 @@ export const Store: React.FC<IStoreProps> = ({
159
163
  const tabIds: TabId[] = tabOrder ?? ['premium', 'packs', 'items'];
160
164
  const availableTabIds: TabId[] = tabIds.filter(id => !(hidePremiumTab && id === 'premium'));
161
165
 
162
- const tabsMap: Record<string, { id: TabId; title: string; content: ReactNode }> = {
166
+ const tabsMap: Record<string, { id: TabId; title: string; icon: ReactNode; content: ReactNode }> = {
163
167
  premium: {
164
168
  id: 'premium',
165
169
  title: 'Premium',
170
+ icon: <Crown width={18} height={18} />,
166
171
  content: (
167
172
  <StorePacksSection
168
173
  packs={packs.filter(pack => pack.priceUSD >= 9.99)}
@@ -176,6 +181,7 @@ export const Store: React.FC<IStoreProps> = ({
176
181
  packs: {
177
182
  id: 'packs',
178
183
  title: packsTabLabel,
184
+ icon: <Gift width={18} height={18} />,
179
185
  content: customPacksContent ?? (
180
186
  <StorePacksSection
181
187
  packs={hidePremiumTab ? packs : packs.filter(pack => pack.priceUSD < 9.99)}
@@ -189,6 +195,7 @@ export const Store: React.FC<IStoreProps> = ({
189
195
  items: {
190
196
  id: 'items',
191
197
  title: 'Items',
198
+ icon: <Box width={18} height={18} />,
192
199
  content: (
193
200
  <StoreItemsSection
194
201
  items={filteredItems.items}
@@ -203,12 +210,11 @@ export const Store: React.FC<IStoreProps> = ({
203
210
  wallet: {
204
211
  id: 'wallet',
205
212
  title: 'Wallet',
213
+ icon: <Wallet width={18} height={18} />,
206
214
  content: customWalletContent ?? null,
207
215
  },
208
216
  };
209
217
 
210
- const tabs = availableTabIds.map(id => tabsMap[id]);
211
-
212
218
  return (
213
219
  <DraggableContainer
214
220
  title="Store"
@@ -276,16 +282,14 @@ export const Store: React.FC<IStoreProps> = ({
276
282
  </CartButton>
277
283
  </TopBar>
278
284
  <MainContent>
279
- <InternalTabs
280
- tabs={tabs}
281
- activeTextColor="#000000"
282
- activeColor="#fef08a"
283
- inactiveColor="#6b7280"
284
- borderColor="#f59e0b"
285
- hoverColor="#fef3c7"
286
- activeTab={activeTab}
287
- onTabChange={(tabId: string) => setActiveTab(tabId as TabId)}
285
+ <Tabs
286
+ options={availableTabIds.map(id => ({ id, label: tabsMap[id].title, icon: tabsMap[id].icon }))}
287
+ activeTabId={activeTab}
288
+ onTabChange={(tabId) => setActiveTab(tabId as TabId)}
288
289
  />
290
+ <TabContent>
291
+ {tabsMap[activeTab]?.content}
292
+ </TabContent>
289
293
  </MainContent>
290
294
  {cartItems.length > 0 && (
291
295
  <Footer>
@@ -349,12 +353,12 @@ const MainContent = styled.div`
349
353
  flex-direction: column;
350
354
  min-height: 0;
351
355
  overflow: hidden;
356
+ `;
352
357
 
353
- .rpgui-tabs-content {
354
- flex: 1;
355
- overflow-y: auto;
356
- padding-right: 0.5rem;
357
- }
358
+ const TabContent = styled.div`
359
+ flex: 1;
360
+ overflow-y: auto;
361
+ padding-right: 0.5rem;
358
362
  `;
359
363
 
360
364
  const Footer = styled.div`
@@ -4,6 +4,7 @@ import { FaCartPlus } from 'react-icons/fa';
4
4
  import styled from 'styled-components';
5
5
  import { SelectArrow } from '../Arrow/SelectArrow';
6
6
  import { CTAButton } from '../shared/CTAButton/CTAButton';
7
+ import { ItemRowWrapper } from '../shared/ItemRowWrapper';
7
8
  import { SpriteFromAtlas } from '../shared/SpriteFromAtlas';
8
9
  import { useQuantityControl } from '../../hooks/useQuantityControl';
9
10
 
@@ -56,27 +57,29 @@ export const StoreItemRow: React.FC<IStoreItemRowProps> = ({
56
57
  };
57
58
 
58
59
  return (
59
- <ItemWrapper $isHighlighted={item.store?.isHighlighted || false}>
60
- <ItemIconContainer>
61
- <SpriteFromAtlas
62
- atlasJSON={atlasJSON}
63
- atlasIMG={atlasIMG}
64
- spriteKey={item.texturePath}
65
- width={32}
66
- height={32}
67
- imgScale={2}
68
- centered
69
- />
70
- </ItemIconContainer>
71
-
72
- <ItemDetails>
73
- <ItemName>{item.name}</ItemName>
74
- <ItemPrice>
75
- ${item.price}
76
- {(item as any).dcPrice ? ` · ${((item as any).dcPrice as number).toLocaleString()} DC` : ''}
77
- </ItemPrice>
78
- <ItemDescription>{item.description}</ItemDescription>
79
- </ItemDetails>
60
+ <ItemRowWrapper $isHighlighted={item.store?.isHighlighted || false}>
61
+ <LeftSection>
62
+ <ItemIconContainer>
63
+ <SpriteFromAtlas
64
+ atlasJSON={atlasJSON}
65
+ atlasIMG={atlasIMG}
66
+ spriteKey={item.texturePath}
67
+ width={32}
68
+ height={32}
69
+ imgScale={2}
70
+ centered
71
+ />
72
+ </ItemIconContainer>
73
+
74
+ <ItemDetails>
75
+ <ItemName>{item.name}</ItemName>
76
+ <ItemPrice>
77
+ ${item.price}
78
+ {(item as any).dcPrice ? ` · ${((item as any).dcPrice as number).toLocaleString()} DC` : ''}
79
+ </ItemPrice>
80
+ <ItemDescription>{item.description}</ItemDescription>
81
+ </ItemDetails>
82
+ </LeftSection>
80
83
 
81
84
  <Controls>
82
85
  {/* Show text input if configured, else show arrows only for stackable items */}
@@ -121,24 +124,16 @@ export const StoreItemRow: React.FC<IStoreItemRowProps> = ({
121
124
  disabled={!hasRequiredAccount}
122
125
  />
123
126
  </Controls>
124
- </ItemWrapper>
127
+ </ItemRowWrapper>
125
128
  );
126
129
  };
127
130
 
128
- const ItemWrapper = styled.div<{ $isHighlighted: boolean }>`
131
+ const LeftSection = styled.div`
129
132
  display: flex;
130
133
  align-items: center;
131
134
  gap: 0.75rem;
132
- padding: 0.5rem 1rem;
133
- border-bottom: 1px solid rgba(255, 255, 255, 0.1);
134
- background: ${props =>
135
- props.$isHighlighted ? 'rgba(255, 215, 0, 0.1)' : 'transparent'};
136
- border-left: ${props =>
137
- props.$isHighlighted ? '3px solid #ffd700' : '3px solid transparent'};
138
-
139
- &:last-child {
140
- border-bottom: none;
141
- }
135
+ flex: 1;
136
+ min-width: 0;
142
137
  `;
143
138
 
144
139
  const ItemIconContainer = styled.div`
@@ -9,7 +9,7 @@ import styled from 'styled-components';
9
9
  import { ScrollableContent } from '../../shared/ScrollableContent/ScrollableContent';
10
10
  import { StoreCharacterSkinRow } from '../StoreCharacterSkinRow';
11
11
  import { StoreItemRow } from '../StoreItemRow';
12
- import { Dropdown } from '../../Dropdown';
12
+ import { SegmentedToggle } from '../../shared/SegmentedToggle';
13
13
  import { SearchBar } from '../../shared/SearchBar/SearchBar';
14
14
  import { useStoreFiltering } from '../../../hooks/useStoreFiltering';
15
15
 
@@ -37,6 +37,7 @@ export const StoreItemsSection: React.FC<IStoreItemsSectionProps> = ({
37
37
  const {
38
38
  searchQuery,
39
39
  setSearchQuery,
40
+ selectedCategory,
40
41
  setSelectedCategory,
41
42
  categoryOptions,
42
43
  filteredItems,
@@ -93,13 +94,11 @@ export const StoreItemsSection: React.FC<IStoreItemsSectionProps> = ({
93
94
  placeholder="Search items..."
94
95
  />
95
96
  </SearchBarContainer>
96
- <DropdownContainer>
97
- <Dropdown
98
- options={categoryOptions}
99
- onChange={value => setSelectedCategory(value as ItemType | 'all')}
100
- width="100%"
101
- />
102
- </DropdownContainer>
97
+ <SegmentedToggle
98
+ options={categoryOptions.map(opt => ({ id: opt.value, label: opt.option }))}
99
+ activeId={selectedCategory}
100
+ onChange={id => setSelectedCategory(id as ItemType | 'all')}
101
+ />
103
102
  </SearchHeader>
104
103
 
105
104
  <ScrollableContent
@@ -130,8 +129,3 @@ const SearchHeader = styled.div`
130
129
  const SearchBarContainer = styled.div`
131
130
  flex: 0.75;
132
131
  `;
133
-
134
- const DropdownContainer = styled.div`
135
- flex: 0.25;
136
- min-width: 140px;
137
- `;
@@ -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 { ItemRowWrapper } from '../../shared/ItemRowWrapper';
6
7
  import { SpriteFromAtlas } from '../../shared/SpriteFromAtlas';
7
8
  import { ScrollableContent } from '../../shared/ScrollableContent/ScrollableContent';
8
9
  import { usePackFiltering } from '../../../hooks/usePackFiltering';
@@ -43,15 +44,17 @@ export const StorePacksSection: React.FC<IStorePacksSectionProps> = ({
43
44
  const renderPack = useCallback(
44
45
  (pack: IItemPack) => (
45
46
  <PackRow key={pack.key} onClick={() => onSelectPack?.(pack)}>
46
- <PackIconContainer>
47
- {renderPackIcon(pack)}
48
- </PackIconContainer>
49
-
50
- <PackDetails>
51
- <PackName>{pack.title}</PackName>
52
- <PackPrice>${pack.priceUSD}</PackPrice>
53
- {pack.description && <PackDescription>{pack.description}</PackDescription>}
54
- </PackDetails>
47
+ <LeftSection>
48
+ <PackIconContainer>
49
+ {renderPackIcon(pack)}
50
+ </PackIconContainer>
51
+
52
+ <PackDetails>
53
+ <PackName>{pack.title}</PackName>
54
+ <PackPrice>${pack.priceUSD}</PackPrice>
55
+ {pack.description && <PackDescription>{pack.description}</PackDescription>}
56
+ </PackDetails>
57
+ </LeftSection>
55
58
 
56
59
  <Controls>
57
60
  <CTAButton
@@ -84,21 +87,16 @@ export const StorePacksSection: React.FC<IStorePacksSectionProps> = ({
84
87
  );
85
88
  };
86
89
 
87
- const PackRow = styled.div`
90
+ const PackRow = styled(ItemRowWrapper)`
91
+ cursor: pointer;
92
+ `;
93
+
94
+ const LeftSection = styled.div`
88
95
  display: flex;
89
96
  align-items: center;
90
97
  gap: 0.75rem;
91
- padding: 0.5rem 1rem;
92
- border-bottom: 1px solid rgba(255, 255, 255, 0.1);
93
- cursor: pointer;
94
-
95
- &:last-child {
96
- border-bottom: none;
97
- }
98
-
99
- &:hover {
100
- background: rgba(255, 255, 255, 0.04);
101
- }
98
+ flex: 1;
99
+ min-width: 0;
102
100
  `;
103
101
 
104
102
  const PackIconContainer = styled.div`
@@ -0,0 +1,22 @@
1
+ import styled from 'styled-components';
2
+
3
+ export const ItemRowWrapper = styled.div<{ $isHighlighted?: boolean }>`
4
+ display: flex;
5
+ align-items: center;
6
+ justify-content: space-between;
7
+ padding: 0.6rem 1rem;
8
+ margin-bottom: 4px;
9
+ background: ${p => p.$isHighlighted ? 'rgba(255, 215, 0, 0.08)' : 'rgba(0, 0, 0, 0.25)'};
10
+ border: 1px solid rgba(255, 255, 255, 0.05);
11
+ border-radius: 6px;
12
+ border-left: 4px solid ${p => p.$isHighlighted ? '#ffd700' : 'transparent'};
13
+ transition: all 0.2s ease-in-out;
14
+
15
+ &:hover {
16
+ background: rgba(245, 158, 11, 0.08);
17
+ border-color: rgba(245, 158, 11, 0.2);
18
+ border-left-color: #f59e0b;
19
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
20
+ transform: translateY(-1px);
21
+ }
22
+ `;
@@ -43,13 +43,26 @@ const mockYourBuyOrders: IMarketplaceBuyOrderItem[] = [
43
43
  ];
44
44
 
45
45
  const mockOpenBuyOrders: IMarketplaceBuyOrderItem[] = [
46
+ // Abyssal Tide Staff — single request
46
47
  { _id: 'obo-1', owner: 'player-2', itemBlueprintKey: 'items/abyssal-tide-staff', itemRarity: 'Epic', maxPrice: 1500, escrowedGold: 1500, fee: 75, status: MarketplaceBuyOrderStatus.Active, createdAt: daysAgo(1), updatedAt: daysAgo(1) },
48
+ // Wooden Shield — single request
47
49
  { _id: 'obo-2', owner: 'player-3', itemBlueprintKey: 'items/wooden-shield', maxPrice: 200, escrowedGold: 200, fee: 10, status: MarketplaceBuyOrderStatus.Active, createdAt: daysAgo(5), updatedAt: daysAgo(5) },
48
- { _id: 'obo-3', owner: 'player-4', itemBlueprintKey: 'items/fire-wand', itemRarity: 'Rare', maxPrice: 800, escrowedGold: 800, fee: 40, status: MarketplaceBuyOrderStatus.Active, createdAt: daysAgo(2), updatedAt: daysAgo(2) },
49
- { _id: 'obo-4', owner: 'player-5', itemBlueprintKey: 'items/broad-sword', maxPrice: 350, escrowedGold: 350, fee: 17, status: MarketplaceBuyOrderStatus.Active, createdAt: daysAgo(3), updatedAt: daysAgo(3) },
50
+ // Fire Wand 2 requests (grouped)
51
+ { _id: 'obo-3a', owner: 'player-4', itemBlueprintKey: 'items/fire-wand', itemRarity: 'Rare', maxPrice: 800, escrowedGold: 800, fee: 40, status: MarketplaceBuyOrderStatus.Active, createdAt: daysAgo(2), updatedAt: daysAgo(2) },
52
+ { _id: 'obo-3b', owner: 'player-10', itemBlueprintKey: 'items/fire-wand', maxPrice: 550, escrowedGold: 550, fee: 27, status: MarketplaceBuyOrderStatus.Active, createdAt: daysAgo(4), updatedAt: daysAgo(4) },
53
+ // Broad Sword — 3 requests (grouped)
54
+ { _id: 'obo-4a', owner: 'player-5', itemBlueprintKey: 'items/broad-sword', maxPrice: 350, escrowedGold: 350, fee: 17, status: MarketplaceBuyOrderStatus.Active, createdAt: daysAgo(3), updatedAt: daysAgo(3) },
55
+ { _id: 'obo-4b', owner: 'player-11', itemBlueprintKey: 'items/broad-sword', itemRarity: 'Rare', maxPrice: 480, escrowedGold: 480, fee: 24, status: MarketplaceBuyOrderStatus.Active, createdAt: daysAgo(1), updatedAt: daysAgo(1) },
56
+ { _id: 'obo-4c', owner: 'player-12', itemBlueprintKey: 'items/broad-sword', maxPrice: 270, escrowedGold: 270, fee: 13, status: MarketplaceBuyOrderStatus.Active, createdAt: daysAgo(6), updatedAt: daysAgo(6) },
57
+ // Barbarian Helmet — single request
50
58
  { _id: 'obo-5', owner: 'player-6', itemBlueprintKey: 'items/barbarian-helmet', itemRarity: 'Uncommon', maxPrice: 600, escrowedGold: 600, fee: 30, status: MarketplaceBuyOrderStatus.Active, createdAt: daysAgo(1), updatedAt: daysAgo(1) },
51
- { _id: 'obo-6', owner: 'player-7', itemBlueprintKey: 'items/leather-armor', maxPrice: 420, escrowedGold: 420, fee: 21, status: MarketplaceBuyOrderStatus.Active, createdAt: daysAgo(4), updatedAt: daysAgo(4) },
52
- { _id: 'obo-7', owner: 'player-8', itemBlueprintKey: 'items/angelic-sword', itemRarity: 'Legendary', maxPrice: 5000, escrowedGold: 5000, fee: 250, status: MarketplaceBuyOrderStatus.Active, createdAt: daysAgo(6), updatedAt: daysAgo(6) },
59
+ // Leather Armor 2 requests (grouped)
60
+ { _id: 'obo-6a', owner: 'player-7', itemBlueprintKey: 'items/leather-armor', maxPrice: 420, escrowedGold: 420, fee: 21, status: MarketplaceBuyOrderStatus.Active, createdAt: daysAgo(4), updatedAt: daysAgo(4) },
61
+ { _id: 'obo-6b', owner: 'player-13', itemBlueprintKey: 'items/leather-armor', itemRarity: 'Uncommon', maxPrice: 380, escrowedGold: 380, fee: 19, status: MarketplaceBuyOrderStatus.Active, createdAt: daysAgo(2), updatedAt: daysAgo(2) },
62
+ // Angelic Sword — 2 requests (grouped)
63
+ { _id: 'obo-7a', owner: 'player-8', itemBlueprintKey: 'items/angelic-sword', itemRarity: 'Legendary', maxPrice: 5000, escrowedGold: 5000, fee: 250, status: MarketplaceBuyOrderStatus.Active, createdAt: daysAgo(6), updatedAt: daysAgo(6) },
64
+ { _id: 'obo-7b', owner: 'player-14', itemBlueprintKey: 'items/angelic-sword', itemRarity: 'Epic', maxPrice: 3500, escrowedGold: 3500, fee: 175, status: MarketplaceBuyOrderStatus.Active, createdAt: daysAgo(3), updatedAt: daysAgo(3) },
65
+ // Greater Life Potion — single request
53
66
  { _id: 'obo-8', owner: 'player-9', itemBlueprintKey: 'items/greater-life-potion', maxPrice: 90, stackQty: 10, escrowedGold: 900, fee: 45, status: MarketplaceBuyOrderStatus.Active, createdAt: daysAgo(2), updatedAt: daysAgo(2) },
54
67
  ];
55
68
 
@@ -169,7 +182,7 @@ const Template: Story = () => {
169
182
  onYourBuyOrdersPageChange={p => console.log('your orders page:', p)}
170
183
  onCancelBuyOrder={id => setYourBuyOrders(prev => prev.filter(o => o._id !== id))}
171
184
  openBuyOrders={mockOpenBuyOrders}
172
- openBuyOrdersTotal={mockOpenBuyOrders.length} // 8 items → pagination shows (page size = 5)
185
+ openBuyOrdersTotal={mockOpenBuyOrders.length}
173
186
  openBuyOrdersPage={1}
174
187
  onOpenBuyOrdersPageChange={(p: number) => console.log('open orders page:', p)}
175
188
  // Blueprint Search props