@rpg-engine/long-bow 0.8.162 → 0.8.165

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rpg-engine/long-bow",
3
- "version": "0.8.162",
3
+ "version": "0.8.165",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "typings": "dist/index.d.ts",
@@ -90,7 +90,6 @@ export const BuyPanel: React.FC<IBuyPanelProps> = ({
90
90
  dcBalance = 0,
91
91
  dcToGoldSwapRate = 0,
92
92
  openBuyOrders = [],
93
- openBuyOrdersTotal = 0,
94
93
  openBuyOrdersPage = 1,
95
94
  onOpenBuyOrdersPageChange,
96
95
  isLoading = false,
@@ -191,6 +190,11 @@ export const BuyPanel: React.FC<IBuyPanelProps> = ({
191
190
  });
192
191
  }, [visibleBuyOrders]);
193
192
 
193
+ const paginatedGroupedBuyOrders = useMemo(() => {
194
+ const start = (openBuyOrdersPage - 1) * BUY_REQUESTS_PER_PAGE;
195
+ return groupedBuyOrders.slice(start, start + BUY_REQUESTS_PER_PAGE);
196
+ }, [groupedBuyOrders, openBuyOrdersPage]);
197
+
194
198
  const showSellSection = browseMode === 'sell';
195
199
  const showBuySection = browseMode === 'buy';
196
200
  const hasVisibleContent =
@@ -447,7 +451,7 @@ export const BuyPanel: React.FC<IBuyPanelProps> = ({
447
451
  {groupedBuyOrders.length === 0 ? (
448
452
  <SectionEmpty>No public buy requests found.</SectionEmpty>
449
453
  ) : (
450
- groupedBuyOrders.map(({ bestOrder, otherOrders }) => (
454
+ paginatedGroupedBuyOrders.map(({ bestOrder, otherOrders }) => (
451
455
  <GroupedBuyOrderRow
452
456
  key={bestOrder._id}
453
457
  bestOrder={bestOrder}
@@ -473,9 +477,9 @@ export const BuyPanel: React.FC<IBuyPanelProps> = ({
473
477
  onPageChange={onPageChange}
474
478
  />
475
479
  )}
476
- {showBuySection && openBuyOrdersTotal > BUY_REQUESTS_PER_PAGE && (
480
+ {showBuySection && groupedBuyOrders.length > BUY_REQUESTS_PER_PAGE && (
477
481
  <Pager
478
- totalItems={openBuyOrdersTotal}
482
+ totalItems={groupedBuyOrders.length}
479
483
  currentPage={openBuyOrdersPage}
480
484
  itemsPerPage={BUY_REQUESTS_PER_PAGE}
481
485
  onPageChange={onOpenBuyOrdersPageChange ?? (() => {})}
@@ -10,6 +10,7 @@ import { uiColors } from '../../constants/uiColors';
10
10
  import { DraggableContainer } from '../DraggableContainer';
11
11
  import { RPGUIContainerTypes } from '../RPGUI/RPGUIContainer';
12
12
  import { Tabs } from '../shared/Tabs';
13
+ import { LabelPill } from '../shared/LabelPill/LabelPill';
13
14
  import { CTAButton } from '../shared/CTAButton/CTAButton';
14
15
  import { CartView } from './CartView';
15
16
  import { useStoreCart } from './hooks/useStoreCart';
@@ -42,6 +43,7 @@ export interface IStoreProps {
42
43
  textInputItemKeys?: string[];
43
44
  customPacksContent?: React.ReactNode;
44
45
  customWalletContent?: React.ReactNode;
46
+ packsBadge?: string;
45
47
  }
46
48
 
47
49
  export const Store: React.FC<IStoreProps> = ({
@@ -64,6 +66,7 @@ export const Store: React.FC<IStoreProps> = ({
64
66
  customPacksContent,
65
67
  customWalletContent,
66
68
  packsTabLabel = 'Packs',
69
+ packsBadge,
67
70
  }) => {
68
71
  const [selectedPack, setSelectedPack] = useState<IItemPack | null>(null);
69
72
  const [activeTab, setActiveTab] = useState<TabId>(() => {
@@ -87,7 +90,7 @@ export const Store: React.FC<IStoreProps> = ({
87
90
  const [isCollectingMetadata, setIsCollectingMetadata] = useState(false);
88
91
  const [currentMetadataItem, setCurrentMetadataItem] = useState<IProductBlueprint | null>(null);
89
92
 
90
- const handleAddPackToCart = (pack: IItemPack) => {
93
+ const handleAddPackToCart = (pack: IItemPack, quantity: number = 1) => {
91
94
  const packItem: IProductBlueprint = {
92
95
  key: pack.key,
93
96
  name: pack.title,
@@ -105,7 +108,7 @@ export const Store: React.FC<IStoreProps> = ({
105
108
  maxStackSize: 1,
106
109
  isUsable: false,
107
110
  };
108
- handleAddToCart(packItem, 1);
111
+ handleAddToCart(packItem, quantity);
109
112
  };
110
113
 
111
114
  const filterItems = (
@@ -163,7 +166,7 @@ export const Store: React.FC<IStoreProps> = ({
163
166
  const tabIds: TabId[] = tabOrder ?? ['premium', 'packs', 'items'];
164
167
  const availableTabIds: TabId[] = tabIds.filter(id => !(hidePremiumTab && id === 'premium'));
165
168
 
166
- const tabsMap: Record<string, { id: TabId; title: string; icon: ReactNode; content: ReactNode }> = {
169
+ const tabsMap: Record<string, { id: TabId; title: ReactNode; icon: ReactNode; content: ReactNode }> = {
167
170
  premium: {
168
171
  id: 'premium',
169
172
  title: 'Premium',
@@ -180,7 +183,12 @@ export const Store: React.FC<IStoreProps> = ({
180
183
  },
181
184
  packs: {
182
185
  id: 'packs',
183
- title: packsTabLabel,
186
+ title: packsBadge ? (
187
+ <TabLabelWithBadge>
188
+ {packsTabLabel}
189
+ <LabelPill background="#f59e0b" borderColor="#f59e0b" color="#000">{packsBadge}</LabelPill>
190
+ </TabLabelWithBadge>
191
+ ) : packsTabLabel,
184
192
  icon: <Gift width={18} height={18} />,
185
193
  content: customPacksContent ?? (
186
194
  <StorePacksSection
@@ -390,6 +398,13 @@ const CartInfo = styled.div`
390
398
  }
391
399
  `;
392
400
 
401
+ const TabLabelWithBadge = styled.span`
402
+ display: inline-flex;
403
+ align-items: center;
404
+ gap: 5px;
405
+ `;
406
+
407
+
393
408
  const LoadingMessage = styled.div`
394
409
  text-align: center;
395
410
  color: ${uiColors.white};
@@ -6,20 +6,73 @@ import { CTAButton } from '../../shared/CTAButton/CTAButton';
6
6
  import { ItemRowWrapper } from '../../shared/ItemRowWrapper';
7
7
  import { SpriteFromAtlas } from '../../shared/SpriteFromAtlas';
8
8
  import { ScrollableContent } from '../../shared/ScrollableContent/ScrollableContent';
9
+ import { SelectArrow } from '../../Arrow/SelectArrow';
9
10
  import { usePackFiltering } from '../../../hooks/usePackFiltering';
11
+ import { useQuantityControl } from '../../../hooks/useQuantityControl';
10
12
 
11
13
  interface IStorePacksSectionProps {
12
14
  packs: IItemPack[];
13
- onAddToCart: (pack: IItemPack) => void;
15
+ onAddToCart: (pack: IItemPack, quantity: number) => void;
14
16
  onSelectPack?: (pack: IItemPack) => void;
15
17
  atlasJSON?: any;
16
18
  atlasIMG?: string;
17
19
  }
18
20
 
21
+ interface IPackRowItemProps {
22
+ pack: IItemPack;
23
+ onAddToCart: (pack: IItemPack, quantity: number) => void;
24
+ renderPackIcon: (pack: IItemPack) => React.ReactNode;
25
+ }
26
+
27
+ const PackRowItem: React.FC<IPackRowItemProps> = ({ pack, onAddToCart, renderPackIcon }) => {
28
+ const { quantity, handleQuantityChange, handleBlur, incrementQuantity, decrementQuantity, resetQuantity } = useQuantityControl();
29
+
30
+ const handleAdd = () => {
31
+ onAddToCart(pack, quantity);
32
+ resetQuantity();
33
+ };
34
+
35
+ return (
36
+ <PackRow>
37
+ <LeftSection>
38
+ <PackIconContainer>
39
+ {renderPackIcon(pack)}
40
+ </PackIconContainer>
41
+
42
+ <PackDetails>
43
+ <PackName>{pack.title}</PackName>
44
+ <PackPrice>${pack.priceUSD}</PackPrice>
45
+ {pack.description && <PackDescription>{pack.description}</PackDescription>}
46
+ </PackDetails>
47
+ </LeftSection>
48
+
49
+ <Controls>
50
+ <ArrowsContainer>
51
+ <SelectArrow direction="left" onPointerDown={decrementQuantity} size={24} />
52
+ <QuantityInput
53
+ type="number"
54
+ value={quantity}
55
+ onChange={handleQuantityChange}
56
+ onBlur={handleBlur}
57
+ min={1}
58
+ max={99}
59
+ className="rpgui-input"
60
+ />
61
+ <SelectArrow direction="right" onPointerDown={incrementQuantity} size={24} />
62
+ </ArrowsContainer>
63
+ <CTAButton
64
+ icon={<FaCartPlus />}
65
+ label="Add"
66
+ onClick={handleAdd}
67
+ />
68
+ </Controls>
69
+ </PackRow>
70
+ );
71
+ };
72
+
19
73
  export const StorePacksSection: React.FC<IStorePacksSectionProps> = ({
20
74
  packs,
21
75
  onAddToCart,
22
- onSelectPack,
23
76
  atlasJSON,
24
77
  atlasIMG,
25
78
  }) => {
@@ -43,32 +96,14 @@ export const StorePacksSection: React.FC<IStorePacksSectionProps> = ({
43
96
 
44
97
  const renderPack = useCallback(
45
98
  (pack: IItemPack) => (
46
- <PackRow key={pack.key} onClick={() => onSelectPack?.(pack)}>
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>
58
-
59
- <Controls>
60
- <CTAButton
61
- icon={<FaCartPlus />}
62
- label="Add"
63
- onClick={e => {
64
- e.stopPropagation();
65
- onAddToCart(pack);
66
- }}
67
- />
68
- </Controls>
69
- </PackRow>
99
+ <PackRowItem
100
+ key={pack.key}
101
+ pack={pack}
102
+ onAddToCart={onAddToCart}
103
+ renderPackIcon={renderPackIcon}
104
+ />
70
105
  ),
71
- [onSelectPack, onAddToCart]
106
+ [onAddToCart, renderPackIcon]
72
107
  );
73
108
 
74
109
  return (
@@ -87,9 +122,7 @@ export const StorePacksSection: React.FC<IStorePacksSectionProps> = ({
87
122
  );
88
123
  };
89
124
 
90
- const PackRow = styled(ItemRowWrapper)`
91
- cursor: pointer;
92
- `;
125
+ const PackRow = styled(ItemRowWrapper)``;
93
126
 
94
127
  const LeftSection = styled.div`
95
128
  display: flex;
@@ -144,5 +177,32 @@ const PackDescription = styled.div`
144
177
  const Controls = styled.div`
145
178
  display: flex;
146
179
  align-items: center;
180
+ gap: 0.5rem;
147
181
  flex-shrink: 0;
148
182
  `;
183
+
184
+ const ArrowsContainer = styled.div`
185
+ position: relative;
186
+ display: flex;
187
+ align-items: center;
188
+ width: 120px;
189
+ height: 42px;
190
+ justify-content: space-between;
191
+ `;
192
+
193
+ const QuantityInput = styled.input`
194
+ width: 40px;
195
+ text-align: center;
196
+ margin: 0 auto;
197
+ font-size: 0.875rem;
198
+ background: rgba(0, 0, 0, 0.2);
199
+ color: #ffffff;
200
+ border: none;
201
+ padding: 0.25rem;
202
+
203
+ &::-webkit-inner-spin-button,
204
+ &::-webkit-outer-spin-button {
205
+ -webkit-appearance: none;
206
+ margin: 0;
207
+ }
208
+ `;
@@ -237,8 +237,9 @@ export const Default: Story = {
237
237
  atlasIMG={itemsAtlasIMG}
238
238
  hidePremiumTab={true}
239
239
  tabOrder={['items', 'packs']}
240
- defaultActiveTab="items"
240
+ defaultActiveTab="packs"
241
241
  textInputItemKeys={['original-greater-life-potion-2', 'original-angelic-sword-1', 'character-name-change']}
242
+ packsBadge="SAVE"
242
243
  />
243
244
  ),
244
245
  };