Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
21869e7
fix:Quest check active again in write post
Driedoutjerky Jun 7, 2026
bf205ef
fix:Render dashboard quests from quest hook
Driedoutjerky Jun 7, 2026
cdad137
fix:profile page synced to logics
Driedoutjerky Jun 7, 2026
25e18e8
Merge pull request #24 from Driedoutjerky/feat/addAssets
Driedoutjerky Jun 7, 2026
8b4130a
init:frontend asset registry
Driedoutjerky Jun 7, 2026
03002ef
feat:cosmetic asset application
Driedoutjerky Jun 9, 2026
3786091
feat:cosmetic,background equippable from inventory
Driedoutjerky Jun 9, 2026
011e1e3
claim matching quests after post submission
Driedoutjerky Jun 9, 2026
2997ee5
feat: auth routing
Driedoutjerky Jun 9, 2026
a7f50fc
feat: Integrate post submission with quest claiming
Driedoutjerky Jun 9, 2026
1d99c2f
fix: write post submission feedback
Driedoutjerky Jun 9, 2026
0c0bd2a
fix/improve auth form message feedback
Driedoutjerky Jun 9, 2026
5779aa4
fix: redirection issues
Driedoutjerky Jun 9, 2026
c61bcce
feat:Refresh current user from backend session
Driedoutjerky Jun 9, 2026
1fc6a4a
feat:load shop items from backend
Driedoutjerky Jun 9, 2026
5ec02c3
feat: connect shop purchase to backend
Driedoutjerky Jun 9, 2026
f049882
feat: Load inventory items from backend
Driedoutjerky Jun 9, 2026
f06f1a1
feat: connect inventory equip to backend
Driedoutjerky Jun 9, 2026
9ee4f2d
feat: sync visuals with equipped inventory
Driedoutjerky Jun 9, 2026
5c0bd02
fix: minor things during actual testing
Driedoutjerky Jun 9, 2026
626c2d9
Merge branch 'dev' into integration
Driedoutjerky Jun 9, 2026
3b35b48
Merge pull request #28 from Driedoutjerky/integration
Driedoutjerky Jun 9, 2026
9ca470a
fix: resolve route merge conflict after header PR
Driedoutjerky Jun 9, 2026
9dcc4dc
feat: functioning header component
Driedoutjerky Jun 9, 2026
0856e8d
feat: music player implemented
Driedoutjerky Jun 9, 2026
3f5702e
fix: minor lint fix
Driedoutjerky Jun 9, 2026
d34e6d8
fix: unequip endpoint connected
Driedoutjerky Jun 9, 2026
716aa84
fix: map asset name for inventory and shop
Driedoutjerky Jun 9, 2026
7d9998a
fix:dispatch right after will changes
Driedoutjerky Jun 9, 2026
060ab68
feat: music covers
Driedoutjerky Jun 9, 2026
038b9df
fix:default background added
Driedoutjerky Jun 9, 2026
1edbe64
fix:music stopping issue
Driedoutjerky Jun 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 77 additions & 24 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
// <div /> tests whether centered layout works.
// <section className="panel stack-md" /> tests whether reusable panel and vertical spacing classes work.
// <h1 /> tests title style
import Header from "./components/header.jsx";
import { BrowserRouter, Navigate, Route, Routes, Outlet } from "react-router-dom";
import { isAuthenticated } from "./api/userApi";

import OpeningPage from "./pages/OpeningPage.jsx";
import RegisterPage from "./pages/RegisterPage.jsx";
import LoginPage from "./pages/LoginPage.jsx";
Expand All @@ -13,41 +15,92 @@ import EggDashboardPage from "./pages/EggDashboardPage.jsx";
import InventoryPage from "./pages/InventoryPage.jsx";
import ShopPage from "./pages/ShopPage.jsx";
import MemoryArchivePage from "./pages/MemoryArchivePage.jsx";
import { BrowserRouter, Routes, Route, useLocation } from "react-router-dom";

// 1. Create a wrapper component inside the Router context
function AppContent() {
const location = useLocation();
import Header from "./components/header.jsx";
import MusicPlayer from "./components/MusicPlayer.jsx";

function ProtectedRoute({ children }) {
if (!isAuthenticated()) {
return <Navigate to="/login" replace />;
}

return children;
}

function ProtectedPage({ children }) {
return (
<ProtectedRoute>
<Header />
{children}
</ProtectedRoute>
);
}

function PublicOnlyRoute({ children }) {
if (isAuthenticated()) {
return <Navigate to="/nest" replace />;
}

return children;
}

// Define paths where you don't want the header
const hideHeaderPaths = ["/shop"];
function ProtectedLayout() {
if (!isAuthenticated()) {
return <Navigate to="/login" replace />;
}

return (
<>
{/* Only render Header if the current path isn't in the hide list */}
{!hideHeaderPaths.includes(location.pathname) && <Header />}

<main style={{ padding: "1rem" }}>
<Routes>
<Route path="/" element={<EggDashboardPage />} />
<Route path="/write" element={<WritePostPage />} />
<Route path="/nest" element={<EggDashboardPage />} />
<Route path="/archive" element={<MemoryArchivePage />} />
<Route path="/posts/:id" element={<ViewPostPage />} />
<Route path="/shop" element={<ShopPage />} />
<Route path="/inventory" element={<InventoryPage />} />
<Route path="/profile" element={<ProfilePage />} />
</Routes>
</main>
<Header />
<Outlet />
<MusicPlayer />
</>
);
}

// 2. Keep your main App component clean
function App() {
return (
<BrowserRouter>
<AppContent />
<Routes>
<Route
path="/"
element={
<PublicOnlyRoute>
<OpeningPage />
</PublicOnlyRoute>
}
/>

<Route
path="/login"
element={
<PublicOnlyRoute>
<LoginPage />
</PublicOnlyRoute>
}
/>

<Route
path="/register"
element={
<PublicOnlyRoute>
<RegisterPage />
</PublicOnlyRoute>
}
/>

<Route element={<ProtectedLayout />}>
<Route path="/nest" element={<EggDashboardPage />} />
<Route path="/write" element={<WritePostPage />} />
<Route path="/archive" element={<MemoryArchivePage />} />
<Route path="/posts/:id" element={<ViewPostPage />} />
<Route path="/shop" element={<ShopPage />} />
<Route path="/inventory" element={<InventoryPage />} />
<Route path="/profile" element={<ProfilePage />} />
</Route>

<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</BrowserRouter>
);
}
Expand Down
105 changes: 64 additions & 41 deletions src/api/eggApi.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { getRawShopItems } from "./shopApi";
import { getInventoryItems } from "./inventoryApi";

const EGG_STORAGE_KEY = "memory_egg_egg";

const defaultEgg = {
Expand All @@ -7,9 +10,15 @@ const defaultEgg = {
glow: 0,
warmth: 0,
weight: 0,

active_background_id: null,
active_music_id: null,
active_decoration_id: null,

equipped_background: "default",
selected_music: null,
equipped_cosmetic: null,

updated_at: new Date().toISOString(),
};

Expand Down Expand Up @@ -39,47 +48,26 @@ function saveEggToStorage(egg) {
localStorage.setItem(EGG_STORAGE_KEY, JSON.stringify(egg));
}

function getEmptyStats() {
return {
glow: 0,
warmth: 0,
weight: 0,
};
}

function getDecorationStats(decorationItem) {
const stats = getEmptyStats();

if (!decorationItem || decorationItem.item_type !== "decoration") {
return stats;
}

if (!decorationItem.effect_type || !decorationItem.effect_value) {
return stats;
}

if (!Object.hasOwn(stats, decorationItem.effect_type)) {
return stats;
}

return {
...stats,
[decorationItem.effect_type]: Number(decorationItem.effect_value),
};
}

function findEquippedItemByType(inventoryItems, itemType) {
return inventoryItems.find(
(item) => item.item_type === itemType && item.is_equipped
);
}

export async function getEgg() {
return loadEggFromStorage();
try {
const rawShopItems = await getRawShopItems();
const inventoryItems = await getInventoryItems(rawShopItems);

return recalculateEggFromInventory(inventoryItems);
} catch (error) {
console.warn("Failed to sync egg from inventory:", error);
return loadEggFromStorage();
}
}

export async function recalculateEggFromInventory(inventoryItems) {
const egg = loadEggFromStorage();
const currentEgg = loadEggFromStorage();

const equippedBackground = findEquippedItemByType(
inventoryItems,
Expand All @@ -91,18 +79,53 @@ export async function recalculateEggFromInventory(inventoryItems) {
"decoration"
);

const decorationStats = getDecorationStats(equippedDecoration);
const bonusStats = inventoryItems.reduce(
(totals, item) => {
if (!item.is_equipped || !item.effect_type || item.effect_value == null) {
return totals;
}

const effectValue = Number(item.effect_value);

if (Number.isNaN(effectValue)) {
return totals;
}

if (!["glow", "warmth", "weight"].includes(item.effect_type)) {
return totals;
}

return {
...totals,
[item.effect_type]: totals[item.effect_type] + effectValue,
};
},
{
glow: 0,
warmth: 0,
weight: 0,
}
);

const baseGlow = currentEgg.base_glow ?? currentEgg.glow ?? 0;
const baseWarmth = currentEgg.base_warmth ?? currentEgg.warmth ?? 0;
const baseWeight = currentEgg.base_weight ?? currentEgg.weight ?? 0;


const updatedEgg = {
...egg,
stage: 1,
glow: decorationStats.glow,
warmth: decorationStats.warmth,
weight: decorationStats.weight,
active_background_id: equippedBackground?.item_id ?? null,
active_music_id: equippedMusic?.item_id ?? null,
active_decoration_id: equippedDecoration?.item_id ?? null,
updated_at: new Date().toISOString(),
...currentEgg,

base_glow: baseGlow,
base_warmth: baseWarmth,
base_weight: baseWeight,

glow: Math.min(100, baseGlow + bonusStats.glow),
warmth: Math.min(100, baseWarmth + bonusStats.warmth),
weight: Math.min(100, baseWeight + bonusStats.weight),

equipped_background: equippedBackground?.asset_key || "default",
selected_music: equippedMusic?.asset_key || null,
equipped_cosmetic: equippedDecoration?.asset_key || null,
};

saveEggToStorage(updatedEgg);
Expand Down
Loading
Loading