@@ -352,48 +126,49 @@ function InventoryPage() {
- {visibleItems.map((item) => (
-
@@ -403,17 +178,21 @@ function InventoryPage() {
Close
- {selectedItem && (
+ {selectedItem ? (
{selectedItem.is_equipped ? "Unequip" : "Equip"}
+ ) : (
+
+ No Item Selected
+
)}
+
+ {errorMessage && {errorMessage}
}
diff --git a/src/pages/ShopPage.css b/src/pages/ShopPage.css
index 79af2bb..63bb3ba 100644
--- a/src/pages/ShopPage.css
+++ b/src/pages/ShopPage.css
@@ -9,11 +9,12 @@
/* Header */
.shop-header {
- height: 42px;
+ height: 72px;
display: flex;
align-items: center;
justify-content: space-between;
- padding: 0 1.8rem;
+ gap: 1rem;
+ padding: 0 2rem;
background: var(--color-bg-soft);
border-bottom: 1px solid var(--color-border);
}
@@ -90,11 +91,33 @@
transform: translateX(-50%);
}
+.shop-return-link {
+ min-height: 32px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0 1rem;
+ border: 1px solid #cdbeb4;
+ border-radius: 999px;
+ background: #fffdf8;
+ color: #5d463c;
+ font-size: 0.78rem;
+ font-weight: 800;
+ line-height: 1;
+ white-space: nowrap;
+ text-decoration: none;
+}
+
+.shop-return-link:hover {
+ background: #f3ede5;
+ color: #4a342b;
+}
+
/* Shop window */
.shop-window {
width: min(100% - 3rem, 1120px);
- height: calc(100dvh - 42px - 5rem);
+ height: calc(100dvh - 72px - 5rem);
min-height: 560px;
margin: 2.5rem auto;
display: grid;
@@ -369,12 +392,32 @@
background: #60483f;
}
+.shop-error-message {
+ margin: 0;
+ color: #b34135;
+ font-size: 0.75rem;
+ font-weight: 800;
+}
+
+.buy-item-button:disabled {
+ opacity: 0.45;
+ cursor: not-allowed;
+}
+
+.shop-footer-actions {
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 1rem;
+ margin-left: auto;
+}
+
/* Responsive */
@media (max-width: 900px) {
.shop-window {
width: min(100% - 2rem, 720px);
- height: calc(100dvh - 42px - 2rem);
+ height: calc(100dvh - 72px - 2rem);
min-height: 0;
margin: 1rem auto;
grid-template-rows: 64px minmax(0, 1fr) 86px;
@@ -421,8 +464,16 @@
@media (max-width: 560px) {
.shop-header {
- height: 54px;
- padding: 0 1rem;
+ height: auto;
+ min-height: 60px;
+ padding: 0.7rem 1rem;
+ gap: 0.75rem;
+ }
+
+ .shop-return-link {
+ min-height: 30px;
+ padding: 0 0.75rem;
+ font-size: 0.7rem;
}
.will-balance {
@@ -433,7 +484,7 @@
.shop-window {
width: min(100% - 1rem, 440px);
- height: calc(100dvh - 54px - 1rem);
+ height: calc(100dvh - 60px - 1rem);
margin: 0.5rem auto;
border-radius: 18px;
grid-template-rows: 58px minmax(0, 1fr) 76px;
@@ -519,4 +570,10 @@
min-height: 40px;
font-size: 0.76rem;
}
+
+ .shop-footer-actions {
+ flex-direction: column;
+ align-items: flex-end;
+ gap: 0.35rem;
+ }
}
\ No newline at end of file
diff --git a/src/pages/ShopPage.jsx b/src/pages/ShopPage.jsx
index 1628931..46244d3 100644
--- a/src/pages/ShopPage.jsx
+++ b/src/pages/ShopPage.jsx
@@ -1,14 +1,15 @@
import { useMemo, useState } from "react";
+import { useShop } from "../hooks/useShop";
import "./ShopPage.css";
const shopCategories = [
{
- id: "backgrounds",
+ id: "background",
label: "Backgrounds",
icon: "▱",
},
{
- id: "decorations",
+ id: "decoration",
label: "Decorations",
icon: "⚭",
},
@@ -19,295 +20,35 @@ const shopCategories = [
},
];
-const shopItems = [
- {
- item_id: 1,
- name: "Starry Night",
- item_type: "backgrounds",
- description: "A quiet night sky for your egg's resting place.",
- price: 0,
- effect_type: "glow",
- effect_value: 1,
- asset_url: null,
- is_active: true,
-
- // Temporary frontend-only mock fields.
- owned: true,
- equipped: true,
- },
- {
- item_id: 2,
- name: "Good Morning",
- item_type: "backgrounds",
- description: "A warm morning background filled with soft light.",
- price: 450,
- effect_type: "warmth",
- effect_value: 1,
- asset_url: null,
- is_active: true,
-
- owned: false,
- equipped: false,
- },
- {
- item_id: 3,
- name: "Dreamy Cloud",
- item_type: "backgrounds",
- description: "A gentle cloudy scene for slow reflection.",
- price: 300,
- effect_type: null,
- effect_value: null,
- asset_url: null,
- is_active: true,
-
- owned: false,
- equipped: false,
- },
- {
- item_id: 4,
- name: "Test Item",
- item_type: "backgrounds",
- description: "Wow! a shop!",
- price: 999,
- effect_type: null,
- effect_value: null,
- asset_url: null,
- is_active: true,
-
- owned: false,
- equipped: false,
- },
- {
- item_id: 5,
- name: "Test Item",
- item_type: "backgrounds",
- description: "Wow! a shop!",
- price: 999,
- effect_type: null,
- effect_value: null,
- asset_url: null,
- is_active: true,
-
- owned: false,
- equipped: false,
- },
- {
- item_id: 6,
- name: "Test Item",
- item_type: "backgrounds",
- description: "Wow! a shop!",
- price: 999,
- effect_type: null,
- effect_value: null,
- asset_url: null,
- is_active: true,
-
- owned: false,
- equipped: false,
- },
- {
- item_id: 7,
- name: "Test Item",
- item_type: "backgrounds",
- description: "Wow! a shop!",
- price: 999,
- effect_type: null,
- effect_value: null,
- asset_url: null,
- is_active: true,
-
- owned: false,
- equipped: false,
- },
- {
- item_id: 8,
- name: "Test Item",
- item_type: "backgrounds",
- description: "Wow! a shop!",
- price: 999,
- effect_type: null,
- effect_value: null,
- asset_url: null,
- is_active: true,
-
- owned: false,
- equipped: false,
- },
- {
- item_id: 9,
- name: "Test Item",
- item_type: "backgrounds",
- description: "Wow! a shop!",
- price: 999,
- effect_type: null,
- effect_value: null,
- asset_url: null,
- is_active: true,
-
- owned: false,
- equipped: false,
- },
- {
- item_id: 10,
- name: "Test Item",
- item_type: "backgrounds",
- description: "Wow! a shop!",
- price: 999,
- effect_type: null,
- effect_value: null,
- asset_url: null,
- is_active: true,
-
- owned: false,
- equipped: false,
- },
- {
- item_id: 11,
- name: "Test Deco",
- item_type: "decorations",
- description: "Wow! a shop!",
- price: 999,
- effect_type: null,
- effect_value: null,
- asset_url: null,
- is_active: true,
-
- owned: true,
- equipped: false,
- },
- {
- item_id: 12,
- name: "Test Deco",
- item_type: "decorations",
- description: "Wow! a shop!",
- price: 999,
- effect_type: null,
- effect_value: null,
- asset_url: null,
- is_active: true,
-
- owned: false,
- equipped: false,
- },
- {
- item_id: 13,
- name: "Test Deco",
- item_type: "decorations",
- description: "Wow! a shop!",
- price: 999,
- effect_type: null,
- effect_value: null,
- asset_url: null,
- is_active: true,
-
- owned: false,
- equipped: false,
- },
- {
- item_id: 14,
- name: "Test Deco",
- item_type: "decorations",
- description: "Wow! a shop!",
- price: 999,
- effect_type: null,
- effect_value: null,
- asset_url: null,
- is_active: true,
-
- owned: false,
- equipped: false,
- },
- {
- item_id: 15,
- name: "Test Deco",
- item_type: "decorations",
- description: "Wow! a shop!",
- price: 999,
- effect_type: null,
- effect_value: null,
- asset_url: null,
- is_active: true,
-
- owned: false,
- equipped: false,
- },
- {
- item_id: 16,
- name: "Test Deco",
- item_type: "decorations",
- description: "Wow! a shop!",
- price: 999,
- effect_type: null,
- effect_value: null,
- asset_url: null,
- is_active: true,
-
- owned: false,
- equipped: false,
- },
- {
- item_id: 17,
- name: "Test Music",
- item_type: "music",
- description: "Music is life",
- price: 999,
- effect_type: null,
- effect_value: null,
- asset_url: null,
- is_active: true,
-
- owned: false,
- equipped: false,
- },
- {
- item_id: 18,
- name: "Invisible music",
- item_type: "music",
- description: "You shouldn't be able to see me.",
- price: 999,
- effect_type: null,
- effect_value: null,
- asset_url: null,
- is_active: false,
-
- owned: false,
- equipped: false,
- },
- {
- item_id: 17,
- name: "Invisible deco",
- item_type: "decorations",
- description: "You shouldn't be able to see me.",
- price: 999,
- effect_type: null,
- effect_value: null,
- asset_url: null,
- is_active: false,
-
- owned: false,
- equipped: false,
- },
-];
-
function ShopPage() {
- const [activeCategory, setActiveCategory] = useState("backgrounds");
- const [selectedItemId, setSelectedItemId] = useState(1);
+ const [activeCategory, setActiveCategory] = useState("background");
+ const [selectedItemId, setSelectedItemId] = useState(null);
+
+ const {
+ shopItems,
+ user,
+ loading,
+ errorMessage,
+ purchaseItem,
+ } = useShop();
const visibleItems = useMemo(() => {
return shopItems.filter(
(item) => item.item_type === activeCategory && item.is_active
);
- }, [activeCategory]);
+ }, [activeCategory, shopItems]);
const selectedItem = useMemo(() => {
- const selected = shopItems.find((item) => item.item_id === selectedItemId);
+ const selected = shopItems.find(
+ (item) => Number(item.item_id) === Number(selectedItemId)
+ );
if (selected && selected.item_type === activeCategory && selected.is_active) {
return selected;
}
return visibleItems[0] ?? null;
- }, [activeCategory, selectedItemId, visibleItems]);
+ }, [activeCategory, selectedItemId, shopItems, visibleItems]);
function handleCategoryChange(categoryId) {
const firstItem = shopItems.find(
@@ -315,16 +56,26 @@ function ShopPage() {
);
setActiveCategory(categoryId);
+ setSelectedItemId(firstItem?.item_id ?? null);
+ }
- if (firstItem) {
- setSelectedItemId(firstItem.item_id);
+ async function handlePurchaseSelectedItem() {
+ if (!selectedItem) {
+ return;
+ }
+
+ try {
+ await purchaseItem(selectedItem.item_id);
+ setSelectedItemId(selectedItem.item_id);
+ } catch {
+ // errorMessage is handled inside useShop
}
}
return (