From 3fa4ac8ecb26cf3a8582965514a61b8a69bda44b Mon Sep 17 00:00:00 2001 From: Fishon Amos <43862685+fishonamos@users.noreply.github.com> Date: Mon, 22 Apr 2024 08:34:22 -0700 Subject: [PATCH] feat: sorted completed quests and created daily and main quest routes #1 (#54) * add sorting for completed * Create quest.go To store Quest Structs * Create Quests.go * Update routes.go * Update routes.go * Update and rename Quests.go to quests.go * Update quest.go * Delete quest.go * Update quests.go Updates to quests route * Update quests.go Changed route name to follow other routes pattern. * Update quests.go * Update Quests.js to consume mainQuests and dailyQuests routes * Update Quests.js * Display local quests for now --------- Co-authored-by: Otaiki1 Co-authored-by: Brandon Roberts Co-authored-by: Brandon R <54774639+b-j-roberts@users.noreply.github.com> --- backend/routes/quests.go | 76 +++++++++++++++++++ backend/routes/routes.go | 1 + frontend/src/tabs/quests/Quests.js | 116 +++++++++++++++++++++++------ 3 files changed, 169 insertions(+), 24 deletions(-) create mode 100644 backend/routes/quests.go diff --git a/backend/routes/quests.go b/backend/routes/quests.go new file mode 100644 index 00000000..50afea33 --- /dev/null +++ b/backend/routes/quests.go @@ -0,0 +1,76 @@ +package routes + +import ( + "context" + "encoding/json" + "log" + "net/http" + + "github.com/keep-starknet-strange/art-peace/backend/core" +) + +// the Quest struct will represent the structure for both Daily and Main Quests data +type Quest struct { + Key int `json:"key"` + Name string `json:"name"` + Description string `json:"description"` + Reward int `json:"reward"` + DayIndex int `json:"dayIndex,omitempty"` // Only for daily quests +} + +func InitQuestsRoutes() { + http.HandleFunc("/getDailyQuests", GetDailyQuests) + http.HandleFunc("/getMainQuests", GetMainQuests) +} + +// Query dailyQuests +func GetDailyQuests(w http.ResponseWriter, r *http.Request) { + query := `SELECT key, name, description, reward, dayIndex FROM DailyQuests ORDER BY dayIndex ASC` + handleQuestQuery(w, r, query) +} + +// Query mainQuest +func GetMainQuests(w http.ResponseWriter, r *http.Request) { + query := `SELECT key, name, description, reward FROM MainQuests` + handleQuestQuery(w, r, query) +} + +func handleQuestQuery(w http.ResponseWriter, r *http.Request, query string) { + var quests []Quest + rows, err := core.ArtPeaceBackend.Databases.Postgres.Query(context.Background(), query) + if err != nil { + http.Error(w, "Database query failed: "+err.Error(), http.StatusInternalServerError) + return + } + defer rows.Close() + + for rows.Next() { + var q Quest + if err := rows.Scan(&q.Key, &q.Name, &q.Description, &q.Reward, &q.DayIndex); err != nil { + log.Printf("Error scanning row: %v", err) + continue // Log and continue to process other rows + } + quests = append(quests, q) + } + if err := rows.Err(); err != nil { + log.Printf("Error during rows iteration: %v", err) + http.Error(w, "Error processing data: "+err.Error(), http.StatusInternalServerError) + return + } + + setupCORS(&w, r) + w.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(quests); err != nil { + http.Error(w, "Error encoding response: "+err.Error(), http.StatusInternalServerError) + } +} + +// CORS setup +func setupCORS(w *http.ResponseWriter, r *http.Request) { + (*w).Header().Set("Access-Control-Allow-Origin", "*") + if r.Method == "OPTIONS" { + (*w).Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS") + (*w).Header().Set("Access-Control-Allow-Headers", "Content-Type") + (*w).WriteHeader(http.StatusOK) + } +} diff --git a/backend/routes/routes.go b/backend/routes/routes.go index 31dfb92e..97bb3226 100644 --- a/backend/routes/routes.go +++ b/backend/routes/routes.go @@ -8,5 +8,6 @@ func InitRoutes() { InitTemplateRoutes() InitUserRoutes() InitContractRoutes() + InitQuestsRoutes() InitColorsRoutes() } diff --git a/frontend/src/tabs/quests/Quests.js b/frontend/src/tabs/quests/Quests.js index de29e9ca..1af41f32 100644 --- a/frontend/src/tabs/quests/Quests.js +++ b/frontend/src/tabs/quests/Quests.js @@ -1,74 +1,142 @@ -import React from 'react' -import './Quests.css'; -import BasicTab from '../BasicTab.js'; -import QuestItem from './QuestItem.js'; +import React, { useState, useEffect } from "react"; +import "./Quests.css"; +import BasicTab from "../BasicTab.js"; +import QuestItem from "./QuestItem.js"; -const Quests = props => { +const Quests = (props) => { + const [dailyQuests, setDailyQuests] = useState([]); + const [mainQuests, setMainQuests] = useState([]); + + useEffect(() => { + const fetchQuests = async () => { + try { + // Fetching daily quests from backend + const dailyResponse = await fetch('http://localhost:8080/getDailyQuests'); + const dailyData = await dailyResponse.json(); + setDailyQuests(dailyData); + + // Fetching main quests from backend + const mainResponse = await fetch('http://localhost:8080/getMainQuests'); + const mainData = await mainResponse.json(); + setMainQuests(mainData); + } catch (error) { + console.error('Failed to fetch quests', error); + } + }; + + fetchQuests(); + }, []); // TODO: Main quests should be scrollable // TODO: Main quests should be moved to the bottom on complete // TODO: Pull quests from backend // TODO: Links in descriptions - const dailyQuests = [ + + + + const localDailyQuests = [ { title: "Place 10 pixels", description: "Add 10 pixels on the canvas", reward: "3", - status: "completed" + status: "completed", }, { title: "Build a template", description: "Create a template for the community to use", reward: "3", - status: "claim" + status: "claim", }, { title: "Deploy a Memecoin", description: "Create an Unruggable memecoin", reward: "10", - status: "completed" - } - ] + status: "completed", + }, + ]; - const mainQuests = [ + const localMainQuests = [ { title: "Tweet #art/peace", description: "Tweet about art/peace using the hashtag & addr", reward: "10", - status: "incomplete" + status: "incomplete", }, { title: "Place 100 pixels", description: "Add 100 pixels on the canvas", reward: "10", - status: "completed" + status: "completed", }, { title: "Mint an art/peace NFT", description: "Mint an NFT using the art/peace theme", reward: "5", - status: "incomplete" + status: "incomplete", + }, + ]; + + const sortByCompleted = (arr) => { + if (!arr) return []; + const newArray = []; + for (let i = 0; i < arr.length; i++) { + if (arr[i].status == "completed") { + newArray.push(arr[i]); + } else { + newArray.unshift(arr[i]); + } } - ] + return newArray; + }; // TODO: Icons for each tab? return ( -
-
+
+

Dailys

{props.timeLeftInDay}

- {dailyQuests.map((quest, index) => ( - + {sortByCompleted(dailyQuests).map((quest, index) => ( + ))} - + {sortByCompleted(localDailyQuests).map((quest, index) => ( + + ))} +

Main

- {mainQuests.map((quest, index) => ( - + {sortByCompleted(mainQuests).map((quest, index) => ( + + ))} + {sortByCompleted(localMainQuests).map((quest, index) => ( + ))}
); -} +}; export default Quests;