diff --git a/components/Structures/Missions/Astronomers/DailyMinorPlanet/DMPVote.tsx b/components/Structures/Missions/Astronomers/DailyMinorPlanet/DMPVote.tsx index ecbb40b2..190b692f 100644 --- a/components/Structures/Missions/Astronomers/DailyMinorPlanet/DMPVote.tsx +++ b/components/Structures/Missions/Astronomers/DailyMinorPlanet/DMPVote.tsx @@ -28,7 +28,7 @@ export default function VoteDMPClassifications() { setError("User session not found."); setLoading(false); return; - } + }; setLoading(true); setError(null); @@ -45,15 +45,12 @@ export default function VoteDMPClassifications() { const media = classification.media; let images: string[] = []; - // Check if media is an array with at least 2 elements (array structure like [[], [...image URLs]]) if (Array.isArray(media) && media.length > 1) { - // Extract the image URLs from the second element if it's an array of URLs images = media[1] && Array.isArray(media[1]) ? media[1] : []; } - // Check if there's a single image URL (media is not an array but an object with uploadUrl) else if (media && typeof media === "object" && media.uploadUrl) { images.push(media.uploadUrl); - } + }'' const votes = classification.classificationConfiguration?.votes || 0; @@ -66,7 +63,7 @@ export default function VoteDMPClassifications() { setError("Failed to load classifications."); } finally { setLoading(false); - } + }'' }; useEffect(() => { @@ -100,7 +97,7 @@ export default function VoteDMPClassifications() { } } catch (error) { console.error("Error voting:", error); - } + }; }; return ( diff --git a/components/Structures/Missions/Astronomers/DailyMinorPlanet/DailyMinorPlanet.tsx b/components/Structures/Missions/Astronomers/DailyMinorPlanet/DailyMinorPlanet.tsx index 76f0aae4..fec27487 100644 --- a/components/Structures/Missions/Astronomers/DailyMinorPlanet/DailyMinorPlanet.tsx +++ b/components/Structures/Missions/Astronomers/DailyMinorPlanet/DailyMinorPlanet.tsx @@ -120,8 +120,8 @@ const DailyMinorPlanetMissions = () => { const [currentChapter, setCurrentChapter] = useState(1); const maxUnlockedChapter = Math.max( - Math.floor(experiencePoints / 9) + 1, // Based on experience points - Math.max(...missions.map(mission => mission.chapter)) // Ensure higher chapters are unlocked if there are missions in them + Math.floor(experiencePoints / 9) + 1, + Math.max(...missions.map(mission => mission.chapter)) ); useEffect(() => { diff --git a/components/Structures/Missions/Astronomers/PlanetHunters/PHVote.tsx b/components/Structures/Missions/Astronomers/PlanetHunters/PHVote.tsx index 56331072..3673c235 100644 --- a/components/Structures/Missions/Astronomers/PlanetHunters/PHVote.tsx +++ b/components/Structures/Missions/Astronomers/PlanetHunters/PHVote.tsx @@ -28,7 +28,7 @@ export default function VotePlanetClassifictions() { if (!session?.user) { setError("User session not found."); setLoading(false); - return; + return; }; setLoading(true); diff --git a/components/Structures/Missions/Meteorologists/Cloudspotting/CloudspottingOnMars.tsx b/components/Structures/Missions/Meteorologists/Cloudspotting/CloudspottingOnMars.tsx new file mode 100644 index 00000000..f5062ff8 --- /dev/null +++ b/components/Structures/Missions/Meteorologists/Cloudspotting/CloudspottingOnMars.tsx @@ -0,0 +1,194 @@ +import { useEffect, useState } from "react"; +import { useSupabaseClient, useSession } from "@supabase/auth-helpers-react"; +import MissionShell from "../../BasePlate"; +import { CloudCogIcon, FolderCog, Vote } from "lucide-react"; +import { StarterLidar } from "@/components/Projects/Lidar/Clouds"; +import VoteCoMClassifications from "./CoMVote"; + +const CloudspottingOnMars = () => { + const supabase = useSupabaseClient(); + const session = useSession(); + + const [missions, setMissions] = useState([ + { + id: 1, + chapter: 1, + title: "Make a cloud classification", + description: + "Use your weather balloon & LIDAR technologies to discover properties about Martian clouds", + icon: CloudCogIcon, + points: 2, + completedCount: 0, + internalComponent: () => , + color: 'text-blue-500', + shadow: false, + action: () => {}, + }, + { + id: 2, + chapter: 1, + title: "Propose a cloud in your classifications", + description: "Make a classification indicating a positive cloud candidate", + icon: FolderCog, + points: 1, + completedCount: 0, + internalComponent: () => , + color: 'text-cyan-300', + shadow: false, + action: () => {}, + }, + { + id: 3, + chapter: 1, + title: "Comment or vote on a cloud classification", + description: + "Collaborate with other players to rate proposed cloud candidates and behaviour", + icon: Vote, + points: 1, + completedCount: 0, + internalComponent: () => , + color: 'text-green-700', + shadow: false, + action: () => [] + }, + { + id: 4, + chapter: 2, + title: "Have a cloud candidate confirmed", + description: + "Validate the presence of a cloud candidate by confirming it through analysis and collaboration.", + icon: CloudCogIcon, + points: 3, + completedCount: 0, + internalComponent: () =>
, + color: 'text-red-500', + shadow: false, + action: () => {} + }, + { + id: 5, + chapter: 2, + title: "Connect to a planet (terrestrial list)", + description: + "Establish a connection to a planet within the terrestrial list and begin analyzing cloud patterns.", + icon: FolderCog, + points: 2, + completedCount: 0, + internalComponent: () =>
, + color: 'text-yellow-500', + shadow: false, + action: () => {} + }, + { + id: 6, + chapter: 2, + title: "Propose temperature range", + description: + "Submit your proposed temperature range for cloud formation based on your data and observations.", + icon: CloudCogIcon, + points: 2, + completedCount: 0, + internalComponent: () =>
, + color: 'text-blue-300', + shadow: false, + action: () => {} + }, + { + id: 7, + chapter: 2, + title: "Make a CoM shapes classification", + description: + "Classify cloud formations based on their Center of Mass (CoM) shapes to identify patterns.", + icon: CloudCogIcon, + points: 2, + completedCount: 0, + internalComponent: () =>
, + color: 'text-green-300', + shadow: false, + action: () => {} + }, + { + id: 8, + chapter: 2, + title: "Comment/vote on a shapes classification", + description: + "Engage with other players by commenting on or voting for cloud shapes classifications to improve the system.", + icon: Vote, + points: 1, + completedCount: 0, + internalComponent: () =>
, + color: 'text-pink-500', + shadow: false, + action: () => {} + }, + { + id: 9, + chapter: 2, + title: "Propose cloud type (1 of the 6 major ones)", + description: + "Propose a cloud type (from the 6 major types) and include the corresponding temperature range, outputting a shape classification.", + icon: FolderCog, + points: 3, + completedCount: 0, + internalComponent: () =>
, + color: 'text-orange-500', + shadow: false, + action: () => {} + }, + { + id: 10, + chapter: 2, + title: "Connect a cloud shape with type to a regular cloud", + description: + "Link a cloud shape classification to a regular cloud type, verifying its characteristics.", + icon: CloudCogIcon, + points: 2, + completedCount: 0, + internalComponent: () =>
, + color: 'text-purple-500', + shadow: false, + action: () => {} + } + ]); + + const [experiencePoints, setExperiencePoints] = useState(0); + const [level, setLevel] = useState(1); + const [currentChapter, setCurrentChapter] = useState(1); + + const maxUnlockedChapter = Math.max( + Math.floor(experiencePoints / 9) + 1, + Math.max(...missions.map(mission => mission.chapter)) + ); + + useEffect(() => { + if (!session) { + return; + }; + + const fetchMissionData = async () => { + // Fetch and update mission data from Supabase if needed + } + }, [supabase, session]); + + const handlePreviousChapter = () => { + if (currentChapter > 1) setCurrentChapter(currentChapter - 1); + }; + + const handleNextChapter = () => { + if (currentChapter < maxUnlockedChapter) setCurrentChapter(currentChapter + 1); + }; + + return ( + mission.chapter === currentChapter)} + experiencePoints={experiencePoints} + level={level} + currentChapter={currentChapter} + maxUnlockedChapter={maxUnlockedChapter} + onPreviousChapter={handlePreviousChapter} + onNextChapter={handleNextChapter} + /> + ); +}; + +export default CloudspottingOnMars; \ No newline at end of file diff --git a/components/Structures/Missions/Meteorologists/Cloudspotting/CoMVote.tsx b/components/Structures/Missions/Meteorologists/Cloudspotting/CoMVote.tsx new file mode 100644 index 00000000..5c07d93f --- /dev/null +++ b/components/Structures/Missions/Meteorologists/Cloudspotting/CoMVote.tsx @@ -0,0 +1,130 @@ +'use client'; + +import React, { useEffect, useState } from "react"; +import { PostCardSingle } from "@/content/Posts/PostSingle"; +import { useSession, useSupabaseClient } from "@supabase/auth-helpers-react"; +import StarnetLayout from "@/components/Layout/Starnet"; + +interface Classification { + id: number; + created_at: string; + content: string | null; + author: string | null; + anomaly: number | null; + media: any | null; + classificationtype: string | null; + classificationConfiguration: any | null; +}; + +export default function VotePlanetClassifictions() { + const supabase = useSupabaseClient(); + const session = useSession(); + + const [classifications, setClassifications] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const fetchClassifications = async () => { + if (!session?.user) { + setError("User session not found."); + setLoading(false); + return; + }; + + setLoading(true); + setError(null); + try { + const { data, error } = await supabase + .from('classifications') + .select('*') + .eq('classificationtype', 'cloud') + .order('created_at', { ascending: false }) as { data: Classification[]; error: any }; + + if (error) throw error; + + const processedData = data.map((classification) => { + const media = classification.media; + let images: string[] = []; + + if (Array.isArray(media) && media.length === 2 && typeof media[1] === "string") { + images.push(media[1]); + } else if (media && media.uploadUrl) { + images.push(media.uploadUrl); + } + + const votes = classification.classificationConfiguration?.votes || 0; + + return { ...classification, images, votes }; + }); + + setClassifications(processedData); + } catch (error) { + console.error("Error fetching classifications:", error); + setError("Failed to load classifications."); + } finally { + setLoading(false); + }; + }; + + useEffect(() => { + fetchClassifications(); + }, [session]) + + const handleVote = async (classificationId: number, currentConfig: any) => { + try { + const currentVotes = currentConfig?.votes || 0; + + const updatedConfig = { + ...currentConfig, + votes: currentVotes + 1, + }; + + const { error } = await supabase + .from("classifications") + .update({ classificationConfiguration: updatedConfig }) + .eq("id", classificationId); + + if (error) { + console.error("Error updating classificationConfiguration:", error); + } else { + setClassifications((prevClassifications) => + prevClassifications.map((classification) => + classification.id === classificationId + ? { ...classification, votes: updatedConfig.votes } + : classification + ) + ); + } + } catch (error) { + console.error("Error voting:", error); + }; + }; + + return ( +
+ {loading ? ( +

Loading classifications...

+ ) : error ? ( +

{error}

+ ) : ( + classifications.map((classification) => ( + handleVote(classification.id, classification.classificationConfiguration)} + /> + )) + )} +
+ ); +}; \ No newline at end of file diff --git a/constants/Structures/Properties.tsx b/constants/Structures/Properties.tsx index 96fa7481..87846777 100644 --- a/constants/Structures/Properties.tsx +++ b/constants/Structures/Properties.tsx @@ -30,6 +30,7 @@ import { ZoodexIguanas } from "@/components/Projects/Zoodex/iguanasFromAbove"; import PlanetHuntersSteps from "@/components/Structures/Missions/Astronomers/PlanetHunters/PlanetHunters"; import { useRouter } from 'next/router'; import DailyMinorPlanetMissions from "@/components/Structures/Missions/Astronomers/DailyMinorPlanet/DailyMinorPlanet"; +import CloudspottingOnMars from "@/components/Structures/Missions/Meteorologists/Cloudspotting/CloudspottingOnMars"; interface IndividualStructureProps { name?: string; @@ -285,7 +286,7 @@ export const StructuresConfig: StructureConfig = { { icon: , text: "Search your clouds", - dynamicComponent: , + dynamicComponent: , sizePercentage: 60, }, { diff --git a/content/Posts/PostSingle.tsx b/content/Posts/PostSingle.tsx index d2fe1629..b7cb229d 100644 --- a/content/Posts/PostSingle.tsx +++ b/content/Posts/PostSingle.tsx @@ -29,7 +29,7 @@ interface PostCardSingleProps { votes: number; category: string; tags?: string[]; - classificationConfig?: any; + classificationConfig?: any; images: string[]; classificationType: string; onVote?: () => void;