Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 12 additions & 0 deletions src/api/eggApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
};

Expand Down Expand Up @@ -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(),
};

Expand Down
1 change: 1 addition & 0 deletions src/api/inventoryApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down
163 changes: 122 additions & 41 deletions src/api/questsApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
];

Expand All @@ -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();
}


Expand All @@ -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(),
};
});
Expand All @@ -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();
}
Loading
Loading