diff --git a/src/api/eggApi.js b/src/api/eggApi.js index 5dcbf86..4e998c6 100644 --- a/src/api/eggApi.js +++ b/src/api/eggApi.js @@ -7,9 +7,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(), }; @@ -99,9 +105,15 @@ export async function recalculateEggFromInventory(inventoryItems) { 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, + + equipped_background: equippedBackground?.asset_key || "default", + selected_music: equippedMusic?.asset_key || null, + equipped_cosmetic: equippedDecoration?.asset_key || null, + updated_at: new Date().toISOString(), }; diff --git a/src/api/inventoryApi.js b/src/api/inventoryApi.js index db47ee2..9c388dc 100644 --- a/src/api/inventoryApi.js +++ b/src/api/inventoryApi.js @@ -38,6 +38,7 @@ function buildInventoryView(userItem, shopItem) { price: shopItem.price, effect_type: shopItem.effect_type, effect_value: shopItem.effect_value, + asset_key: shopItem.asset_key, asset_url: shopItem.asset_url, is_active: shopItem.is_active, }; diff --git a/src/api/questsApi.js b/src/api/questsApi.js index d361375..0533b7a 100644 --- a/src/api/questsApi.js +++ b/src/api/questsApi.js @@ -2,51 +2,70 @@ import { addWill } from "./userApi"; const QUEST_STORAGE_KEY = "memory_egg_quests"; +const API_BASE_URL = + import.meta.env.VITE_API_BASE_URL || "http://localhost:5000/api"; + +function getAuthHeaders() { + const token = localStorage.getItem("memory_egg_token"); + + return { + "Content-Type": "application/json", + ...(token ? { Authorization: `Bearer ${token}` } : {}), + }; +} + +function shouldUseBackend() { + return Boolean(localStorage.getItem("memory_egg_token")); +} + const defaultQuests = [ { + user_quest_id: 1, quest_id: 1, + user_id: 1, title: "Write a Study Memory", description: "Write one post with the study tag.", - quest_type: "tag", + quest_type: "post_tag", required_tag: "study", - required_word_count: 1, - requires_image: false, + required_word_count: 0, + required_image: 0, reward_will: 10, status: "assigned", assigned_date: new Date().toISOString().slice(0, 10), completed_post_id: null, completed_at: null, - claimed_at: null, }, { + user_quest_id: 2, quest_id: 2, + user_id: 1, title: "A Small Reflection", description: "Write a reflection post with at least 20 words.", - quest_type: "word_count", + quest_type: "post_tag_word_count", required_tag: "reflection", required_word_count: 20, - requires_image: false, + required_image: 0, reward_will: 15, status: "assigned", assigned_date: new Date().toISOString().slice(0, 10), completed_post_id: null, completed_at: null, - claimed_at: null, }, { + user_quest_id: 3, quest_id: 3, + user_id: 1, title: "Capture a Memory", description: "Write any post with an image attached.", quest_type: "image", required_tag: null, - required_word_count: 1, - requires_image: true, + required_word_count: 0, + required_image: 1, reward_will: 20, status: "assigned", assigned_date: new Date().toISOString().slice(0, 10), completed_post_id: null, completed_at: null, - claimed_at: null, }, ]; @@ -73,19 +92,56 @@ function saveQuestsToStorage(quests) { } function doesPostCompleteQuest(post, quest) { - const tagMatches = !quest.required_tag || post.tag === quest.required_tag; + if (!post || !quest) { + return false; + } - const wordCountMatches = - !quest.required_word_count || - post.word_count >= quest.required_word_count; + const questType = quest.quest_type; + const postTag = post.tag; + const requiredTag = quest.required_tag; + const postWordCount = Number(post.word_count || 0); + const requiredWordCount = Number(quest.required_word_count || 0); + const hasImage = Boolean(post.image_url); - const imageMatches = !quest.requires_image || Boolean(post.image_url); + if (questType === "post_tag") { + return postTag === requiredTag; + } - return tagMatches && wordCountMatches && imageMatches; + if (questType === "word_count") { + return postWordCount >= requiredWordCount; + } + + if (questType === "image") { + return hasImage; + } + + if (questType === "post_tag_image") { + return postTag === requiredTag && hasImage; + } + + if (questType === "post_tag_word_count") { + return postTag === requiredTag && postWordCount >= requiredWordCount; + } + + return false; } export async function getTodayQuests() { - return loadQuestsFromStorage(); + if (!shouldUseBackend()) { + return loadQuestsFromStorage(); + } + + const response = await fetch(`${API_BASE_URL}/quests/today`, { + method: "GET", + headers: getAuthHeaders(), + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => null); + throw new Error(errorData?.error || "Failed to load today's quests."); + } + + return response.json(); } @@ -107,7 +163,7 @@ export async function checkPostAgainstQuests(post) { return { ...quest, status: "completed", - completed_post_id: post.post_id, + completed_post_id: post.post_id || post.id, completed_at: new Date().toISOString(), }; }); @@ -117,38 +173,63 @@ export async function checkPostAgainstQuests(post) { return updatedQuests; } -export async function claimQuestReward(questId) { - const quests = loadQuestsFromStorage(); +export async function claimQuestReward({ userQuestId, postId }) { + if (!shouldUseBackend()) { + const quests = loadQuestsFromStorage(); - const selectedQuest = quests.find( - (quest) => Number(quest.quest_id) === Number(questId) - ); + const selectedQuest = quests.find( + (quest) => Number(quest.user_quest_id) === Number(userQuestId) + ); - if (!selectedQuest) { - throw new Error("Quest not found."); - } - - if (selectedQuest.status !== "completed") { - throw new Error("Quest is not completed yet."); - } + if (!selectedQuest) { + throw new Error("Quest not found."); + } - const updatedQuests = quests.map((quest) => { - if (Number(quest.quest_id) !== Number(questId)) { - return quest; + if (selectedQuest.status === "claimed") { + throw new Error("Quest has already been claimed."); } - return { - ...quest, - status: "claimed", - claimed_at: new Date().toISOString(), - }; - }); + const updatedQuests = quests.map((quest) => { + if (Number(quest.user_quest_id) !== Number(userQuestId)) { + return quest; + } + + return { + ...quest, + status: "claimed", + completed_post_id: postId, + completed_at: new Date().toISOString().slice(0, 10), + }; + }); + saveQuestsToStorage(updatedQuests); const updatedUser = await addWill(selectedQuest.reward_will); return { - reward_will: selectedQuest.reward_will, - user: updatedUser, + userQuest: { + ...selectedQuest, + status: "claimed", + completed_post_id: postId, + completed_at: new Date().toISOString().slice(0, 10), + }, + user: updatedUser, + reward_will: selectedQuest.reward_will, }; + } + + const response = await fetch(`${API_BASE_URL}/quests/${userQuestId}/claim`, { + method: "POST", + headers: getAuthHeaders(), + body: JSON.stringify({ + post_id: postId, + }), + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => null); + throw new Error(errorData?.error || "Failed to claim quest."); + } + + return response.json(); } \ No newline at end of file diff --git a/src/api/shopApi.js b/src/api/shopApi.js index 8b0f2aa..4bfc1fc 100644 --- a/src/api/shopApi.js +++ b/src/api/shopApi.js @@ -10,100 +10,193 @@ const SHOP_ITEMS_STORAGE_KEY = "memory_egg_shop_items"; const defaultShopItems = [ { item_id: 1, - name: "Starry Night", + name: "Fall Window", item_type: "background", - description: "A quiet night sky for your egg's resting place.", + description: "A calm autumn view outside the egg's window.", price: 150, effect_type: null, effect_value: null, + asset_key: "fall_bg", asset_url: null, is_active: true, }, { item_id: 2, - name: "Good Morning", + name: "Grass Field", item_type: "background", - description: "A warm morning background filled with soft light.", + description: "A peaceful green field for quiet reflection.", price: 180, effect_type: null, effect_value: null, + asset_key: "grass_bg", asset_url: null, is_active: true, }, { item_id: 3, - name: "Dreamy Cloud", + name: "Night Street", item_type: "background", - description: "A gentle cloudy scene for slow reflection.", + description: "A quiet street scene glowing under the night lights.", price: 130, effect_type: null, effect_value: null, + asset_key: "nightstreet_bg", asset_url: null, is_active: true, }, { item_id: 4, - name: "Warm Blanket", + name: "Angelic", item_type: "decoration", - description: "A soft blanket that gives the egg warmth.", + description: "A bright angelic halo for your egg.", price: 120, - effect_type: "warmth", + effect_type: "glow", effect_value: "15", + asset_key: "angelic", asset_url: null, is_active: true, }, { item_id: 5, - name: "Tiny Lamp", + name: "Beard", + item_type: "decoration", + description: "A dignified beard for a wise-looking egg.", + price: 100, + effect_type: "weight", + effect_value: "10", + asset_key: "beard", + asset_url: null, + is_active: true, + }, + { + item_id: 6, + name: "Dirty Boots", + item_type: "decoration", + description: "A pair of muddy boots from a long memory walk.", + price: 110, + effect_type: "weight", + effect_value: "12", + asset_key: "dirty_boots", + asset_url: null, + is_active: true, + }, + { + item_id: 7, + name: "Flower Crown", item_type: "decoration", - description: "A small lamp that helps the egg glow softly.", + description: "A gentle flower crown that gives the egg warmth.", + price: 140, + effect_type: "warmth", + effect_value: "15", + asset_key: "flower_crown", + asset_url: null, + is_active: true, + }, + { + item_id: 8, + name: "Glasses", + item_type: "decoration", + description: "Tiny glasses for a thoughtful egg.", + price: 100, + effect_type: "glow", + effect_value: "10", + asset_key: "glasses", + asset_url: null, + is_active: true, + }, + { + item_id: 9, + name: "Life Buoy", + item_type: "decoration", + description: "A floating ring for memories that need saving.", + price: 130, + effect_type: "warmth", + effect_value: "12", + asset_key: "life_buoy", + asset_url: null, + is_active: true, + }, + { + item_id: 10, + name: "On Fire", + item_type: "decoration", + description: "A blazing effect for an egg full of energy.", price: 180, effect_type: "glow", - effect_value: "15", + effect_value: "20", + asset_key: "on_fire", asset_url: null, is_active: true, }, { - item_id: 6, - name: "Shell Ribbon", + item_id: 11, + name: "Spinning Hat", item_type: "decoration", - description: "A decorative ribbon that gives the egg weight.", - price: 220, + description: "A playful spinning hat for a cheerful egg.", + price: 130, + effect_type: "warmth", + effect_value: "10", + asset_key: "spinning_hat", + asset_url: null, + is_active: true, + }, + { + item_id: 12, + name: "Top Hat", + item_type: "decoration", + description: "A formal top hat for a classy egg.", + price: 160, effect_type: "weight", effect_value: "15", + asset_key: "top_hat", asset_url: null, is_active: true, }, { - item_id: 7, - name: "Rainy Lullaby", + item_id: 13, + name: "Work Overall", + item_type: "decoration", + description: "A hardworking overall for an egg with discipline.", + price: 170, + effect_type: "weight", + effect_value: "18", + asset_key: "work_overall", + asset_url: null, + is_active: true, + }, + { + item_id: 14, + name: "Eternity in Moments", item_type: "music", - description: "A quiet rainy melody for writing memories.", + description: "A quiet track for long memory writing.", price: 160, effect_type: null, effect_value: null, + asset_key: "eternity_in_moments", asset_url: null, is_active: true, }, { - item_id: 8, - name: "Morning Piano", + item_id: 15, + name: "Gold Phenomenon", item_type: "music", - description: "A calm piano piece for beginning the day.", + description: "A warm background track with a gentle glow.", price: 190, effect_type: null, effect_value: null, + asset_key: "gold_phenomenon", asset_url: null, is_active: true, }, { - item_id: 9, - name: "Soft Static", + item_id: 16, + name: "Mi Querido", item_type: "music", - description: "A soft ambient track for quiet focus.", + description: "A soft emotional melody for reflection.", price: 100, effect_type: null, effect_value: null, + asset_key: "mi_querido", asset_url: null, is_active: true, }, diff --git a/src/assets/assetRegistry.js b/src/assets/assetRegistry.js new file mode 100644 index 0000000..262e58e --- /dev/null +++ b/src/assets/assetRegistry.js @@ -0,0 +1,74 @@ +import defaultBackground from "./background.png"; +import defaultEgg from "./egg.PNG"; +import defaultNest from "./nest.PNG"; +import notebook from "./notebook.PNG"; +import windowFrame from "./windowframe.PNG"; + +import fallBackground from "./background/fall-bg.PNG"; +import grassBackground from "./background/grass-bg.PNG"; +import nightStreetBackground from "./background/nightstreet-bg.png"; + +import angelic from "./cosmetics/Angelic.png"; +import beard from "./cosmetics/Beard.png"; +import dirtyBoots from "./cosmetics/DirtyBoots.png"; +import flowerCrown from "./cosmetics/FlowerCrown.png"; +import glasses from "./cosmetics/Glasses.png"; +import lifeBuoy from "./cosmetics/LifeBuoy.png"; +import onFire from "./cosmetics/OnFire.png"; +import spinningHat from "./cosmetics/SpinningHat.png"; +import topHat from "./cosmetics/TopHat.png"; +import workOverall from "./cosmetics/WorkOverall.png"; + +import eternityInMoments from "./music/eternity-in-moments.m4a"; +import goldPhenomenon from "./music/gold-phenomenon.m4a"; +import miQuerido from "./music/mi-querido.m4a"; + +export const baseAssets = { + background: defaultBackground, + egg: defaultEgg, + nest: defaultNest, + notebook, + windowFrame, +}; + +export const backgroundAssets = { + default: defaultBackground, + fall_bg: fallBackground, + grass_bg: grassBackground, + nightstreet_bg: nightStreetBackground, +}; + +export const cosmeticAssets = { + angelic, + beard, + dirty_boots: dirtyBoots, + flower_crown: flowerCrown, + glasses, + life_buoy: lifeBuoy, + on_fire: onFire, + spinning_hat: spinningHat, + top_hat: topHat, + work_overall: workOverall, +}; + +export const musicAssets = { + eternity_in_moments: eternityInMoments, + gold_phenomenon: goldPhenomenon, + mi_querido: miQuerido, +}; + +export function getBackgroundAsset(backgroundKey) { + return backgroundAssets[backgroundKey] || backgroundAssets.default; +} + +export function getCosmeticAsset(cosmeticKey) { + if (!cosmeticKey) { + return null; + } + + return cosmeticAssets[cosmeticKey] || null; +} + +export function getMusicAsset(musicKey) { + return musicAssets[musicKey] || musicAssets.eternity_in_moments; +} \ No newline at end of file diff --git a/src/assets/background/.gitkeep b/src/assets/background/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/assets/background/.gitkeep @@ -0,0 +1 @@ + diff --git a/src/assets/background/fall-bg.PNG b/src/assets/background/fall-bg.PNG new file mode 100644 index 0000000..d52d7b0 Binary files /dev/null and b/src/assets/background/fall-bg.PNG differ diff --git a/src/assets/background/grass-bg.PNG b/src/assets/background/grass-bg.PNG new file mode 100644 index 0000000..fca9344 Binary files /dev/null and b/src/assets/background/grass-bg.PNG differ diff --git a/src/assets/background/nightstreet-bg.png b/src/assets/background/nightstreet-bg.png new file mode 100644 index 0000000..a3c33b8 Binary files /dev/null and b/src/assets/background/nightstreet-bg.png differ diff --git a/src/assets/cosmetics/Angelic.png b/src/assets/cosmetics/Angelic.png new file mode 100644 index 0000000..f43e2a8 Binary files /dev/null and b/src/assets/cosmetics/Angelic.png differ diff --git a/src/assets/cosmetics/Beard.png b/src/assets/cosmetics/Beard.png new file mode 100644 index 0000000..7538065 Binary files /dev/null and b/src/assets/cosmetics/Beard.png differ diff --git a/src/assets/cosmetics/DirtyBoots.png b/src/assets/cosmetics/DirtyBoots.png new file mode 100644 index 0000000..8876dbe Binary files /dev/null and b/src/assets/cosmetics/DirtyBoots.png differ diff --git a/src/assets/cosmetics/FlowerCrown.png b/src/assets/cosmetics/FlowerCrown.png new file mode 100644 index 0000000..79ee34e Binary files /dev/null and b/src/assets/cosmetics/FlowerCrown.png differ diff --git a/src/assets/cosmetics/Glasses.png b/src/assets/cosmetics/Glasses.png new file mode 100644 index 0000000..08fea5f Binary files /dev/null and b/src/assets/cosmetics/Glasses.png differ diff --git a/src/assets/cosmetics/LifeBuoy.png b/src/assets/cosmetics/LifeBuoy.png new file mode 100644 index 0000000..b5ded77 Binary files /dev/null and b/src/assets/cosmetics/LifeBuoy.png differ diff --git a/src/assets/cosmetics/OnFire.png b/src/assets/cosmetics/OnFire.png new file mode 100644 index 0000000..4da382f Binary files /dev/null and b/src/assets/cosmetics/OnFire.png differ diff --git a/src/assets/cosmetics/SpinningHat.png b/src/assets/cosmetics/SpinningHat.png new file mode 100644 index 0000000..f5c0147 Binary files /dev/null and b/src/assets/cosmetics/SpinningHat.png differ diff --git a/src/assets/cosmetics/TopHat.png b/src/assets/cosmetics/TopHat.png new file mode 100644 index 0000000..8df6ef4 Binary files /dev/null and b/src/assets/cosmetics/TopHat.png differ diff --git a/src/assets/cosmetics/WorkOverall.png b/src/assets/cosmetics/WorkOverall.png new file mode 100644 index 0000000..50e2d41 Binary files /dev/null and b/src/assets/cosmetics/WorkOverall.png differ diff --git a/src/assets/music/.gitkeep b/src/assets/music/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/assets/music/.gitkeep @@ -0,0 +1 @@ + diff --git a/src/assets/music/eternity-in-moments.m4a b/src/assets/music/eternity-in-moments.m4a new file mode 100644 index 0000000..4c225bd Binary files /dev/null and b/src/assets/music/eternity-in-moments.m4a differ diff --git a/src/assets/music/gold-phenomenon.m4a b/src/assets/music/gold-phenomenon.m4a new file mode 100644 index 0000000..f885505 Binary files /dev/null and b/src/assets/music/gold-phenomenon.m4a differ diff --git a/src/assets/music/mi-querido.m4a b/src/assets/music/mi-querido.m4a new file mode 100644 index 0000000..a89b6a0 Binary files /dev/null and b/src/assets/music/mi-querido.m4a differ diff --git a/src/hooks/useQuests.js b/src/hooks/useQuests.js index 6807c2a..75cb009 100644 --- a/src/hooks/useQuests.js +++ b/src/hooks/useQuests.js @@ -8,45 +8,79 @@ import { export function useQuests() { const [quests, setQuests] = useState([]); const [loading, setLoading] = useState(true); + const [errorMessage, setErrorMessage] = useState(""); const reloadQuests = useCallback(async () => { setLoading(true); + setErrorMessage(""); - const data = await getTodayQuests(); + try { + const data = await getTodayQuests(); - setQuests(Array.isArray(data) ? data : []); - setLoading(false); + setQuests(Array.isArray(data) ? data : []); - return data; + return data; + } catch (error) { + setErrorMessage(error.message); + throw error; + } finally { + setLoading(false); + } }, []); const checkPostForQuestCompletion = useCallback(async (post) => { - const updatedQuests = await checkPostAgainstQuests(post); + setErrorMessage(""); - setQuests(Array.isArray(updatedQuests) ? updatedQuests : []); + try { + const updatedQuests = await checkPostAgainstQuests(post); - return updatedQuests; + setQuests(Array.isArray(updatedQuests) ? updatedQuests : []); + + return updatedQuests; + } catch (error) { + setErrorMessage(error.message); + throw error; + } }, []); - const claimReward = useCallback(async (questId) => { - await claimQuestReward(questId); + const claimQuestForPost = useCallback(async ({ userQuestId, postId }) => { + setErrorMessage(""); - const updatedQuests = await getTodayQuests(); + try { + const result = await claimQuestReward({ userQuestId, postId }); - setQuests(Array.isArray(updatedQuests) ? updatedQuests : []); + const updatedQuests = await getTodayQuests(); - return updatedQuests; + setQuests(Array.isArray(updatedQuests) ? updatedQuests : []); + + return result; + } catch (error) { + setErrorMessage(error.message); + throw error; + } }, []); useEffect(() => { let ignore = false; async function loadInitialQuests() { - const data = await getTodayQuests(); - - if (!ignore) { - setQuests(Array.isArray(data) ? data : []); - setLoading(false); + setLoading(true); + setErrorMessage(""); + + try { + const data = await getTodayQuests(); + + if (!ignore) { + setQuests(Array.isArray(data) ? data : []); + } + } catch (error) { + if (!ignore) { + setErrorMessage(error.message); + } + } finally { + if (!ignore) { + setLoading(false); + } } } @@ -60,8 +94,9 @@ export function useQuests() { return { quests, loading, + errorMessage, reloadQuests, checkPostForQuestCompletion, - claimReward, + claimQuestForPost, }; } \ No newline at end of file diff --git a/src/pages/EggDashboardPage.css b/src/pages/EggDashboardPage.css index 1e7934e..ce55c18 100644 --- a/src/pages/EggDashboardPage.css +++ b/src/pages/EggDashboardPage.css @@ -250,11 +250,6 @@ transform-origin: bottom right; } -.scene-egg img { - transform: scale(2); - transform-origin: bottom right; -} - .scene-egg { right: 480px; bottom: 50px; @@ -263,6 +258,35 @@ z-index: 6; transform: rotate(12deg); transform-origin: bottom center; + position: absolute; +} + +.scene-egg img { + width: 100%; + height: 100%; + object-fit: contain; + display: block; + image-rendering: pixelated; +} + +.scene-egg > img:first-child { + position: relative; + z-index: 1; + transform: scale(2); + transform-origin: bottom right; +} + +.scene-cosmetic { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + object-fit: contain; + image-rendering: pixelated; + pointer-events: none; + z-index: 2; + transform: scale(2); + transform-origin: bottom right; } /* Egg stats */ diff --git a/src/pages/EggDashboardPage.jsx b/src/pages/EggDashboardPage.jsx index 57743ee..6d1c498 100644 --- a/src/pages/EggDashboardPage.jsx +++ b/src/pages/EggDashboardPage.jsx @@ -1,15 +1,22 @@ import "./EggDashboardPage.css"; -import eggImage from "../assets/egg.PNG"; -import nestImage from "../assets/nest.PNG"; -import notebookImage from "../assets/notebook.PNG"; -import windowFrameImage from "../assets/windowframe.PNG"; -import windowBackgroundImage from "../assets/background.png"; - import { useEgg } from "../hooks/useEgg"; +import { useQuests } from "../hooks/useQuests"; +import { + baseAssets, + getBackgroundAsset, + getCosmeticAsset, +} from "../assets/assetRegistry"; function EggDashboardPage() { const { egg, loading } = useEgg(); + const { quests, loading: questsLoading } = useQuests(); + + const equippedCosmeticKey = egg?.equipped_cosmetic || egg?.equippedCosmetic || null; + const equippedCosmeticImage = getCosmeticAsset(equippedCosmeticKey); + const equippedBackgroundKey = egg?.equipped_background || egg?.equippedBackground || "default"; + const equippedBackgroundImage = getBackgroundAsset(equippedBackgroundKey); + return (
@@ -28,11 +35,11 @@ function EggDashboardPage() {
@@ -40,11 +47,20 @@ function EggDashboardPage() {
- Nest + Nest
- Egg + Egg + + {equippedCosmeticImage && ( + + )}
@@ -98,27 +114,31 @@ function EggDashboardPage() {

My Notebook

Today’s Quests

+ {questsLoading ? ( +

Loading quests...

+ ) : quests.length === 0 ? ( +

No quests assigned.

+ ) : (
    -
  • - - Write 500+ words in total today -
  • -
  • - - Write about what you studied -
  • -
  • - - Upload one photo memory -
  • + {quests.map((quest) => ( +
  • + + {quest.title} +
  • + ))}
+ )}
diff --git a/src/pages/WritePostPage.jsx b/src/pages/WritePostPage.jsx index c1f4fa3..a06f98e 100644 --- a/src/pages/WritePostPage.jsx +++ b/src/pages/WritePostPage.jsx @@ -1,16 +1,15 @@ import { useState } from "react"; import { useNavigate } from "react-router-dom"; -/* import { createPost } from "../api/postsApi"; */ import { usePosts } from "../hooks/usePosts"; -/* import { checkPostAgainstQuests } from "../api/questsApi"; */ import { useCurrentUser } from "../hooks/useCurrentUser"; import { useQuests } from "../hooks/useQuests"; +import { doesPostLikelySatisfyQuest } from "../utils/questMatching"; import "./WritePostPage.css"; function WritePostPage() { const { addPost } = usePosts(); const { user, reloadUser } = useCurrentUser(); - const { quests, checkPostForQuestCompletion, claimReward } = useQuests(); + const { quests, claimQuestForPost } = useQuests(); const [title, setTitle] = useState(""); const [content, setContent] = useState(""); @@ -25,30 +24,46 @@ function WritePostPage() { const estimatedWill = Math.max(1, Math.floor(wordCount / 10)); async function handleSubmit(event) { - event.preventDefault(); /*Stop page from refreshing in form submit*/ + event.preventDefault(); if (!title.trim() || !content.trim()) { alert("Please write both title and content."); return; } - const { newPost, updatedPosts } = await addPost({ - title, - content, - tag, - image_url: null, - visibility, - }); - - /*await checkPostForQuestCompletion(newPost);*/ - - alert("Post created! Redirecting you to Archive Page"); - navigate("/archive"); - } - - async function handleClaimQuest(questId) { - await claimReward(questId); - await reloadUser(); + try { + const { newPost } = await addPost({ + title, + content, + tag, + image_url: null, + visibility, + }); + + const postId = newPost.id || newPost.post_id; + + const matchingQuests = quests.filter((quest) => + doesPostLikelySatisfyQuest(newPost, quest) + ); + + for (const quest of matchingQuests) { + try { + await claimQuestForPost({ + userQuestId: quest.user_quest_id, + postId, + }); + } catch (claimError) { + console.warn("Quest claim failed:", claimError); + } + } + + await reloadUser(); + + alert("Post created! Redirecting you to Archive Page"); + navigate("/archive"); + } catch (error) { + alert(error.message || "Failed to create post."); + } } return ( @@ -207,7 +222,7 @@ function WritePostPage() { {quest.status}
- + {/* REMOVED: quest completion is automated. {quest.status === "completed" && (