@rpg-engine/long-bow 0.8.71 → 0.8.73
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/Store/CartView.d.ts +7 -6
- package/dist/components/Store/Store.d.ts +2 -2
- package/dist/components/Store/StoreCharacterSkinRow.d.ts +3 -3
- package/dist/components/Store/StoreItemDetails.d.ts +3 -3
- package/dist/components/Store/StoreItemRow.d.ts +4 -3
- package/dist/components/Store/hooks/useStoreCart.d.ts +5 -3
- package/dist/components/Store/hooks/useStoreMetadata.d.ts +3 -3
- package/dist/components/Store/sections/StoreItemsSection.d.ts +3 -3
- package/dist/hooks/useCharacterSkinNavigation.d.ts +7 -0
- package/dist/hooks/usePackFiltering.d.ts +7 -0
- package/dist/hooks/useQuantityControl.d.ts +10 -0
- package/dist/hooks/useStoreFiltering.d.ts +11 -0
- package/dist/long-bow.cjs.development.js +264 -112
- 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 +265 -113
- package/dist/long-bow.esm.js.map +1 -1
- package/package.json +2 -2
- package/src/components/Store/CartView.tsx +9 -6
- package/src/components/Store/Store.tsx +13 -21
- package/src/components/Store/StoreCharacterSkinRow.tsx +64 -46
- package/src/components/Store/StoreItemDetails.tsx +4 -4
- package/src/components/Store/StoreItemRow.tsx +64 -56
- package/src/components/Store/hooks/useStoreCart.ts +14 -9
- package/src/components/Store/hooks/useStoreMetadata.ts +5 -5
- package/src/components/Store/sections/StoreItemsSection.tsx +78 -27
- package/src/components/Store/sections/StorePacksSection.tsx +5 -10
- package/src/hooks/useCharacterSkinNavigation.ts +34 -0
- package/src/hooks/usePackFiltering.ts +20 -0
- package/src/hooks/useQuantityControl.ts +41 -0
- package/src/hooks/useStoreFiltering.ts +51 -0
- package/src/mocks/dailyTasks.mocks.ts +6 -6
- package/src/stories/Features/store/Store.stories.tsx +59 -72
|
@@ -1,13 +1,25 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
IProductBlueprint,
|
|
3
|
+
MetadataType,
|
|
4
|
+
UserAccountTypes,
|
|
5
|
+
ItemType,
|
|
6
|
+
} from '@rpg-engine/shared';
|
|
7
|
+
import React from 'react';
|
|
8
|
+
import styled from 'styled-components';
|
|
3
9
|
import { ScrollableContent } from '../../shared/ScrollableContent/ScrollableContent';
|
|
4
10
|
import { StoreCharacterSkinRow } from '../StoreCharacterSkinRow';
|
|
5
11
|
import { StoreItemRow } from '../StoreItemRow';
|
|
6
|
-
|
|
12
|
+
import { Dropdown } from '../../Dropdown';
|
|
13
|
+
import { SearchBar } from '../../shared/SearchBar/SearchBar';
|
|
14
|
+
import { useStoreFiltering } from '../../../hooks/useStoreFiltering';
|
|
7
15
|
|
|
8
16
|
interface IStoreItemsSectionProps {
|
|
9
|
-
items:
|
|
10
|
-
onAddToCart: (
|
|
17
|
+
items: IProductBlueprint[];
|
|
18
|
+
onAddToCart: (
|
|
19
|
+
item: IProductBlueprint,
|
|
20
|
+
quantity: number,
|
|
21
|
+
metadata?: Record<string, any>
|
|
22
|
+
) => void;
|
|
11
23
|
atlasJSON: Record<string, any>;
|
|
12
24
|
atlasIMG: string;
|
|
13
25
|
userAccountType?: UserAccountTypes;
|
|
@@ -22,18 +34,20 @@ export const StoreItemsSection: React.FC<IStoreItemsSectionProps> = ({
|
|
|
22
34
|
userAccountType,
|
|
23
35
|
textInputItemKeys = [],
|
|
24
36
|
}) => {
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
37
|
+
const {
|
|
38
|
+
searchQuery,
|
|
39
|
+
setSearchQuery,
|
|
40
|
+
setSelectedCategory,
|
|
41
|
+
categoryOptions,
|
|
42
|
+
filteredItems,
|
|
43
|
+
} = useStoreFiltering(items);
|
|
30
44
|
|
|
31
|
-
const renderStoreItem = (item:
|
|
45
|
+
const renderStoreItem = (item: IProductBlueprint) => {
|
|
32
46
|
// Prefer a specialized character skin row when needed
|
|
33
47
|
if (item.metadataType === MetadataType.CharacterSkin) {
|
|
34
48
|
return (
|
|
35
49
|
<StoreCharacterSkinRow
|
|
36
|
-
key={item.
|
|
50
|
+
key={item.key}
|
|
37
51
|
item={item}
|
|
38
52
|
atlasJSON={atlasJSON}
|
|
39
53
|
atlasIMG={atlasIMG}
|
|
@@ -43,10 +57,10 @@ export const StoreItemsSection: React.FC<IStoreItemsSectionProps> = ({
|
|
|
43
57
|
);
|
|
44
58
|
}
|
|
45
59
|
// Render text input row when configured for this item key
|
|
46
|
-
if (textInputItemKeys.includes(item.key)
|
|
60
|
+
if (textInputItemKeys.includes(item.key)) {
|
|
47
61
|
return (
|
|
48
62
|
<StoreItemRow
|
|
49
|
-
key={item.
|
|
63
|
+
key={item.key}
|
|
50
64
|
item={item}
|
|
51
65
|
atlasJSON={atlasJSON}
|
|
52
66
|
atlasIMG={atlasIMG}
|
|
@@ -59,7 +73,7 @@ export const StoreItemsSection: React.FC<IStoreItemsSectionProps> = ({
|
|
|
59
73
|
// Fallback to standard arrow-based row
|
|
60
74
|
return (
|
|
61
75
|
<StoreItemRow
|
|
62
|
-
key={item.
|
|
76
|
+
key={item.key}
|
|
63
77
|
item={item}
|
|
64
78
|
atlasJSON={atlasJSON}
|
|
65
79
|
atlasIMG={atlasIMG}
|
|
@@ -70,17 +84,54 @@ export const StoreItemsSection: React.FC<IStoreItemsSectionProps> = ({
|
|
|
70
84
|
};
|
|
71
85
|
|
|
72
86
|
return (
|
|
73
|
-
<
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
87
|
+
<StoreContainer>
|
|
88
|
+
<SearchHeader>
|
|
89
|
+
<SearchBarContainer>
|
|
90
|
+
<SearchBar
|
|
91
|
+
value={searchQuery}
|
|
92
|
+
onChange={setSearchQuery}
|
|
93
|
+
placeholder="Search items..."
|
|
94
|
+
/>
|
|
95
|
+
</SearchBarContainer>
|
|
96
|
+
<DropdownContainer>
|
|
97
|
+
<Dropdown
|
|
98
|
+
options={categoryOptions}
|
|
99
|
+
onChange={value => setSelectedCategory(value as ItemType | 'all')}
|
|
100
|
+
width="100%"
|
|
101
|
+
/>
|
|
102
|
+
</DropdownContainer>
|
|
103
|
+
</SearchHeader>
|
|
104
|
+
|
|
105
|
+
<ScrollableContent
|
|
106
|
+
items={filteredItems}
|
|
107
|
+
renderItem={renderStoreItem}
|
|
108
|
+
emptyMessage="No items match your filters."
|
|
109
|
+
layout="list"
|
|
110
|
+
maxHeight="350px"
|
|
111
|
+
/>
|
|
112
|
+
</StoreContainer>
|
|
85
113
|
);
|
|
86
114
|
};
|
|
115
|
+
|
|
116
|
+
const StoreContainer = styled.div`
|
|
117
|
+
display: flex;
|
|
118
|
+
flex-direction: column;
|
|
119
|
+
height: 100%;
|
|
120
|
+
gap: 0.5rem;
|
|
121
|
+
`;
|
|
122
|
+
|
|
123
|
+
const SearchHeader = styled.div`
|
|
124
|
+
display: flex;
|
|
125
|
+
gap: 0.5rem;
|
|
126
|
+
align-items: center;
|
|
127
|
+
padding-top: 0.25rem;
|
|
128
|
+
`;
|
|
129
|
+
|
|
130
|
+
const SearchBarContainer = styled.div`
|
|
131
|
+
flex: 0.75;
|
|
132
|
+
`;
|
|
133
|
+
|
|
134
|
+
const DropdownContainer = styled.div`
|
|
135
|
+
flex: 0.25;
|
|
136
|
+
min-width: 140px;
|
|
137
|
+
`;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { IItemPack } from '@rpg-engine/shared';
|
|
2
|
-
import React, { useCallback
|
|
2
|
+
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
6
|
import { ScrollableContent } from '../../shared/ScrollableContent/ScrollableContent';
|
|
7
7
|
import { ShoppingCardHorizontal } from '../../shared/ShoppingCart/CartCardHorizontal';
|
|
8
|
+
import { usePackFiltering } from '../../../hooks/usePackFiltering';
|
|
8
9
|
|
|
9
10
|
interface IStorePacksSectionProps {
|
|
10
11
|
packs: IItemPack[];
|
|
@@ -17,7 +18,9 @@ export const StorePacksSection: React.FC<IStorePacksSectionProps> = ({
|
|
|
17
18
|
onAddToCart,
|
|
18
19
|
onSelectPack,
|
|
19
20
|
}) => {
|
|
20
|
-
const
|
|
21
|
+
const { searchQuery, setSearchQuery, filteredPacks } = usePackFiltering(
|
|
22
|
+
packs
|
|
23
|
+
);
|
|
21
24
|
|
|
22
25
|
const renderPackFooter = useCallback(
|
|
23
26
|
(pack: IItemPack) => (
|
|
@@ -50,14 +53,6 @@ export const StorePacksSection: React.FC<IStorePacksSectionProps> = ({
|
|
|
50
53
|
[onSelectPack, renderPackFooter]
|
|
51
54
|
);
|
|
52
55
|
|
|
53
|
-
const filteredPacks = useMemo(
|
|
54
|
-
() =>
|
|
55
|
-
packs.filter(pack =>
|
|
56
|
-
pack.title.toLowerCase().includes(searchQuery.toLowerCase())
|
|
57
|
-
),
|
|
58
|
-
[packs, searchQuery]
|
|
59
|
-
);
|
|
60
|
-
|
|
61
56
|
return (
|
|
62
57
|
<ScrollableContent
|
|
63
58
|
items={filteredPacks}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { ICharacterProps } from '../components/Character/CharacterSelection';
|
|
3
|
+
|
|
4
|
+
export const useCharacterSkinNavigation = (
|
|
5
|
+
availableCharacters: ICharacterProps[],
|
|
6
|
+
itemKey: string
|
|
7
|
+
) => {
|
|
8
|
+
const [currentIndex, setCurrentIndex] = useState(0);
|
|
9
|
+
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
setCurrentIndex(0);
|
|
12
|
+
}, [itemKey]);
|
|
13
|
+
|
|
14
|
+
const handlePreviousSkin = () => {
|
|
15
|
+
setCurrentIndex(prevIndex =>
|
|
16
|
+
prevIndex === 0 ? availableCharacters.length - 1 : prevIndex - 1
|
|
17
|
+
);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const handleNextSkin = () => {
|
|
21
|
+
setCurrentIndex(prevIndex =>
|
|
22
|
+
prevIndex === availableCharacters.length - 1 ? 0 : prevIndex + 1
|
|
23
|
+
);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const currentCharacter = availableCharacters[currentIndex];
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
currentIndex,
|
|
30
|
+
currentCharacter,
|
|
31
|
+
handlePreviousSkin,
|
|
32
|
+
handleNextSkin,
|
|
33
|
+
};
|
|
34
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { useMemo, useState } from 'react';
|
|
2
|
+
import { IItemPack } from '@rpg-engine/shared';
|
|
3
|
+
|
|
4
|
+
export const usePackFiltering = (packs: IItemPack[]) => {
|
|
5
|
+
const [searchQuery, setSearchQuery] = useState('');
|
|
6
|
+
|
|
7
|
+
const filteredPacks = useMemo(
|
|
8
|
+
() =>
|
|
9
|
+
packs.filter(pack =>
|
|
10
|
+
pack.title.toLowerCase().includes(searchQuery.toLowerCase())
|
|
11
|
+
),
|
|
12
|
+
[packs, searchQuery]
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
searchQuery,
|
|
17
|
+
setSearchQuery,
|
|
18
|
+
filteredPacks,
|
|
19
|
+
};
|
|
20
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
|
|
3
|
+
export const useQuantityControl = (
|
|
4
|
+
initialQuantity: number = 1,
|
|
5
|
+
min: number = 1,
|
|
6
|
+
max: number = 99
|
|
7
|
+
) => {
|
|
8
|
+
const [quantity, setQuantity] = useState(initialQuantity);
|
|
9
|
+
|
|
10
|
+
const handleQuantityChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
11
|
+
const value = parseInt(e.target.value) || min;
|
|
12
|
+
setQuantity(Math.min(Math.max(min, value), max));
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const handleBlur = () => {
|
|
16
|
+
if (quantity < min) setQuantity(min);
|
|
17
|
+
if (quantity > max) setQuantity(max);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const incrementQuantity = () => {
|
|
21
|
+
setQuantity(prev => Math.min(prev + 1, max));
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const decrementQuantity = () => {
|
|
25
|
+
setQuantity(prev => Math.max(min, prev - 1));
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const resetQuantity = () => {
|
|
29
|
+
setQuantity(initialQuantity);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
quantity,
|
|
34
|
+
setQuantity,
|
|
35
|
+
handleQuantityChange,
|
|
36
|
+
handleBlur,
|
|
37
|
+
incrementQuantity,
|
|
38
|
+
decrementQuantity,
|
|
39
|
+
resetQuantity,
|
|
40
|
+
};
|
|
41
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { useMemo, useState } from 'react';
|
|
2
|
+
import { IProductBlueprint, ItemType } from '@rpg-engine/shared';
|
|
3
|
+
import { IOptionsProps } from '../components/Dropdown';
|
|
4
|
+
|
|
5
|
+
export const useStoreFiltering = (items: IProductBlueprint[]) => {
|
|
6
|
+
const [searchQuery, setSearchQuery] = useState('');
|
|
7
|
+
const [selectedCategory, setSelectedCategory] = useState<ItemType | 'all'>(
|
|
8
|
+
'all'
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
const categoryOptions: IOptionsProps[] = useMemo(() => {
|
|
12
|
+
const uniqueCategories = Array.from(
|
|
13
|
+
new Set(items.map(item => item.itemType))
|
|
14
|
+
);
|
|
15
|
+
const allCategories = ['all', ...uniqueCategories] as (ItemType | 'all')[];
|
|
16
|
+
return allCategories.map((category, index) => ({
|
|
17
|
+
id: index,
|
|
18
|
+
value: category,
|
|
19
|
+
option: category === 'all' ? 'All' : category,
|
|
20
|
+
}));
|
|
21
|
+
}, [items]);
|
|
22
|
+
|
|
23
|
+
const filteredItems = useMemo(() => {
|
|
24
|
+
return items
|
|
25
|
+
.filter(item => {
|
|
26
|
+
const matchesSearch = item.name
|
|
27
|
+
.toLowerCase()
|
|
28
|
+
.includes(searchQuery.toLowerCase());
|
|
29
|
+
const matchesCategory =
|
|
30
|
+
selectedCategory === 'all' || item.itemType === selectedCategory;
|
|
31
|
+
return matchesSearch && matchesCategory;
|
|
32
|
+
})
|
|
33
|
+
.sort((a, b) => {
|
|
34
|
+
const aHighlighted = a.store?.isHighlighted || false;
|
|
35
|
+
const bHighlighted = b.store?.isHighlighted || false;
|
|
36
|
+
|
|
37
|
+
if (aHighlighted && !bHighlighted) return -1;
|
|
38
|
+
if (!aHighlighted && bHighlighted) return 1;
|
|
39
|
+
return 0;
|
|
40
|
+
});
|
|
41
|
+
}, [items, searchQuery, selectedCategory]);
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
searchQuery,
|
|
45
|
+
setSearchQuery,
|
|
46
|
+
selectedCategory,
|
|
47
|
+
setSelectedCategory,
|
|
48
|
+
categoryOptions,
|
|
49
|
+
filteredItems,
|
|
50
|
+
};
|
|
51
|
+
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ICharacterDailyTask, RewardType, TaskDifficulty, TaskStatus, TaskType } from "@rpg-engine/shared";
|
|
2
2
|
|
|
3
3
|
export const mockTasks: ICharacterDailyTask[] = [
|
|
4
4
|
{
|
|
5
|
-
key:
|
|
5
|
+
key: 'hunt-rats',
|
|
6
6
|
name: 'Kill Rats',
|
|
7
7
|
description: 'Eliminate 5 rats in the forest',
|
|
8
8
|
type: TaskType.KillMobs,
|
|
@@ -43,7 +43,7 @@ export const mockTasks: ICharacterDailyTask[] = [
|
|
|
43
43
|
tier: 0
|
|
44
44
|
},
|
|
45
45
|
{
|
|
46
|
-
key:
|
|
46
|
+
key: 'craft-cheese',
|
|
47
47
|
name: 'Gather Resources',
|
|
48
48
|
description: 'Collect 10 pieces of wood',
|
|
49
49
|
type: TaskType.CollectItems,
|
|
@@ -84,7 +84,7 @@ export const mockTasks: ICharacterDailyTask[] = [
|
|
|
84
84
|
tier: 0
|
|
85
85
|
},
|
|
86
86
|
{
|
|
87
|
-
key:
|
|
87
|
+
key: 'hunt-elora-the-queen',
|
|
88
88
|
name: 'Slay the Dragon',
|
|
89
89
|
description: 'Defeat the Queen Dragon',
|
|
90
90
|
type: TaskType.KillMobs,
|
|
@@ -125,7 +125,7 @@ export const mockTasks: ICharacterDailyTask[] = [
|
|
|
125
125
|
tier: 0
|
|
126
126
|
},
|
|
127
127
|
{
|
|
128
|
-
key:
|
|
128
|
+
key: 'explore-village',
|
|
129
129
|
name: "Village Ilya Explorer",
|
|
130
130
|
description: "Visit the key locations in the ilya village",
|
|
131
131
|
difficulty: TaskDifficulty.Regular,
|
|
@@ -167,7 +167,7 @@ export const mockTasks: ICharacterDailyTask[] = [
|
|
|
167
167
|
tier: 0
|
|
168
168
|
},
|
|
169
169
|
{
|
|
170
|
-
key:
|
|
170
|
+
key: 'craft-life-potion',
|
|
171
171
|
name: 'Craft Potions',
|
|
172
172
|
description: 'Craft 5 health potions for the local alchemist',
|
|
173
173
|
type: TaskType.CraftItems,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { IItemPack,
|
|
1
|
+
import { IItemPack, IProductBlueprint, IPurchase, ItemRarities, ItemSubType, ItemType, MetadataType, PaymentCurrency, PurchaseType, UserAccountTypes } from '@rpg-engine/shared';
|
|
2
2
|
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
3
|
import React from 'react';
|
|
4
4
|
import { RPGUIRoot } from '../../../components/RPGUI/RPGUIRoot';
|
|
@@ -29,42 +29,26 @@ export default meta;
|
|
|
29
29
|
type Story = StoryObj<typeof Store>;
|
|
30
30
|
|
|
31
31
|
// Create mock items once, with fixed stock values
|
|
32
|
-
const storeItems:
|
|
33
|
-
_id: `original-${item.key}-${index}`,
|
|
32
|
+
const storeItems: IProductBlueprint[] = mockItems.map((item, index) => ({
|
|
34
33
|
key: `original-${item.key}-${index}`,
|
|
35
34
|
name: item.name,
|
|
36
35
|
description: item.description,
|
|
37
|
-
|
|
36
|
+
price: item.basePrice,
|
|
37
|
+
currency: PaymentCurrency.USD,
|
|
38
38
|
texturePath: item.texturePath,
|
|
39
39
|
textureAtlas: 'items',
|
|
40
40
|
textureKey: item.texturePath,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
attack: item.attack || 0,
|
|
46
|
-
defense: item.defense || 0,
|
|
47
|
-
weight: item.weight,
|
|
41
|
+
type: PurchaseType.Item,
|
|
42
|
+
onPurchase: async () => { },
|
|
43
|
+
itemType: item.type,
|
|
44
|
+
itemSubType: item.subType,
|
|
48
45
|
rarity: item.rarity,
|
|
49
|
-
|
|
46
|
+
weight: item.weight,
|
|
50
47
|
isStackable: item.maxStackSize > 1,
|
|
51
|
-
isUsable: true,
|
|
52
|
-
isStorable: true,
|
|
53
|
-
hasUseWith: false,
|
|
54
|
-
isSolid: false,
|
|
55
|
-
isTwoHanded: false,
|
|
56
|
-
isItemContainer: false,
|
|
57
|
-
layer: 1,
|
|
58
|
-
allowedEquipSlotType: item.allowedEquipSlotType || [],
|
|
59
48
|
maxStackSize: item.maxStackSize || 1,
|
|
60
|
-
|
|
61
|
-
canSell: item.canSell ?? true,
|
|
62
|
-
rangeType: item.rangeType,
|
|
63
|
-
entityEffects: item.entityEffects || [],
|
|
64
|
-
entityEffectChance: item.entityEffectChance || 0,
|
|
65
|
-
equippedBuff: item.equippedBuff || [],
|
|
66
|
-
equippedBuffDescription: item.equippedBuffDescription || '',
|
|
49
|
+
isUsable: true,
|
|
67
50
|
requiredAccountType: item.rarity === 'Legendary' ? [UserAccountTypes.PremiumGold] : [],
|
|
51
|
+
|
|
68
52
|
}));
|
|
69
53
|
|
|
70
54
|
// Sample character skins
|
|
@@ -86,72 +70,77 @@ const availableCharacters = [
|
|
|
86
70
|
},
|
|
87
71
|
];
|
|
88
72
|
|
|
73
|
+
// Create character name change item
|
|
74
|
+
const characterNameChangeItem: IProductBlueprint = {
|
|
75
|
+
key: 'character-name-change',
|
|
76
|
+
name: 'Character Name Change',
|
|
77
|
+
description: 'Change your character\'s name to something new',
|
|
78
|
+
price: 9.99,
|
|
79
|
+
currency: PaymentCurrency.USD,
|
|
80
|
+
texturePath: 'items/character_customization.png',
|
|
81
|
+
textureAtlas: 'items',
|
|
82
|
+
textureKey: 'items/character_customization.png',
|
|
83
|
+
type: PurchaseType.Item,
|
|
84
|
+
onPurchase: async () => { },
|
|
85
|
+
itemType: ItemType.Other,
|
|
86
|
+
itemSubType: ItemSubType.Other,
|
|
87
|
+
rarity: ItemRarities.Common,
|
|
88
|
+
weight: 0,
|
|
89
|
+
isStackable: false,
|
|
90
|
+
maxStackSize: 1,
|
|
91
|
+
isUsable: true,
|
|
92
|
+
inputPlaceholder: 'Enter new character name',
|
|
93
|
+
};
|
|
94
|
+
|
|
89
95
|
// Create character skin items
|
|
90
|
-
const characterSkinItems:
|
|
96
|
+
const characterSkinItems: IProductBlueprint[] = [
|
|
91
97
|
{
|
|
92
|
-
_id: 'skin-character-customization',
|
|
93
98
|
key: 'skin-character-customization',
|
|
94
99
|
name: 'Character Skin Customization',
|
|
95
100
|
description: 'Customize your character\'s appearance with a variety of skins',
|
|
96
|
-
|
|
101
|
+
price: 14.99,
|
|
102
|
+
currency: PaymentCurrency.USD,
|
|
97
103
|
texturePath: 'items/character_customization.png',
|
|
98
104
|
textureAtlas: 'items',
|
|
99
105
|
textureKey: 'items/character_customization.png',
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
defense: 0,
|
|
105
|
-
weight: 0,
|
|
106
|
+
type: PurchaseType.Item,
|
|
107
|
+
onPurchase: async () => { },
|
|
108
|
+
itemType: ItemType.Other,
|
|
109
|
+
itemSubType: ItemSubType.Other,
|
|
106
110
|
rarity: ItemRarities.Rare,
|
|
107
|
-
|
|
111
|
+
weight: 0,
|
|
108
112
|
isStackable: false,
|
|
109
|
-
isUsable: true,
|
|
110
|
-
isStorable: true,
|
|
111
|
-
hasUseWith: false,
|
|
112
|
-
isSolid: false,
|
|
113
|
-
isTwoHanded: false,
|
|
114
|
-
isItemContainer: false,
|
|
115
|
-
layer: 1,
|
|
116
|
-
allowedEquipSlotType: [],
|
|
117
113
|
maxStackSize: 1,
|
|
118
|
-
|
|
114
|
+
isUsable: true,
|
|
119
115
|
metadataType: MetadataType.CharacterSkin,
|
|
120
116
|
metadataConfig: {
|
|
121
117
|
availableCharacters,
|
|
122
118
|
atlasJSON: entitiesAtlasJSON,
|
|
123
119
|
atlasIMG: entitiesAtlasIMG,
|
|
124
120
|
},
|
|
121
|
+
store: {
|
|
122
|
+
isHighlighted: true, // Highlight the first character skin item
|
|
123
|
+
},
|
|
125
124
|
},
|
|
126
125
|
{
|
|
127
|
-
_id: 'skin-premium-character-pack',
|
|
128
126
|
key: 'skin-premium-character-pack',
|
|
129
127
|
name: 'Premium Character Skin Pack',
|
|
130
128
|
description: 'A premium collection of exclusive character skins',
|
|
131
|
-
|
|
129
|
+
price: 24.99,
|
|
130
|
+
currency: PaymentCurrency.USD,
|
|
132
131
|
texturePath: 'items/premium_character_pack.png',
|
|
133
132
|
textureAtlas: 'items',
|
|
134
133
|
textureKey: 'items/premium_character_pack.png',
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
defense: 0,
|
|
140
|
-
weight: 0,
|
|
134
|
+
type: PurchaseType.Item,
|
|
135
|
+
onPurchase: async () => { },
|
|
136
|
+
itemType: ItemType.Other,
|
|
137
|
+
itemSubType: ItemSubType.Other,
|
|
141
138
|
rarity: ItemRarities.Epic,
|
|
142
|
-
|
|
139
|
+
weight: 0,
|
|
143
140
|
isStackable: false,
|
|
144
|
-
isUsable: true,
|
|
145
|
-
isStorable: true,
|
|
146
|
-
hasUseWith: false,
|
|
147
|
-
isSolid: false,
|
|
148
|
-
isTwoHanded: false,
|
|
149
|
-
isItemContainer: false,
|
|
150
|
-
layer: 1,
|
|
151
|
-
allowedEquipSlotType: [],
|
|
152
141
|
maxStackSize: 1,
|
|
142
|
+
isUsable: true,
|
|
153
143
|
requiredAccountType: [UserAccountTypes.PremiumSilver],
|
|
154
|
-
// Add metadata type and config with the same character options
|
|
155
144
|
metadataType: MetadataType.CharacterSkin,
|
|
156
145
|
metadataConfig: {
|
|
157
146
|
availableCharacters,
|
|
@@ -162,22 +151,20 @@ const characterSkinItems: IStoreItem[] = [
|
|
|
162
151
|
];
|
|
163
152
|
|
|
164
153
|
// Create duplicated items once with unique keys
|
|
165
|
-
const duplicatedItems:
|
|
154
|
+
const duplicatedItems: IProductBlueprint[] = [
|
|
166
155
|
...storeItems,
|
|
156
|
+
characterNameChangeItem,
|
|
167
157
|
...characterSkinItems,
|
|
168
158
|
...storeItems.map((item, index) => ({
|
|
169
159
|
...item,
|
|
170
|
-
_id: `copy1-${item.key}-${index}`,
|
|
171
160
|
key: `copy1-${item.key}-${index}`,
|
|
172
161
|
})),
|
|
173
162
|
...storeItems.map((item, index) => ({
|
|
174
163
|
...item,
|
|
175
|
-
_id: `copy2-${item.key}-${index}`,
|
|
176
164
|
key: `copy2-${item.key}-${index}`,
|
|
177
165
|
})),
|
|
178
166
|
...storeItems.map((item, index) => ({
|
|
179
167
|
...item,
|
|
180
|
-
_id: `copy3-${item.key}-${index}`,
|
|
181
168
|
key: `copy3-${item.key}-${index}`,
|
|
182
169
|
})),
|
|
183
170
|
];
|
|
@@ -200,20 +187,20 @@ const mockPacks: IItemPack[] = [
|
|
|
200
187
|
image: customSkinImage,
|
|
201
188
|
},
|
|
202
189
|
|
|
203
|
-
|
|
190
|
+
{
|
|
204
191
|
key: 'ultimate-pack',
|
|
205
192
|
title: '👑 Ultimate Premium Pack',
|
|
206
193
|
description: 'The most exclusive collection of items, effects, and perks. Includes everything from previous tiers plus legendary items!',
|
|
207
194
|
priceUSD: 99.99,
|
|
208
195
|
image: customSkinImage,
|
|
209
196
|
},
|
|
210
|
-
|
|
197
|
+
{
|
|
211
198
|
key: 'gold-pack',
|
|
212
199
|
title: '🥇 Gold Premium Pack',
|
|
213
200
|
description: 'The ultimate premium experience with rare items, unique effects, and exclusive content. For true champions!',
|
|
214
201
|
priceUSD: 49.99,
|
|
215
202
|
image: customSkinImage,
|
|
216
|
-
},
|
|
203
|
+
},
|
|
217
204
|
{
|
|
218
205
|
key: 'silver-pack',
|
|
219
206
|
title: '🥈 Silver Premium Pack',
|
|
@@ -249,7 +236,7 @@ export const Default: Story = {
|
|
|
249
236
|
hidePremiumTab={true}
|
|
250
237
|
tabOrder={['items', 'packs']}
|
|
251
238
|
defaultActiveTab="items"
|
|
252
|
-
textInputItemKeys={['original-greater-life-potion-2', 'original-angelic-sword-1']}
|
|
239
|
+
textInputItemKeys={['original-greater-life-potion-2', 'original-angelic-sword-1', 'character-name-change']}
|
|
253
240
|
/>
|
|
254
241
|
),
|
|
255
242
|
};
|