ps99-api 2.4.0 → 2.6.0

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 (92) hide show
  1. package/.github/workflows/release-on-main.yml +1 -2
  2. package/.idea/runConfigurations/test_changing.xml +1 -1
  3. package/debug_currency.json +57 -0
  4. package/debug_goals.json +271 -0
  5. package/dist/ps99-api.d.ts +2 -0
  6. package/dist/ps99-api.js +4 -1
  7. package/dist/ps99-api.js.map +1 -1
  8. package/dist/request-client/axios.js +6 -1
  9. package/dist/request-client/axios.js.map +1 -1
  10. package/dist/responses/collection/index.d.ts +1 -0
  11. package/dist/responses/collection/index.js +15 -0
  12. package/dist/responses/collection/index.js.map +1 -1
  13. package/dist/responses/collection/rarity.d.ts +1 -0
  14. package/example-web/react/package-lock.json +1504 -1470
  15. package/example-web/react2/package-lock.json +3089 -2766
  16. package/example-web/react2/package.json +6 -1
  17. package/example-web/react2/public/assets/gold_variant_icon.png +0 -0
  18. package/example-web/react2/public/assets/hot_cocoa_egg.png +0 -0
  19. package/example-web/react2/public/index.html +34 -31
  20. package/example-web/react2/src/App.tsx +15 -15
  21. package/example-web/react2/src/assets/guild_placeholder.png +0 -0
  22. package/example-web/react2/src/components/AchievementsComponent.tsx +74 -19
  23. package/example-web/react2/src/components/AutoSizer.tsx +49 -0
  24. package/example-web/react2/src/components/BoostsComponent.tsx +16 -5
  25. package/example-web/react2/src/components/BoothsComponent.tsx +22 -52
  26. package/example-web/react2/src/components/BoxesComponent.tsx +48 -16
  27. package/example-web/react2/src/components/BuffsComponent.tsx +82 -34
  28. package/example-web/react2/src/components/CharmsComponent.tsx +84 -24
  29. package/example-web/react2/src/components/CollectionConfigIndex.tsx +867 -33
  30. package/example-web/react2/src/components/CollectionsIndex.tsx +380 -27
  31. package/example-web/react2/src/components/CollectionsLayout.tsx +60 -0
  32. package/example-web/react2/src/components/CurrencyComponent.tsx +57 -39
  33. package/example-web/react2/src/components/DynamicCollectionConfigData.tsx +172 -15
  34. package/example-web/react2/src/components/EggsComponent.tsx +50 -12
  35. package/example-web/react2/src/components/EnchantsComponent.tsx +88 -42
  36. package/example-web/react2/src/components/FishingRodsComponent.tsx +36 -22
  37. package/example-web/react2/src/components/Footer.tsx +18 -8
  38. package/example-web/react2/src/components/FruitsComponent.tsx +40 -17
  39. package/example-web/react2/src/components/GenericFetchComponent.tsx +9 -1
  40. package/example-web/react2/src/components/GuildBattlesComponent.tsx +41 -34
  41. package/example-web/react2/src/components/Header.tsx +39 -52
  42. package/example-web/react2/src/components/HomePage.tsx +15 -17
  43. package/example-web/react2/src/components/HoverboardsComponent.tsx +23 -99
  44. package/example-web/react2/src/components/ImageComponent.tsx +255 -45
  45. package/example-web/react2/src/components/ItemCard.tsx +240 -0
  46. package/example-web/react2/src/components/LootboxesComponent.tsx +22 -7
  47. package/example-web/react2/src/components/MasteryComponent.tsx +165 -30
  48. package/example-web/react2/src/components/MerchantsComponent.tsx +41 -16
  49. package/example-web/react2/src/components/MiscItemsComponent.tsx +26 -31
  50. package/example-web/react2/src/components/PetsComponent.tsx +100 -61
  51. package/example-web/react2/src/components/PotionsComponent.tsx +121 -27
  52. package/example-web/react2/src/components/RandomEventsComponent.tsx +32 -23
  53. package/example-web/react2/src/components/RanksComponent.tsx +187 -62
  54. package/example-web/react2/src/components/RarityComponent.tsx +123 -5
  55. package/example-web/react2/src/components/ReactWindowMock.tsx +73 -0
  56. package/example-web/react2/src/components/RebirthsComponent.tsx +36 -19
  57. package/example-web/react2/src/components/SecretRoomsComponent.tsx +5 -4
  58. package/example-web/react2/src/components/SeedsComponent.tsx +41 -21
  59. package/example-web/react2/src/components/ShovelsComponent.tsx +21 -9
  60. package/example-web/react2/src/components/Sidebar.tsx +105 -0
  61. package/example-web/react2/src/components/SprinklersComponent.tsx +25 -10
  62. package/example-web/react2/src/components/Tooltip.tsx +36 -0
  63. package/example-web/react2/src/components/UltimatesComponent.tsx +28 -16
  64. package/example-web/react2/src/components/UpgradesComponent.tsx +97 -47
  65. package/example-web/react2/src/components/WateringCansComponent.tsx +20 -14
  66. package/example-web/react2/src/components/WorldsComponent.tsx +21 -11
  67. package/example-web/react2/src/components/XPPotionsComponent.tsx +28 -11
  68. package/example-web/react2/src/components/ZoneFlagsComponent.tsx +25 -14
  69. package/example-web/react2/src/components/ZonesComponent.tsx +43 -60
  70. package/example-web/react2/src/constants/collectionIcons.ts +29 -0
  71. package/example-web/react2/src/context/CollectionDataContext.tsx +62 -0
  72. package/example-web/react2/src/context/ScrollContext.tsx +35 -0
  73. package/example-web/react2/src/hooks/useCollapsibleHeader.ts +69 -0
  74. package/example-web/react2/src/hooks/useExpandableList.ts +38 -0
  75. package/example-web/react2/src/hooks/useItemResolution.ts +351 -0
  76. package/example-web/react2/src/index.css +257 -0
  77. package/example-web/react2/src/index.tsx +2 -1
  78. package/example-web/react2/src/utils/gigantix.ts +40 -0
  79. package/example-web/react2/temp_model.rbxm +0 -0
  80. package/example-web/react2/webpack.config.js +103 -47
  81. package/package.json +11 -11
  82. package/ranks.json +1 -0
  83. package/repro_collection_fetch.ts +33 -0
  84. package/repro_image_fetch.ts +50 -0
  85. package/src/__tests__/__snapshots__/ps99-api-changes.ts.snap +34841 -10439
  86. package/src/__tests__/__snapshots__/ps99-api-live.ts.snap +160667 -67217
  87. package/src/ps99-api.ts +9 -5
  88. package/src/request-client/axios.ts +6 -2
  89. package/src/responses/collection/index.ts +1 -0
  90. package/src/responses/collection/rarity.ts +1 -0
  91. package/tsconfig.json +1 -1
  92. package/example-web/react2/public/service-worker.js +0 -63
@@ -1,46 +1,140 @@
1
1
  import React from "react";
2
2
  import { CollectionConfigData } from "ps99-api";
3
+ import ItemCard from "./ItemCard";
3
4
  import ImageComponent from "./ImageComponent";
5
+ import { useItemResolution } from "../hooks/useItemResolution";
6
+ import { formatGigantix } from "../utils/gigantix";
4
7
 
5
8
  const PotionsComponent: React.FC<{
6
9
  configData: CollectionConfigData<"Potions">;
7
10
  }> = ({ configData }) => {
11
+ const { getRarityColor } = useItemResolution();
12
+
13
+ const isSingleTier = configData.Tiers.length === 1;
14
+
15
+ if (isSingleTier) {
16
+ const tier = configData.Tiers[0];
17
+ const rarityColor = tier.Rarity ? getRarityColor(tier.Rarity) : null;
18
+
19
+ return (
20
+ <div style={{ width: '100%', height: '100%', boxSizing: 'border-box', display: 'flex', flexDirection: 'column', alignItems: 'center', paddingTop: '20px' }}>
21
+ <div style={{
22
+ width: '120px',
23
+ height: '120px',
24
+ borderRadius: '16px',
25
+ border: rarityColor ? `4px solid ${rarityColor}` : '4px solid #eee',
26
+ padding: '10px',
27
+ backgroundColor: '#fff',
28
+ boxShadow: '0 4px 10px rgba(0,0,0,0.1)',
29
+ display: 'flex',
30
+ alignItems: 'center',
31
+ justifyContent: 'center',
32
+ marginBottom: '20px'
33
+ }}>
34
+ <ImageComponent src={tier.Icon} alt={tier.DisplayName} style={{ width: '100%', height: '100%', objectFit: 'contain' }} />
35
+ </div>
36
+
37
+ <div style={{ textAlign: 'center', maxWidth: '400px' }}>
38
+ <div style={{ display: 'flex', gap: '15px', justifyContent: 'center', marginBottom: '10px', fontSize: '0.9em' }}>
39
+ <p><strong>Primary:</strong> <span style={{ color: configData.PrimaryColor }}>{configData.PrimaryColor}</span></p>
40
+ <p><strong>Secondary:</strong> <span style={{ color: configData.SecondaryColor }}>{configData.SecondaryColor}</span></p>
41
+ </div>
42
+ {configData.BaseTier !== 1 && <p><strong>Base Tier:</strong> {configData.BaseTier}</p>}
43
+ {configData.MaxTier !== 1 && <p><strong>Max Tier:</strong> {configData.MaxTier}</p>}
44
+
45
+ <h3 style={{ margin: '10px 0', fontSize: '1.5em', color: '#333' }}>{tier.DisplayName}</h3>
46
+ <p style={{ color: '#666', fontSize: '1.1em', marginBottom: '15px' }}>{tier.Desc}</p>
47
+ <div style={{ fontWeight: '900', fontSize: '1.2em', color: '#333' }}>
48
+ <p>Power: {tier.Power}</p>
49
+ <p>Time: {tier.Time}s</p>
50
+ </div>
51
+ </div>
52
+ </div>
53
+ );
54
+ }
55
+
8
56
  return (
9
- <div style={{ padding: "1em", border: "1px solid #ccc", borderRadius: "8px", backgroundColor: "#f9f9f9" }}>
10
- <h2 style={{ borderBottom: "2px solid #ccc", paddingBottom: "0.5em" }}>
11
- Potion: {configData.Tiers[0].DisplayName}
12
- </h2>
13
- <div style={{ display: 'flex', gap: '1em' }}>
14
- <div style={{ minWidth: '250px' }}>
57
+
58
+ <div style={{ width: '100%', height: '100%', boxSizing: 'border-box' }}>
59
+
60
+ <div style={{ display: 'flex', gap: '20px', alignItems: 'center', marginBottom: '20px', flexWrap: 'wrap', justifyContent: 'center' }}>
61
+ <div style={{ width: '150px', height: '150px' }}>
15
62
  <ImageComponent
16
63
  src={configData.Tiers[0].Icon}
17
64
  alt={configData.Tiers[0].DisplayName}
18
65
  />
19
66
  </div>
20
- <div>
21
- <p><strong>Description:</strong> {configData.Tiers[0].Desc}</p>
22
- <p><strong>Primary Color:</strong> {configData.PrimaryColor}</p>
23
- <p><strong>Secondary Color:</strong> {configData.SecondaryColor}</p>
24
- <p><strong>Base Tier:</strong> {configData.BaseTier}</p>
25
- <p><strong>Max Tier:</strong> {configData.MaxTier}</p>
67
+ <div style={{ textAlign: 'left', flex: 1, minWidth: '200px' }}>
68
+ <p style={{ marginBottom: '8px' }}><strong>Description:</strong> {configData.Tiers[0].Desc}</p>
69
+ <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '8px' }}>
70
+ <p><strong>Primary:</strong> <span style={{ color: configData.PrimaryColor }}>{configData.PrimaryColor}</span></p>
71
+ <p><strong>Secondary:</strong> <span style={{ color: configData.SecondaryColor }}>{configData.SecondaryColor}</span></p>
72
+ <p><strong>Base Tier:</strong> {configData.BaseTier}</p>
73
+ <p><strong>Max Tier:</strong> {configData.MaxTier}</p>
74
+ </div>
26
75
  </div>
27
76
  </div>
28
- <div style={{ marginTop: '1em' }}>
29
- <h3>Tiers:</h3>
30
- <div style={{ display: 'flex', flexWrap: 'wrap', gap: '1em' }}>
31
- {configData.Tiers.map((tier, index) => (
32
- <div key={index} style={{ border: '1px solid #ccc', borderRadius: '8px', padding: '1em', flex: '1 1 calc(33% - 1em)', boxSizing: 'border-box' }}>
33
- <p><strong>Tier {index + 1}:</strong></p>
34
- <p><strong>Display Name:</strong> {tier.DisplayName}</p>
35
- <p><strong>Description:</strong> {tier.Desc}</p>
36
- <div style={{ minWidth: '250px' }}>
37
- <ImageComponent src={tier.Icon} alt={tier.DisplayName} />
77
+
78
+ <div style={{ marginTop: '20px' }}>
79
+ <h3>Tiers</h3>
80
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>
81
+ {configData.Tiers.map((tier, index) => {
82
+ const rarityColor = tier.Rarity ? getRarityColor(tier.Rarity) : '#eee';
83
+ return (
84
+ <div key={index} style={{
85
+ display: 'flex',
86
+ alignItems: 'center',
87
+ padding: '10px 15px',
88
+ background: '#fff',
89
+ borderRadius: '12px',
90
+ border: `1px solid ${rarityColor}`,
91
+ borderLeftWidth: '6px',
92
+ boxShadow: '0 2px 4px rgba(0,0,0,0.03)'
93
+ }}>
94
+ {/* Icon */}
95
+ <div style={{
96
+ width: '48px',
97
+ height: '48px',
98
+ minWidth: '48px',
99
+ marginRight: '15px',
100
+ borderRadius: '8px',
101
+ background: '#f5f5f5',
102
+ padding: '4px',
103
+ display: 'flex',
104
+ alignItems: 'center',
105
+ justifyContent: 'center'
106
+ }}>
107
+ <ImageComponent
108
+ src={tier.Icon}
109
+ alt={tier.DisplayName}
110
+ style={{ width: '100%', height: '100%', objectFit: 'contain' }}
111
+ />
112
+ </div>
113
+
114
+ {/* Info */}
115
+ <div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>
116
+ <div style={{ fontWeight: 'bold', fontSize: '1.1em', color: '#333' }}>
117
+ {tier.DisplayName}
118
+ </div>
119
+ <div style={{ fontSize: '0.85em', color: '#888' }}>
120
+ Tier {index + 1}
121
+ </div>
122
+ </div>
123
+
124
+ {/* Stats */}
125
+ <div style={{ display: 'flex', gap: '20px', alignItems: 'center' }}>
126
+ <div style={{ textAlign: 'right' }}>
127
+ <div style={{ fontSize: '0.8em', color: '#999', textTransform: 'uppercase', fontWeight: '600' }}>Power</div>
128
+ <div style={{ fontWeight: 'bold', color: '#1976d2' }}>{formatGigantix(tier.Power)}%</div>
129
+ </div>
130
+ <div style={{ textAlign: 'right' }}>
131
+ <div style={{ fontSize: '0.8em', color: '#999', textTransform: 'uppercase', fontWeight: '600' }}>Time</div>
132
+ <div style={{ fontWeight: 'bold', color: '#333' }}>{formatGigantix(tier.Time)}s</div>
133
+ </div>
134
+ </div>
38
135
  </div>
39
- <p><strong>Power:</strong> {tier.Power}</p>
40
- <p><strong>Time:</strong> {tier.Time}</p>
41
- <p><strong>Rarity:</strong> {tier.Rarity.DisplayName}</p>
42
- </div>
43
- ))}
136
+ );
137
+ })}
44
138
  </div>
45
139
  </div>
46
140
  </div>
@@ -1,38 +1,47 @@
1
1
  import React from "react";
2
2
  import { CollectionConfigData } from "ps99-api";
3
- import ImageComponent from "./ImageComponent";
3
+ import ItemCard from "./ItemCard";
4
4
 
5
5
  const RandomEventsComponent: React.FC<{
6
6
  configData: CollectionConfigData<"RandomEvents">;
7
7
  }> = ({ configData }) => {
8
8
  return (
9
- <div>
10
- <h2>Random Event: {configData.Name}</h2>
11
- <div>
12
- <ImageComponent src={configData.Icon} alt={configData.Name} />
13
- <p>Color: {configData.Color}</p>
14
- <p>Duration: {configData.Duration} seconds</p>
15
- <p>Breaking Requirement: {configData.BreakingRequirement}</p>
16
- <p>Playtime Requirement: {configData.PlaytimeRequirement} minutes</p>
17
- <p>Chance: {configData.Chance}</p>
18
- <p>Allow in Zones: {configData.AllowInZones ? "Yes" : "No"}</p>
19
- <p>Allow in Instances: {configData.AllowInInstances ? "Yes" : "No"}</p>
20
- <p>Allow Multiple: {configData.AllowMultiple ? "Yes" : "No"}</p>
21
- {configData.MinimumZone && (
22
- <p>Minimum Zone: {configData.MinimumZone}</p>
23
- )}
9
+ <div style={{ width: '100%', height: '100%', boxSizing: 'border-box' }}>
10
+
11
+ <div style={{ maxWidth: '300px', margin: '0 auto 15px auto' }}>
12
+ <ItemCard
13
+ id={configData.Name}
14
+ amount={1}
15
+ label={configData.Name}
16
+ itemData={{
17
+ icon: configData.Icon,
18
+ rarity: undefined,
19
+ name: configData.Name
20
+ }}
21
+ rarityColor={configData.Color}
22
+ />
23
+ </div>
24
+
25
+ <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '10px', marginBottom: '20px', textAlign: 'left' }}>
26
+ <p><strong>Color:</strong> <span style={{ color: configData.Color }}>{configData.Color}</span></p>
27
+ <p><strong>Duration:</strong> {configData.Duration}s</p>
28
+ <p><strong>Chance:</strong> {configData.Chance}</p>
29
+ {configData.BreakingRequirement && <p><strong>Break Req:</strong> {configData.BreakingRequirement}</p>}
24
30
  </div>
25
- <div>
26
- <h3>Area Whitelist:</h3>
27
- <ul>
31
+
32
+ <div style={{ borderTop: '1px solid #eee', paddingTop: '15px' }}>
33
+ <h3 style={{ fontSize: '1.1em', marginBottom: '10px' }}>Area Whitelist</h3>
34
+ <div style={{ display: 'flex', flexWrap: 'wrap', gap: '5px', justifyContent: 'center' }}>
28
35
  {Object.entries(configData.AreaWhitelist).map(
29
36
  ([area, allowed], index) => (
30
- <li key={index}>
31
- {area.replace("_", " ")}: {allowed ? "Yes" : "No"}
32
- </li>
37
+ allowed && (
38
+ <span key={index} className="badge">
39
+ {area.replace(/_/g, " ")}
40
+ </span>
41
+ )
33
42
  ),
34
43
  )}
35
- </ul>
44
+ </div>
36
45
  </div>
37
46
  </div>
38
47
  );
@@ -1,78 +1,203 @@
1
1
  import React from "react";
2
2
  import { CollectionConfigData } from "ps99-api";
3
3
 
4
+ import { useItemResolution } from "../hooks/useItemResolution";
5
+ import ItemCard from "./ItemCard";
6
+ import { useExpandableList } from "../hooks/useExpandableList";
7
+
4
8
  const RanksComponent: React.FC<{
5
9
  configData: CollectionConfigData<"Ranks">;
6
10
  }> = ({ configData }) => {
7
- return (
8
- <div>
9
- <h2>Rank: {configData.Title}</h2>
10
- <p>Rank Number: {configData.RankNumber}</p>
11
- <p>Max Enchants Equipped: {configData.MaxEnchantsEquipped}</p>
12
- <p>Maximum Active Goals: {configData.MaximumActiveGoals}</p>
13
- <p>Unlockable Egg Slots: {configData.UnlockableEggSlots}</p>
14
- <p>Unlockable Pet Slots: {configData.UnlockablePetSlots}</p>
15
- {configData.RequiredRebirth && (
16
- <p>Required Rebirth: {configData.RequiredRebirth}</p>
17
- )}
18
- {configData.RequiredZone && (
19
- <p>Required Zone: {configData.RequiredZone}</p>
20
- )}
11
+ const { loading, resolveItem, getRarityColor } = useItemResolution();
12
+ const goalsLength = React.useMemo(() => Array.isArray(configData.Goals) ? configData.Goals.length : 0, [configData.Goals]);
13
+ const { expandedIndices, toggle, expandAll, collapseAll, isExpanded } = useExpandableList(goalsLength);
21
14
 
22
- <div>
23
- <h3>Goals:</h3>
24
- {configData.Goals.map((goalSet, index) => (
25
- <div key={index}>
26
- <h4>Goal Set {index + 1}</h4>
27
- <ul>
28
- {goalSet.map((goal, goalIndex) => (
29
- <li key={goalIndex}>
30
- <p>Type: {goal.Type}</p>
31
- <p>Amount: {goal.Amount}</p>
32
- <p>Weight: {goal.Weight}</p>
33
- {goal.CurrencyID && <p>Currency ID: {goal.CurrencyID}</p>}
34
- {goal.BreakableType && (
35
- <p>Breakable Type: {goal.BreakableType}</p>
36
- )}
37
- {goal.PotionTier && <p>Potion Tier: {goal.PotionTier}</p>}
38
- {goal.EnchantTier && <p>Enchant Tier: {goal.EnchantTier}</p>}
39
- </li>
40
- ))}
41
- </ul>
42
- </div>
43
- ))}
15
+ if (loading) {
16
+ return (
17
+ <div style={{
18
+ display: "flex",
19
+ justifyContent: "center",
20
+ alignItems: "center",
21
+ height: "100vh",
22
+ fontSize: "1.5rem",
23
+ color: "#888"
24
+ }}>
25
+ Loading reference data...
44
26
  </div>
27
+ );
28
+ }
45
29
 
46
- <div>
47
- <h3>Rewards:</h3>
48
- <ul>
49
- {configData.Rewards.map((reward, rewardIndex) => (
50
- <li key={rewardIndex}>
51
- <p>Stars Required: {reward.StarsRequired}</p>
52
- <p>Reward Item ID: {reward.Item._data.id}</p>
53
- {reward.Item._data._am && <p>Amount: {reward.Item._data._am}</p>}
54
- {reward.Item._data.tn && <p>TN: {reward.Item._data.tn}</p>}
55
- </li>
56
- ))}
57
- </ul>
58
- </div>
30
+ /* renderItemCard logic moved to ItemCard.tsx */
59
31
 
60
- {configData.RankUpRewards && (
61
- <div>
62
- <h3>Rank Up Rewards:</h3>
63
- <ul>
64
- {configData.RankUpRewards.map((reward, rewardIndex) => (
65
- <li key={rewardIndex}>
66
- <p>Reward Item ID: {reward._data.id}</p>
67
- {reward._data._am && <p>Amount: {reward._data._am}</p>}
68
- {reward._data.tn && <p>TN: {reward._data.tn}</p>}
69
- </li>
70
- ))}
71
- </ul>
32
+ return (
33
+ <div style={{ width: "100%", height: "100%", boxSizing: "border-box" }}>
34
+ <div style={{ width: "100%" }}>
35
+
36
+ {/* Info Grid */}
37
+ <div style={{
38
+ display: "grid",
39
+ gridTemplateColumns: "repeat(auto-fit, minmax(180px, 1fr))",
40
+ gap: "16px",
41
+ marginBottom: "60px",
42
+ background: "#ffffff",
43
+ padding: "24px",
44
+ borderRadius: "20px",
45
+ boxShadow: "0 4px 20px rgba(0,0,0,0.05)"
46
+ }}>
47
+ <InfoItem label="Max Enchants" value={configData.MaxEnchantsEquipped} />
48
+ <InfoItem label="Max Goals" value={configData.MaximumActiveGoals} />
49
+ <InfoItem label="Egg Slots" value={configData.UnlockableEggSlots} />
50
+ <InfoItem label="Pet Slots" value={configData.UnlockablePetSlots} />
51
+ {configData.RequiredRebirth && <InfoItem label="Rebirth Req" value={configData.RequiredRebirth} />}
52
+ {configData.RequiredZone && <InfoItem label="Zone Req" value={configData.RequiredZone} />}
72
53
  </div>
73
- )}
54
+
55
+ {/* Rewards Section */}
56
+ {configData.Rewards && configData.Rewards.length > 0 && (
57
+ <div style={{ marginBottom: "60px" }}>
58
+ <SectionTitle title="Rewards" />
59
+ <div style={{
60
+ display: "grid",
61
+ gridTemplateColumns: "repeat(auto-fill, minmax(200px, 1fr))",
62
+ gap: "24px"
63
+ }}>
64
+ {configData.Rewards.map((reward: any, i: number) => {
65
+ // Handle inconsistent reward structure if necessary
66
+ const data = reward.Item?._data || reward.Item || reward._data || reward;
67
+ const id = data.id || data._id || "Reward";
68
+ const amount = data._am || data.Amount || 1;
69
+ const tn = data.tn || data.Tier;
70
+ const itemData = resolveItem(id, tn);
71
+ return (
72
+ <ItemCard
73
+ key={`${id}-${tn}-${i}`}
74
+ id={id}
75
+ amount={amount}
76
+ label={id}
77
+ tn={tn}
78
+ itemData={itemData}
79
+ rarityColor={itemData?.rarity ? getRarityColor(itemData.rarity) : null}
80
+ />
81
+ );
82
+ })}
83
+ </div>
84
+ </div>
85
+ )}
86
+
87
+ {/* Goals Section */}
88
+ {configData.Goals && (
89
+ <div>
90
+ <SectionTitle title="Goals" />
91
+
92
+ <div style={{ marginBottom: '20px', display: 'flex', gap: '10px', justifyContent: 'center' }}>
93
+ <button onClick={expandAll} style={{ padding: '8px 16px', borderRadius: '8px', border: '1px solid #ddd', background: '#fff', cursor: 'pointer' }}>Expand All</button>
94
+ <button onClick={collapseAll} style={{ padding: '8px 16px', borderRadius: '8px', border: '1px solid #ddd', background: '#fff', cursor: 'pointer' }}>Collapse All</button>
95
+ </div>
96
+
97
+ <div style={{ display: "flex", flexDirection: "column", gap: "20px" }}>
98
+ {Array.isArray(configData.Goals) ? configData.Goals.map((goalSet: any[], setIndex: number) => (
99
+ <div key={setIndex} style={{
100
+ background: "#fff",
101
+ borderRadius: "24px",
102
+ padding: "30px",
103
+ boxShadow: "0 2px 10px rgba(0,0,0,0.03)"
104
+ }}>
105
+ <div
106
+ onClick={() => toggle(setIndex)}
107
+ style={{
108
+ display: "flex",
109
+ alignItems: "center",
110
+ gap: "12px",
111
+ cursor: "pointer",
112
+ marginBottom: isExpanded(setIndex) ? "24px" : "0"
113
+ }}
114
+ >
115
+ <div style={{
116
+ background: "#ffcc00",
117
+ width: "8px",
118
+ height: "32px",
119
+ borderRadius: "4px"
120
+ }} />
121
+ <h3 style={{
122
+ fontSize: "1.5rem",
123
+ color: "#444",
124
+ fontWeight: "700",
125
+ margin: 0
126
+ }}>
127
+ {isExpanded(setIndex) ? '▼' : '▶'} Goal Set {setIndex + 1}
128
+ </h3>
129
+ </div>
130
+
131
+ {isExpanded(setIndex) && (
132
+ <div style={{
133
+ display: "grid",
134
+ gridTemplateColumns: "repeat(auto-fill, minmax(180px, 1fr))",
135
+ gap: "20px"
136
+ }}>
137
+ {goalSet.map((goal: any, index: number) => {
138
+ // Determine ID and Label
139
+ // Goals usually have Type (numeric) which we need to resolve
140
+ // Sometimes they might have CurrencyID
141
+ const id = String(goal.CurrencyID || goal.Type);
142
+ const tn = goal.EnchantTier || goal.PotionTier || goal.Tier;
143
+ const itemData = resolveItem(id, tn);
144
+ // Label can be tricky, resolveItem will give us data, but for fallback prompt we use ID
145
+ return (
146
+ <ItemCard
147
+ key={`${id}-${tn}-${index}`}
148
+ id={id}
149
+ amount={goal.Amount}
150
+ label={id}
151
+ tn={tn}
152
+ weight={goal.Weight}
153
+ typeId={goal.Type}
154
+ itemData={itemData}
155
+ rarityColor={itemData?.rarity ? getRarityColor(itemData.rarity) : null}
156
+ />
157
+ );
158
+ })}
159
+ </div>
160
+ )}
161
+ </div>
162
+ )) : (
163
+ <div>No goals structure found</div>
164
+ )}
165
+ </div>
166
+ </div>
167
+ )}
168
+ </div>
74
169
  </div>
75
170
  );
76
171
  };
77
172
 
173
+ // Helper Components
174
+ const InfoItem = ({ label, value }: { label: string, value: any }) => (
175
+ <div style={{ display: "flex", flexDirection: "column", alignItems: "center" }}>
176
+ <span style={{ fontSize: "12px", color: "#999", textTransform: "uppercase", fontWeight: "700" }}>{label}</span>
177
+ <span style={{ fontSize: "18px", color: "#333", fontWeight: "800" }}>{typeof value === 'number' ? value.toLocaleString() : value}</span>
178
+ </div>
179
+ );
180
+
181
+ const SectionTitle = ({ title }: { title: string }) => (
182
+ <h2 style={{
183
+ fontSize: "2.2rem",
184
+ color: "#444",
185
+ marginBottom: "30px",
186
+ textAlign: "center",
187
+ position: "relative",
188
+ display: "inline-block",
189
+ left: "50%",
190
+ transform: "translateX(-50%)"
191
+ }}>
192
+ {title}
193
+ <div style={{
194
+ width: "60px",
195
+ height: "4px",
196
+ background: "#ffcc00",
197
+ borderRadius: "2px",
198
+ margin: "10px auto 0"
199
+ }} />
200
+ </h2>
201
+ );
202
+
78
203
  export default RanksComponent;
@@ -4,14 +4,132 @@ import { CollectionConfigData } from "ps99-api";
4
4
  const RarityComponent: React.FC<{
5
5
  configData: CollectionConfigData<"Rarity">;
6
6
  }> = ({ configData }) => {
7
+ // Use the rarity color for styling
8
+ const rarityColor = configData.Color || "#333";
9
+ const glowColor = rarityColor;
10
+
7
11
  return (
8
- <div>
9
- <h2>Rarity: {configData.DisplayName}</h2>
10
- <p>Rarity Number: {configData.RarityNumber}</p>
11
- <p>Color: {configData.Color}</p>
12
- <p>Announce: {configData.Announce ? "Yes" : "No"}</p>
12
+ <div style={{
13
+ padding: "40px",
14
+ fontFamily: "'Nunito', 'Segoe UI', sans-serif",
15
+ backgroundColor: "#f9f9f9",
16
+ minHeight: "100vh",
17
+ display: "flex",
18
+ flexDirection: "column",
19
+ alignItems: "center"
20
+ }}>
21
+
22
+ {/* Rarity Showcase Card */}
23
+ <div style={{
24
+ background: "#fff",
25
+ borderRadius: "24px",
26
+ padding: "40px",
27
+ boxShadow: `0 10px 40px ${glowColor}40`,
28
+ border: `3px solid ${rarityColor}`,
29
+ maxWidth: "600px",
30
+ width: "100%",
31
+ textAlign: "center",
32
+ marginBottom: "40px",
33
+ position: "relative",
34
+ overflow: "hidden",
35
+ transition: "transform 0.3s ease",
36
+ cursor: "default"
37
+ }}
38
+ onMouseEnter={(e) => {
39
+ e.currentTarget.style.transform = "scale(1.02)";
40
+ }}
41
+ onMouseLeave={(e) => {
42
+ e.currentTarget.style.transform = "scale(1)";
43
+ }}
44
+ >
45
+ {/* Background Glow */}
46
+ <div style={{
47
+ position: "absolute",
48
+ top: "-50%",
49
+ left: "50%",
50
+ transform: "translateX(-50%)",
51
+ width: "150%",
52
+ height: "300px",
53
+ background: `radial-gradient(circle, ${rarityColor}33 0%, transparent 70%)`,
54
+ pointerEvents: "none",
55
+ zIndex: 0
56
+ }} />
57
+
58
+ <div style={{ position: "relative", zIndex: 1 }}>
59
+ <h1 style={{
60
+ fontSize: "3.5rem",
61
+ fontWeight: "900",
62
+ color: rarityColor,
63
+ margin: "0 0 10px 0",
64
+ textShadow: `0 2px 10px ${glowColor}60`
65
+ }}>
66
+ {configData.DisplayName}
67
+ </h1>
68
+ <div style={{
69
+ fontSize: "1.2rem",
70
+ color: "#888",
71
+ fontWeight: "700",
72
+ textTransform: "uppercase",
73
+ letterSpacing: "2px"
74
+ }}>
75
+ Rarity #{configData.RarityNumber}
76
+ </div>
77
+ </div>
78
+ </div>
79
+
80
+ {/* Exhaustive Data Grid */}
81
+ <div style={{
82
+ display: "grid",
83
+ gridTemplateColumns: "repeat(auto-fit, minmax(200px, 1fr))",
84
+ gap: "20px",
85
+ maxWidth: "800px",
86
+ width: "100%"
87
+ }}>
88
+ {Object.entries(configData).map(([key, value]) => {
89
+ // Skip rendering complex objects if intended for simple display, or stringify them
90
+ if (typeof value === 'object' && value !== null) {
91
+ return (
92
+ <DataCard key={key} label={key} value={JSON.stringify(value)} color={rarityColor} />
93
+ );
94
+ }
95
+ return (
96
+ <DataCard key={key} label={key} value={String(value)} color={rarityColor} />
97
+ );
98
+ })}
99
+ </div>
100
+
13
101
  </div>
14
102
  );
15
103
  };
16
104
 
105
+ const DataCard = ({ label, value, color }: { label: string, value: string, color: string }) => (
106
+ <div style={{
107
+ background: "#fff",
108
+ padding: "20px",
109
+ borderRadius: "16px",
110
+ boxShadow: "0 4px 15px rgba(0,0,0,0.05)",
111
+ borderLeft: `4px solid ${color}`,
112
+ display: "flex",
113
+ flexDirection: "column",
114
+ wordBreak: "break-word"
115
+ }}>
116
+ <span style={{
117
+ fontSize: "12px",
118
+ color: "#aaa",
119
+ fontWeight: "700",
120
+ textTransform: "uppercase",
121
+ marginBottom: "8px"
122
+ }}>
123
+ {label}
124
+ </span>
125
+ <span style={{
126
+ fontSize: "16px",
127
+ color: "#333",
128
+ fontWeight: "600"
129
+ }}>
130
+ {value}
131
+ </span>
132
+ </div>
133
+ );
134
+
17
135
  export default RarityComponent;