@rpg-engine/long-bow 0.8.66 → 0.8.68

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.
Files changed (37) hide show
  1. package/dist/components/Character/SkinSelectionGrid.d.ts +11 -0
  2. package/dist/components/Store/CartView.d.ts +1 -0
  3. package/dist/components/Store/MetadataCollector.d.ts +9 -0
  4. package/dist/components/Store/Store.d.ts +13 -1
  5. package/dist/components/Store/StoreCharacterSkinRow.d.ts +11 -0
  6. package/dist/components/Store/StoreItemRow.d.ts +1 -1
  7. package/dist/components/Store/hooks/useStoreCart.d.ts +6 -2
  8. package/dist/components/Store/hooks/useStoreMetadata.d.ts +15 -0
  9. package/dist/components/Store/sections/StoreItemsSection.d.ts +1 -1
  10. package/dist/index.d.ts +5 -1
  11. package/dist/long-bow.cjs.development.js +1581 -193
  12. package/dist/long-bow.cjs.development.js.map +1 -1
  13. package/dist/long-bow.cjs.production.min.js +1 -1
  14. package/dist/long-bow.cjs.production.min.js.map +1 -1
  15. package/dist/long-bow.esm.js +1549 -165
  16. package/dist/long-bow.esm.js.map +1 -1
  17. package/dist/stories/Character/SkinSelectionGrid.stories.d.ts +1 -0
  18. package/dist/stories/Character/character/CharacterSkinSelectionModal.stories.d.ts +1 -5
  19. package/dist/stories/Features/store/MetadataCollector.stories.d.ts +1 -0
  20. package/package.json +2 -2
  21. package/src/components/Character/CharacterSkinSelectionModal.tsx +18 -71
  22. package/src/components/Character/SkinSelectionGrid.tsx +179 -0
  23. package/src/components/Store/CartView.tsx +66 -7
  24. package/src/components/Store/MetadataCollector.tsx +48 -0
  25. package/src/components/Store/Store.tsx +69 -7
  26. package/src/components/Store/StoreCharacterSkinRow.tsx +286 -0
  27. package/src/components/Store/StoreItemRow.tsx +1 -1
  28. package/src/components/Store/__test__/MetadataCollector.spec.tsx +228 -0
  29. package/src/components/Store/__test__/useStoreMetadata.spec.tsx +181 -0
  30. package/src/components/Store/hooks/useStoreCart.ts +89 -44
  31. package/src/components/Store/hooks/useStoreMetadata.ts +55 -0
  32. package/src/components/Store/sections/StoreItemsSection.tsx +30 -11
  33. package/src/index.tsx +6 -3
  34. package/src/stories/Character/SkinSelectionGrid.stories.tsx +106 -0
  35. package/src/stories/Character/character/CharacterSkinSelectionModal.stories.tsx +86 -25
  36. package/src/stories/Features/store/MetadataCollector.stories.tsx +94 -0
  37. package/src/stories/Features/store/Store.stories.tsx +103 -3
@@ -1,23 +1,30 @@
1
1
  import {
2
- ICartItem,
3
- IItemPack,
2
+ ICartItem as IBaseCartItem,
4
3
  IPurchase,
5
4
  IPurchaseUnit,
6
5
  IStoreItem,
7
- PurchaseType,
6
+ MetadataType,
7
+ PurchaseType
8
8
  } from '@rpg-engine/shared';
9
9
  import { useEffect, useRef, useState } from 'react';
10
+ import { useStoreMetadata } from './useStoreMetadata';
11
+
12
+ // Extend the base cart item to include metadata
13
+ interface ICartItem extends IBaseCartItem {
14
+ metadata?: Record<string, any>;
15
+ }
10
16
 
11
17
  interface IUseStoreCart {
12
18
  cartItems: ICartItem[];
13
19
  isCartOpen: boolean;
14
- handleAddToCart: (item: IStoreItem, quantity: number) => void;
20
+ handleAddToCart: (item: IStoreItem, quantity: number, metadata?: Record<string, any>) => void;
15
21
  handleRemoveFromCart: (itemKey: string) => void;
16
22
  handlePurchase: (onPurchase: (purchase: IPurchase) => void) => void;
17
23
  openCart: () => void;
18
24
  closeCart: () => void;
19
25
  getTotalItems: () => number;
20
26
  getTotalPrice: () => number;
27
+ isCollectingMetadata: boolean;
21
28
  }
22
29
 
23
30
  export const useStoreCart = (): IUseStoreCart => {
@@ -31,25 +38,62 @@ export const useStoreCart = (): IUseStoreCart => {
31
38
  };
32
39
  }, []);
33
40
 
34
- const handleAddToCart = (item: IStoreItem, quantity: number) => {
35
- setCartItems(prevItems => {
36
- const existingItem = prevItems.find(
37
- cartItem => cartItem.item.key === item.key
38
- );
39
-
40
- if (existingItem) {
41
- return prevItems.map(cartItem =>
42
- cartItem.item.key === item.key
43
- ? {
44
- ...cartItem,
45
- quantity: Math.min(cartItem.quantity + quantity, 99),
46
- }
47
- : cartItem
41
+ const { collectMetadata, isCollectingMetadata } = useStoreMetadata();
42
+
43
+ const handleAddToCart = async (item: IStoreItem, quantity: number, preselectedMetadata?: Record<string, any>) => {
44
+ // If metadata is already provided (from inline selection), use it directly
45
+ if (preselectedMetadata) {
46
+ setCartItems(prevItems => {
47
+ return [
48
+ ...prevItems,
49
+ {
50
+ item,
51
+ quantity,
52
+ metadata: preselectedMetadata,
53
+ },
54
+ ];
55
+ });
56
+ return;
57
+ }
58
+
59
+ // If item requires metadata but none was provided, collect it before adding to cart
60
+ if (item.metadataType && item.metadataType !== MetadataType.None) {
61
+ const metadata = await collectMetadata(item);
62
+ if (!metadata) return; // User cancelled
63
+
64
+ // Add item with metadata
65
+ setCartItems(prevItems => {
66
+ // Create new cart item with metadata
67
+ return [
68
+ ...prevItems,
69
+ {
70
+ item,
71
+ quantity,
72
+ metadata,
73
+ },
74
+ ];
75
+ });
76
+ } else {
77
+ // Existing add to cart logic for items without metadata
78
+ setCartItems(prevItems => {
79
+ const existingItem = prevItems.find(
80
+ cartItem => cartItem.item.key === item.key
48
81
  );
49
- }
50
82
 
51
- return [...prevItems, { item, quantity }];
52
- });
83
+ if (existingItem) {
84
+ return prevItems.map(cartItem =>
85
+ cartItem.item.key === item.key
86
+ ? {
87
+ ...cartItem,
88
+ quantity: Math.min(cartItem.quantity + quantity, 99),
89
+ }
90
+ : cartItem
91
+ );
92
+ }
93
+
94
+ return [...prevItems, { item, quantity }];
95
+ });
96
+ }
53
97
  };
54
98
 
55
99
  const handleRemoveFromCart = (itemKey: string) => {
@@ -59,28 +103,13 @@ export const useStoreCart = (): IUseStoreCart => {
59
103
  };
60
104
 
61
105
  const handlePurchase = (onPurchase: (purchase: IPurchase) => void) => {
62
- const purchaseUnits: IPurchaseUnit[] = cartItems.map(cartItem => {
63
- const isPack =
64
- 'priceUSD' in cartItem.item &&
65
- typeof cartItem.item.priceUSD === 'number';
66
-
67
- if (isPack) {
68
- const packItem = cartItem.item as IStoreItem & IItemPack;
69
- return {
70
- purchaseKey: packItem.key,
71
- qty: cartItem.quantity,
72
- type: PurchaseType.Pack,
73
- name: packItem.title,
74
- };
75
- }
76
-
77
- return {
78
- purchaseKey: cartItem.item.key,
79
- qty: cartItem.quantity,
80
- type: PurchaseType.Item,
81
- name: cartItem.item.name,
82
- };
83
- });
106
+ const purchaseUnits: IPurchaseUnit[] = cartItems.map(cartItem => ({
107
+ purchaseKey: cartItem.item.key,
108
+ qty: cartItem.quantity,
109
+ type: getPurchaseType(cartItem.item),
110
+ name: cartItem.item.name,
111
+ metadata: cartItem.metadata || cartItem.item.metadataConfig, // Use collected metadata if available
112
+ }));
84
113
 
85
114
  const purchase: IPurchase = {
86
115
  _id: uuidv4(),
@@ -122,9 +151,25 @@ export const useStoreCart = (): IUseStoreCart => {
122
151
  closeCart,
123
152
  getTotalItems,
124
153
  getTotalPrice,
154
+ isCollectingMetadata,
125
155
  };
126
156
  };
157
+
158
+ // Helper functions
159
+ function getPurchaseType(item: IStoreItem): PurchaseType {
160
+ // Check if the item comes from a pack based on naming convention or other property
161
+ if (item.key.startsWith('pack_')) {
162
+ return PurchaseType.Pack;
163
+ } else {
164
+ return PurchaseType.Item;
165
+ }
166
+ }
167
+
127
168
  function uuidv4(): string {
128
- throw new Error('Function not implemented.');
169
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
170
+ const r = (Math.random() * 16) | 0,
171
+ v = c === 'x' ? r : (r & 0x3) | 0x8;
172
+ return v.toString(16);
173
+ });
129
174
  }
130
175
 
@@ -0,0 +1,55 @@
1
+ import { IStoreItem, MetadataType } from "@rpg-engine/shared";
2
+ import { useState } from "react";
3
+
4
+ interface IUseStoreMetadata {
5
+ collectMetadata: (item: IStoreItem) => Promise<Record<string, any> | null>;
6
+ isCollectingMetadata: boolean;
7
+ }
8
+
9
+ export const useStoreMetadata = (): IUseStoreMetadata => {
10
+ const [isCollectingMetadata, setIsCollectingMetadata] = useState(false);
11
+
12
+ const collectMetadata = async (item: IStoreItem): Promise<Record<string, any> | null> => {
13
+ if (!item.metadataType || item.metadataType === MetadataType.None) {
14
+ return null;
15
+ }
16
+
17
+ setIsCollectingMetadata(true);
18
+
19
+ try {
20
+ // This is a promise-based approach that will be resolved when the MetadataCollector
21
+ // component calls the onCollect or onCancel callbacks
22
+ return await new Promise<Record<string, any> | null>((resolve) => {
23
+ // We'll store the resolver functions in a global context
24
+ // that will be accessible to the MetadataCollector component
25
+ window.__metadataResolvers = {
26
+ resolve: (metadata: Record<string, any> | null) => {
27
+ resolve(metadata);
28
+ },
29
+ item,
30
+ };
31
+ });
32
+ } finally {
33
+ setIsCollectingMetadata(false);
34
+ // Clean up the resolvers
35
+ if (window.__metadataResolvers) {
36
+ delete window.__metadataResolvers;
37
+ }
38
+ }
39
+ };
40
+
41
+ return {
42
+ collectMetadata,
43
+ isCollectingMetadata,
44
+ };
45
+ };
46
+
47
+ // Add TypeScript declaration for the global object
48
+ declare global {
49
+ interface Window {
50
+ __metadataResolvers?: {
51
+ resolve: (metadata: Record<string, any> | null) => void;
52
+ item: IStoreItem;
53
+ };
54
+ }
55
+ }
@@ -1,12 +1,13 @@
1
- import { IStoreItem, UserAccountTypes } from '@rpg-engine/shared';
1
+ import { IStoreItem, MetadataType, UserAccountTypes } from '@rpg-engine/shared';
2
2
  import React, { useState } from 'react';
3
3
  import { ScrollableContent } from '../../shared/ScrollableContent/ScrollableContent';
4
+ import { StoreCharacterSkinRow } from '../StoreCharacterSkinRow';
4
5
  import { StoreItemRow } from '../StoreItemRow';
5
6
 
6
7
 
7
8
  interface IStoreItemsSectionProps {
8
9
  items: IStoreItem[];
9
- onAddToCart: (item: IStoreItem, quantity: number) => void;
10
+ onAddToCart: (item: IStoreItem, quantity: number, metadata?: Record<string, any>) => void;
10
11
  atlasJSON: Record<string, any>;
11
12
  atlasIMG: string;
12
13
  userAccountType?: UserAccountTypes;
@@ -25,15 +26,33 @@ export const StoreItemsSection: React.FC<IStoreItemsSectionProps> = ({
25
26
  item.name.toLowerCase().includes(searchQuery.toLowerCase())
26
27
  );
27
28
 
28
- const renderStoreItem = (item: IStoreItem) => (
29
- <StoreItemRow
30
- item={item}
31
- atlasJSON={atlasJSON}
32
- atlasIMG={atlasIMG}
33
- onAddToCart={onAddToCart}
34
- userAccountType={userAccountType || UserAccountTypes.Free}
35
- />
36
- );
29
+ const renderStoreItem = (item: IStoreItem) => {
30
+ // Use the specialized character skin row for items with character skin metadata
31
+ if (item.metadataType === MetadataType.CharacterSkin) {
32
+ return (
33
+ <StoreCharacterSkinRow
34
+ key={item._id}
35
+ item={item}
36
+ atlasJSON={atlasJSON}
37
+ atlasIMG={atlasIMG}
38
+ onAddToCart={onAddToCart}
39
+ userAccountType={userAccountType || UserAccountTypes.Free}
40
+ />
41
+ );
42
+ }
43
+
44
+ // Use the standard item row for all other items
45
+ return (
46
+ <StoreItemRow
47
+ key={item._id}
48
+ item={item}
49
+ atlasJSON={atlasJSON}
50
+ atlasIMG={atlasIMG}
51
+ onAddToCart={onAddToCart}
52
+ userAccountType={userAccountType || UserAccountTypes.Free}
53
+ />
54
+ );
55
+ };
37
56
 
38
57
  return (
39
58
  <ScrollableContent
package/src/index.tsx CHANGED
@@ -42,6 +42,7 @@ export * from './components/NPCDialog/QuestionDialog/QuestionDialog';
42
42
  export * from './components/PartySystem';
43
43
  export * from './components/ProgressBar';
44
44
  export * from './components/PropertySelect/PropertySelect';
45
+ export * from './components/QuantitySelector/QuantitySelectorModal';
45
46
  export * from './components/Quests/QuestInfo/QuestInfo';
46
47
  export * from './components/Quests/QuestList';
47
48
  export * from './components/RadioButton';
@@ -55,6 +56,10 @@ export * from './components/SkillsContainer';
55
56
  export * from './components/SocialModal/SocialModal';
56
57
  export * from './components/Spellbook/Spellbook';
57
58
  export * from './components/Stepper';
59
+ export * from './components/Store/CartView';
60
+ export * from './components/Store/hooks/useStoreCart';
61
+ export * from './components/Store/MetadataCollector';
62
+ export * from './components/Store/Store';
58
63
  export * from './components/Table/Table';
59
64
  export * from './components/TextArea';
60
65
  export * from './components/TimeWidget/TimeWidget';
@@ -63,7 +68,5 @@ export * from './components/TradingMenu/TradingMenu';
63
68
  export * from './components/Truncate';
64
69
  export * from './components/Tutorial/TutorialStepper';
65
70
  export * from './components/typography/DynamicText';
66
-
67
- export * from './components/QuantitySelector/QuantitySelectorModal';
68
-
69
71
  export { useEventListener } from './hooks/useEventListener';
72
+
@@ -0,0 +1,106 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import React from 'react';
3
+ import { SkinSelectionGrid } from '../../components/Character/SkinSelectionGrid';
4
+ import { RPGUIRoot } from '../../components/RPGUI/RPGUIRoot';
5
+ import entitiesAtlasJSON from '../../mocks/atlas/entities/entities.json';
6
+ import entitiesAtlasIMG from '../../mocks/atlas/entities/entities.png';
7
+
8
+ /**
9
+ * SkinSelectionGrid stories showcasing the carousel-style character skin selection component
10
+ */
11
+ const meta = {
12
+ title: 'Character/SkinSelectionGrid',
13
+ component: SkinSelectionGrid,
14
+ parameters: {
15
+ layout: 'centered',
16
+ docs: {
17
+ description: {
18
+ component:
19
+ 'The SkinSelectionGrid component provides a carousel-style selection interface ' +
20
+ 'for character skins. Users can navigate through available skins using left and right arrows.',
21
+ },
22
+ },
23
+ },
24
+ decorators: [
25
+ Story => (
26
+ <RPGUIRoot>
27
+ <div style={{ width: '400px', background: '#333', padding: '20px', borderRadius: '5px' }}>
28
+ <Story />
29
+ </div>
30
+ </RPGUIRoot>
31
+ ),
32
+ ],
33
+ } satisfies Meta<typeof SkinSelectionGrid>;
34
+
35
+ export default meta;
36
+ type Story = StoryObj<typeof SkinSelectionGrid>;
37
+
38
+ // Sample character skins
39
+ const availableCharacters = [
40
+ {
41
+ id: 'black-knight',
42
+ name: 'Black Knight',
43
+ textureKey: 'black-knight',
44
+ },
45
+ {
46
+ id: 'dragon-knight',
47
+ name: 'Dragon Knight',
48
+ textureKey: 'dragon-knight',
49
+ },
50
+ {
51
+ id: 'senior-knight-1',
52
+ name: 'Senior Knight',
53
+ textureKey: 'senior-knight-1',
54
+ },
55
+ {
56
+ id: 'character-1',
57
+ name: 'Character 1',
58
+ textureKey: 'character-1',
59
+ },
60
+ {
61
+ id: 'character-2',
62
+ name: 'Character 2',
63
+ textureKey: 'character-2',
64
+ },
65
+ {
66
+ id: 'character-3',
67
+ name: 'Character 3',
68
+ textureKey: 'character-3',
69
+ },
70
+ {
71
+ id: 'character-4',
72
+ name: 'Character 4',
73
+ textureKey: 'character-4',
74
+ },
75
+ ];
76
+
77
+ /**
78
+ * Default story showing the carousel-style skin selection with multiple character options
79
+ * Users can navigate through the skins using left and right arrows
80
+ */
81
+ export const Default: Story = {
82
+ render: () => (
83
+ <SkinSelectionGrid
84
+ availableCharacters={availableCharacters}
85
+ initialSelectedSkin="black-knight"
86
+ onChange={(skinKey) => console.log('Selected skin:', skinKey)}
87
+ atlasJSON={entitiesAtlasJSON}
88
+ atlasIMG={entitiesAtlasIMG}
89
+ />
90
+ ),
91
+ };
92
+
93
+ /**
94
+ * Story showing the skin selection with only a few options
95
+ */
96
+ export const FewOptions: Story = {
97
+ render: () => (
98
+ <SkinSelectionGrid
99
+ availableCharacters={availableCharacters.slice(0, 3)}
100
+ initialSelectedSkin="dragon-knight"
101
+ onChange={(skinKey) => console.log('Selected skin:', skinKey)}
102
+ atlasJSON={entitiesAtlasJSON}
103
+ atlasIMG={entitiesAtlasIMG}
104
+ />
105
+ ),
106
+ };
@@ -1,52 +1,113 @@
1
- import { Meta, Story } from '@storybook/react';
1
+ import { Meta, StoryObj } from '@storybook/react';
2
2
  import React from 'react';
3
3
  import { RPGUIRoot } from '../../..';
4
4
  import {
5
- CharacterSkinSelectionModal,
6
- ICharacterSkinSelectionModalProps,
5
+ CharacterSkinSelectionModal
7
6
  } from '../../../components/Character/CharacterSkinSelectionModal';
8
7
  import atlasJSON from '../../../mocks/atlas/entities/entities.json';
9
8
  import atlasIMG from '../../../mocks/atlas/entities/entities.png';
10
9
 
11
- const meta: Meta = {
10
+ /**
11
+ * Character skin selection modal with carousel-style navigation.
12
+ * This allows users to browse through available character skins using arrow buttons.
13
+ */
14
+ const meta = {
12
15
  title: 'Character/Character/Skin Selection',
13
16
  component: CharacterSkinSelectionModal,
14
- };
17
+ parameters: {
18
+ layout: 'centered',
19
+ docs: {
20
+ description: {
21
+ component:
22
+ 'Character Skin Selection modal with carousel-style navigation. ' +
23
+ 'Users can browse through available skins using left and right arrows.',
24
+ },
25
+ },
26
+ },
27
+ decorators: [
28
+ Story => (
29
+ <RPGUIRoot>
30
+ <div style={{ background: '#333', padding: '10px', borderRadius: '5px' }}>
31
+ <Story />
32
+ </div>
33
+ </RPGUIRoot>
34
+ ),
35
+ ],
36
+ } satisfies Meta<typeof CharacterSkinSelectionModal>;
15
37
 
16
38
  export default meta;
39
+ type Story = StoryObj<typeof CharacterSkinSelectionModal>;
17
40
 
18
- const Template: Story<ICharacterSkinSelectionModalProps> = (
19
- args: ICharacterSkinSelectionModalProps
20
- ) => (
21
- <RPGUIRoot>
22
- <CharacterSkinSelectionModal {...args} />
23
- </RPGUIRoot>
24
- );
25
-
26
- export const KnightSkins = Template.bind({});
27
-
28
- // Example of different knight skins
29
- const knightCharacters = [
41
+ // Example of different character skins
42
+ const characterSkins = [
30
43
  {
44
+ id: 'black-knight',
31
45
  name: 'Black Knight',
32
46
  textureKey: 'black-knight',
33
47
  },
34
48
  {
49
+ id: 'dragon-knight',
35
50
  name: 'Dragon Knight',
36
51
  textureKey: 'dragon-knight',
37
52
  },
38
53
  {
54
+ id: 'senior-knight-1',
39
55
  name: 'Senior Knight',
40
56
  textureKey: 'senior-knight-1',
41
57
  },
58
+ {
59
+ id: 'character-1',
60
+ name: 'Character 1',
61
+ textureKey: 'character-1',
62
+ },
63
+ {
64
+ id: 'character-2',
65
+ name: 'Character 2',
66
+ textureKey: 'character-2',
67
+ },
68
+ {
69
+ id: 'character-3',
70
+ name: 'Character 3',
71
+ textureKey: 'character-3',
72
+ },
73
+ {
74
+ id: 'character-4',
75
+ name: 'Character 4',
76
+ textureKey: 'character-4',
77
+ },
42
78
  ];
43
79
 
44
- KnightSkins.args = {
45
- isOpen: true,
46
- onClose: () => console.log('Modal closed'),
47
- onConfirm: (textureKey: string) => console.log('Selected skin:', textureKey),
48
- availableCharacters: knightCharacters,
49
- atlasJSON: atlasJSON,
50
- atlasIMG: atlasIMG,
51
- initialSelectedSkin: 'black-knight',
80
+ /**
81
+ * Default story showing the character skin selection modal with multiple options.
82
+ * Users can navigate through the available skins using the left and right arrows.
83
+ */
84
+ export const Default: Story = {
85
+ render: () => (
86
+ <CharacterSkinSelectionModal
87
+ isOpen={true}
88
+ onClose={() => console.log('Modal closed')}
89
+ onConfirm={(textureKey: string) => console.log('Selected skin:', textureKey)}
90
+ availableCharacters={characterSkins}
91
+ atlasJSON={atlasJSON}
92
+ atlasIMG={atlasIMG}
93
+ initialSelectedSkin="black-knight"
94
+ />
95
+ ),
96
+ };
97
+
98
+ /**
99
+ * Story showing the skin selection modal with just a few options.
100
+ */
101
+ export const FewOptions: Story = {
102
+ render: () => (
103
+ <CharacterSkinSelectionModal
104
+ isOpen={true}
105
+ onClose={() => console.log('Modal closed')}
106
+ onConfirm={(textureKey: string) => console.log('Selected skin:', textureKey)}
107
+ availableCharacters={characterSkins.slice(0, 3)}
108
+ atlasJSON={atlasJSON}
109
+ atlasIMG={atlasIMG}
110
+ initialSelectedSkin="dragon-knight"
111
+ />
112
+ ),
52
113
  };
@@ -0,0 +1,94 @@
1
+ import { MetadataType } from '@rpg-engine/shared';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import React from 'react';
4
+ import { RPGUIRoot } from '../../../components/RPGUI/RPGUIRoot';
5
+ import { MetadataCollector } from '../../../components/Store/MetadataCollector';
6
+ import entitiesAtlasJSON from '../../../mocks/atlas/entities/entities.json';
7
+ import entitiesAtlasIMG from '../../../mocks/atlas/entities/entities.png';
8
+
9
+ /**
10
+ * MetadataCollector stories showcasing different use cases for collecting metadata
11
+ * during the purchase flow. This component is used when a purchase requires additional
12
+ * user input before completing the transaction.
13
+ */
14
+ const meta = {
15
+ title: 'Features/Store/MetadataCollector',
16
+ component: MetadataCollector,
17
+ parameters: {
18
+ layout: 'centered',
19
+ docs: {
20
+ description: {
21
+ component:
22
+ 'The MetadataCollector component handles collecting additional information from ' +
23
+ 'users during the purchase flow when needed. For example, when purchasing a ' +
24
+ 'character skin, the user needs to select which skin they want to use.',
25
+ },
26
+ },
27
+ },
28
+ decorators: [
29
+ Story => (
30
+ <RPGUIRoot>
31
+ <Story />
32
+ </RPGUIRoot>
33
+ ),
34
+ ],
35
+ } satisfies Meta<typeof MetadataCollector>;
36
+
37
+ export default meta;
38
+ type Story = StoryObj<typeof MetadataCollector>;
39
+
40
+ // Sample character skins
41
+ const availableCharacters = [
42
+ {
43
+ id: 'black-knight',
44
+ name: 'Black Knight',
45
+ textureKey: 'black-knight',
46
+ },
47
+ {
48
+ id: 'dragon-knight',
49
+ name: 'Dragon Knight',
50
+ textureKey: 'dragon-knight',
51
+ },
52
+ {
53
+ id: 'senior-knight-1',
54
+ name: 'Senior Knight',
55
+ textureKey: 'senior-knight-1',
56
+ },
57
+ ];
58
+
59
+ /**
60
+ * Story showing the Character Skin selection interface that appears
61
+ * when a user purchases a character skin item that requires selecting
62
+ * which skin to apply.
63
+ */
64
+ export const CharacterSkin: Story = {
65
+ render: () => (
66
+ <MetadataCollector
67
+ metadataType={MetadataType.CharacterSkin}
68
+ config={{
69
+ availableCharacters,
70
+ atlasJSON: entitiesAtlasJSON,
71
+ atlasIMG: entitiesAtlasIMG,
72
+ initialSelectedSkin: 'black-knight',
73
+ }}
74
+ onCollect={(metadata: Record<string, any>) => console.log('Metadata collected:', metadata)}
75
+ onCancel={() => console.log('Metadata collection cancelled')}
76
+ />
77
+ ),
78
+ };
79
+
80
+ /**
81
+ * Story showing the fallback for unhandled metadata types.
82
+ * This demonstrates the behavior when a metadata type is requested
83
+ * that hasn't been implemented yet.
84
+ */
85
+ export const Unhandled: Story = {
86
+ render: () => (
87
+ <MetadataCollector
88
+ metadataType={'unhandled-type' as MetadataType}
89
+ config={{}}
90
+ onCollect={() => console.log('Metadata collected')}
91
+ onCancel={() => console.log('Metadata collection cancelled')}
92
+ />
93
+ ),
94
+ };