diff --git a/back/src/create_db.rs b/back/src/create_db.rs index 67eb2af..21cb84a 100644 --- a/back/src/create_db.rs +++ b/back/src/create_db.rs @@ -12,7 +12,9 @@ pub fn init() -> sqlite::Result<()> { "CREATE TABLE IF NOT EXISTS articles ( id INTEGER PRIMARY KEY, title TEXT NOT NULL, - subTitle TEXT, + auteur TEXT, + edited_at DATE_FORMAT('now', '%YYYY-%mm-%dd'), + published_at DATE_FORMAT('now', '%YYYY-%mm-%dd'), content TEXT NOT NULL )", )?; diff --git a/back/src/main.rs b/back/src/main.rs index c7312e9..23d9fc0 100644 --- a/back/src/main.rs +++ b/back/src/main.rs @@ -1,7 +1,7 @@ mod create_db; use create_db::init; -use actix_web::{App, HttpServer, get, Responder, HttpResponse, http::header::ContentType}; +use actix_web::{App, web, HttpServer, get, Responder, HttpResponse, http::header::ContentType}; use actix_files::Files; use serde_json::json; use sqlite::{Connection, State}; @@ -11,9 +11,9 @@ use serde::{Serialize, Deserialize}; struct Article { id: i64, title: String, - auteur: Option, - edited_at: Option, - published_at: Option, + auteur: String, + edited_at: String, + published_at: String, content: String, } @@ -31,13 +31,13 @@ async fn get_articles() -> impl Responder { while let State::Row = stmt.next().unwrap() { let id = stmt.read::(0).unwrap(); let title = stmt.read::(1).unwrap(); - let content = stmt.read::(3).unwrap(); + let content = stmt.read::(5).unwrap(); articles.push(Article { id, title, - auteur: None, - edited_at: None, - published_at: None, + auteur: "".to_string(), + edited_at: "".to_string(), + published_at: "".to_string(), content }); } @@ -45,6 +45,54 @@ async fn get_articles() -> impl Responder { HttpResponse::Ok().json(articles) } + +#[get("/api/articles/{id}")] +async fn get_article(path: web::Path) -> impl Responder { + let id = path.into_inner(); + + // Open the database connection + let conn = match Connection::open("./data/data.db") { + Ok(conn) => conn, + Err(err) => { + eprintln!("Failed to connect to database: {}", err); + return HttpResponse::InternalServerError().body("Failed to connect to database"); + } + }; + + // Fetch the article from the database + match fetch_article_by_id(&conn, id) { + Ok(Some(article)) => HttpResponse::Ok().json(article), + Ok(None) => HttpResponse::NotFound().body(format!("Article with ID {} not found", id)), + Err(err) => { + eprintln!("Database query error: {}", err); + HttpResponse::InternalServerError().body("Database query failed") + } + } +} + +/// Fetches an article by its ID from the database. +fn fetch_article_by_id(conn: &Connection, id: i64) -> Result, sqlite::Error> { + let mut stmt = conn.prepare( + "SELECT id, title, auteur, edited_at, published_at, content + FROM articles WHERE id = ?1" + )?; + stmt.bind((1, id))?; + + let mut article = None; + while let State::Row = stmt.next()? { + article = Some(Article { + id: stmt.read::(0)?, + title: stmt.read::(1)?, + auteur: stmt.read::(2)?, + edited_at: stmt.read::(3)?, + published_at: stmt.read::(4)?, + content: stmt.read::(5)?, + }); + } + + Ok(article) +} + #[get("/api")] async fn api() -> impl Responder { let value = json!({ @@ -72,6 +120,7 @@ async fn main() -> Result<(), std::io::Error> { .service(hello) .service(get_articles) .service(api) + .service(get_article) .service(Files::new("/", "public").index_file("index.html")) .service(Files::new("/game", "public").index_file("index.html")) .service(Files::new("/chaos", "public").index_file("index.html")) diff --git a/front/index.html b/front/index.html index e4b78ea..48914fe 100644 --- a/front/index.html +++ b/front/index.html @@ -4,7 +4,8 @@ - Vite + React + TS + EcoMarin +
diff --git a/front/package.json b/front/package.json index e1132b0..7e85ec8 100644 --- a/front/package.json +++ b/front/package.json @@ -10,9 +10,14 @@ "preview": "vite preview" }, "dependencies": { + "@react-three/drei": "^9.120.0", + "@react-three/fiber": "^8.17.10", + "@types/three": "^0.170.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-router": "^7.0.2", + "three": "^0.171.0", + "three-stdlib": "^2.34.0", "react-router-dom": "^6.2.1" }, "devDependencies": { diff --git a/front/public/models/man.glb b/front/public/models/man.glb new file mode 100644 index 0000000..abd24f6 Binary files /dev/null and b/front/public/models/man.glb differ diff --git a/front/src/components/3d/Axes.tsx b/front/src/components/3d/Axes.tsx new file mode 100644 index 0000000..29e489a --- /dev/null +++ b/front/src/components/3d/Axes.tsx @@ -0,0 +1,29 @@ +import { LineBasicMaterial, Line, Group } from 'three' +import * as THREE from 'three' + +export default function Axes() { + const axes = new Group() + + const x = new Line( + new THREE.BufferGeometry().setFromPoints([new THREE.Vector3(0, 0, 0), + new THREE.Vector3(100, 0, 0)]), + new LineBasicMaterial({ color: 0xff0000 }) // red + ) + const y = new Line( + new THREE.BufferGeometry().setFromPoints([new THREE.Vector3(0, 0, 0), + new THREE.Vector3(0, 100, 0)]), + new LineBasicMaterial({ color: 0x00ff00 }) // green + ) + const z = new Line( + new THREE.BufferGeometry().setFromPoints([new THREE.Vector3(0, 0, 0), + new THREE.Vector3(0, 0, 100)]), + new LineBasicMaterial({ color: 0x0000ff }) // blue + ) + + axes.add(x) + axes.add(y) + axes.add(z) + + + return +} \ No newline at end of file diff --git a/front/src/components/3d/Character.tsx b/front/src/components/3d/Character.tsx new file mode 100644 index 0000000..272f3c3 --- /dev/null +++ b/front/src/components/3d/Character.tsx @@ -0,0 +1,24 @@ +import { useGLTF } from '@react-three/drei' + +export default function Character() { + // import glb file + + // load the glb file in "/models/BASEmodel.glb" + const { scene } = useGLTF('/models/man.glb') + + // rotate the character + scene.rotation.x = -Math.PI / 2 + scene.rotation.y = 0 + + // enfoncer le personnage dans le sol + scene.position.y = -1 + + return ( + + + + + ) + + +} \ No newline at end of file diff --git a/front/src/components/3d/Floor.tsx b/front/src/components/3d/Floor.tsx new file mode 100644 index 0000000..7a24e3f --- /dev/null +++ b/front/src/components/3d/Floor.tsx @@ -0,0 +1,18 @@ +import { Group } from 'three' +import * as THREE from 'three' + + +export default function Floor() { + const floor = new Group() + + const floorGeometry = new THREE.PlaneGeometry(100, 100, 100, 100) + const floorMaterial = new THREE.MeshStandardMaterial({ color: 0x00ff00, side: THREE.DoubleSide }) + const floorMesh = new THREE.Mesh(floorGeometry, floorMaterial) + + floorMesh.rotation.x = -Math.PI / 2 + floorMesh.position.y = -2 + + floor.add(floorMesh) + + return +} \ No newline at end of file diff --git a/front/src/components/3d/Marker.tsx b/front/src/components/3d/Marker.tsx new file mode 100644 index 0000000..61419d9 --- /dev/null +++ b/front/src/components/3d/Marker.tsx @@ -0,0 +1,22 @@ +import React from 'react' +import * as THREE from 'three' + +interface MarkerProps { + position: number[], + color: string, + onClick?: () => void +} + +export default function Marker({ position, color, onClick }: MarkerProps) { + + const [positionState, setPositionState] = React.useState(new THREE.Vector3(...position)) + + // Return the marker object + // return + return ( + setPositionState(positionState.clone().setZ(positionState.z + 0.1))} onPointerOut={() => setPositionState(new THREE.Vector3(...position))}> + + + + ) +} diff --git a/front/src/components/3d/Ocean.tsx b/front/src/components/3d/Ocean.tsx new file mode 100644 index 0000000..0808e50 --- /dev/null +++ b/front/src/components/3d/Ocean.tsx @@ -0,0 +1,52 @@ +// src/components/Ocean.tsx +import React, { useEffect, useRef } from 'react'; +import * as THREE from 'three'; +import { Water, WaterOptions } from 'three/examples/jsm/objects/Water.js'; + +const Ocean: React.FC = () => { + + const waterGeometry = new THREE.PlaneGeometry(10000, 10000); + const waterOption:WaterOptions = { + textureWidth: 512, + textureHeight: 512, + waterNormals: new THREE.TextureLoader().load('https://threejs.org/examples/textures/waternormals.jpg', function (texture) { + texture.wrapS = texture.wrapT = THREE.RepeatWrapping; + }), + alpha: 0.9, + sunDirection: new THREE.Vector3(), + sunColor: 0xffffff, + waterColor: '#001e0f', + distortionScale: 3.7, + fog: true, + + + }; + const water = new Water(waterGeometry, waterOption); + water.rotation.x = -Math.PI / 2; + water.position.y = -1; + + + const waterRef = useRef(null); + useEffect(() => { + if (waterRef.current) { + waterRef.current.add(water); + } + }, [waterRef]); + + + useEffect(() => { + const animate = () => { + requestAnimationFrame(animate); + water.material.uniforms['time'].value += 0.1 / 60.0; + }; + animate(); + }, []); + + return ( + + + + ); +}; + +export default Ocean; diff --git a/front/src/components/ArticleCard.tsx b/front/src/components/ArticleCard.tsx index 6a02bb3..8167cc6 100644 --- a/front/src/components/ArticleCard.tsx +++ b/front/src/components/ArticleCard.tsx @@ -13,7 +13,7 @@ export default function ArticleCard({ articlePreview }: { articlePreview: Articl margin: '20px', padding: '1.25em', paddingBottom: '1em', - flex: '27%', + flex: '0 1 30%', textAlign: 'left', }}> Article preview(true); const [error, setError] = useState(null); + + const articles: ArticlePreview[] = [ + { + id: 1, + title: "The Great Barrier Reef", + preview: "The Great Barrier Reef is the largest coral reef system in the world, composed of over 2,900 individual reefs and 900 islands stretching for over 2,300 kilometers (1,400 mi) over an area of approximately 344,400 square kilometers (133,000 sq mi).", + }, + { + id: 2, + title: "The Great Pacific Garbage Patch", + preview: "The Great Pacific Garbage Patch, also known as the Pacific trash vortex, is a gyre of marine debris particles in the north-central Pacific Ocean. It is located roughly from 135°W to 155°W and 35°N to 42°N.", + }, + { + id: 3, + title: "The Amazon Rainforest", + preview: "The Amazon rainforest, alternatively, the Amazon Jungle, also known in English as Amazonia, is a moist broadleaf tropical rainforest in the Amazon biome that covers most of the Amazon basin of South America.", + }, + { + id: 4, + title: "The Arctic", + preview: "The Arctic is a polar region located at the northernmost part of Earth. The Arctic consists of the Arctic Ocean, adjacent seas, and parts of Alaska (United States), Northern Canada (Canada), Finland, Greenland (Kingdom of Denmark), Iceland, Norway, Russia, and Sweden.", + }, + { + id: 5, + title: "The Antarctic", + preview: "The Antarctic, is a polar region containing the geographic South Pole and is situated in the Antarctic region of the Southern Hemisphere, almost entirely south of the Antarctic Circle, and is surrounded by the Southern Ocean.", + + } + ]; + + useEffect(() => { fetch('/api/articles') - .then(response => response.json()) + // .then(response => response.json()) + .then(_ => articles) .then((data: ArticlePreview[]) => setArticlePreviews(data)) .catch(_ => setError('Failed to fetch articles')) .finally(() => setLoading(false)); }, []); return ( -
Articles - {articlePreviews.map(articlePreview => ( - - ))} +
+ + {articlePreviews.map(articlePreview => ( + + ))} +
{!loading && !error && articlePreviews.length === 0 &&

No articles found

} {loading &&

Loading...

} {error &&

{error}

} diff --git a/front/src/components/Button.tsx b/front/src/components/Button.tsx index 654bda5..db4b4ad 100644 --- a/front/src/components/Button.tsx +++ b/front/src/components/Button.tsx @@ -1,14 +1,14 @@ -import { ReactNode, MouseEventHandler } from 'react'; +import { ReactNode } from 'react'; interface ButtonProps { - onClick: MouseEventHandler; color: 'primary' | 'secondary'; children: ReactNode; + url?: string; } -export default function Button({ onClick, color, children }: ButtonProps) { +export default function Button({ color, children, url }: ButtonProps) { return ( - + textDecoration: 'none', + }} href={url} className="ni-button" role="button">{children} ) } diff --git a/front/src/components/ClickableLink.tsx b/front/src/components/ClickableLink.tsx index 0096871..ee9f54c 100644 --- a/front/src/components/ClickableLink.tsx +++ b/front/src/components/ClickableLink.tsx @@ -9,7 +9,7 @@ export default function ClickableLink({url, text}: ClickableLinkProps) { backgroundColor: "transparent", borderRadius: '8px', borderWidth: '0', - color:"purple", + color:"white", cursor: 'pointer', display: 'inline-block', fontFamily: '"Haas Grot Text R Web", "Helvetica Neue", Helvetica, Arial, sans-serif', diff --git a/front/src/components/Footer.tsx b/front/src/components/Footer.tsx index 468812c..f4b2bee 100644 --- a/front/src/components/Footer.tsx +++ b/front/src/components/Footer.tsx @@ -7,12 +7,14 @@ interface FooterProps { export default function Footer ({bgcolor}:FooterProps) { return (
-
ENSIBS
RedCRAB
-
+

ENSIBS

RedCRAB

+

Made with ❤️ by RedCRAB

+
+ style={{ width: "100%", height: "auto", + padding: "10px"}}/>
) diff --git a/front/src/components/FstSection.tsx b/front/src/components/FstSection.tsx index a978737..7cf827b 100644 --- a/front/src/components/FstSection.tsx +++ b/front/src/components/FstSection.tsx @@ -1,19 +1,13 @@ -import Button from "./Button"; + import NavBar from "./NavBar"; +import { Link } from "react-router"; -interface FstSectionProps { - centertxt: string; - txtbt1:string; - txtbt2:string; - image:string; -} - -export default function FstSection ({centertxt, txtbt1, txtbt2, image}: FstSectionProps) { +export default function FstSection () { return (
-

{centertxt}


+

Help us to save our oceans


-
) diff --git a/front/src/components/Modal.tsx b/front/src/components/Modal.tsx new file mode 100644 index 0000000..083699f --- /dev/null +++ b/front/src/components/Modal.tsx @@ -0,0 +1,78 @@ +import React, { useState } from "react"; + +interface ModalProps { + state: "none" | "flex", + setState: (state: "none" | "flex") => void + question : { + question: string, + reponse_idx: number, + choix: string[] + }, + valides?: [boolean[], React.Dispatch>], + markerIndex?: number +} + +export default function Modal({ state, setState, question, valides, markerIndex }: ModalProps) { + const [isSuccess, setIsSuccess] = useState(false); + const [isSubmitted, setIsSubmitted] = useState(false); + console.log(state, setState, question) + return ( +
+
+ { + isSuccess ?

CONGRATULATIONS

: isSubmitted && !isSuccess &&

WRONG : CORRECT ANSWER IS {question.choix[question.reponse_idx]}

+ } +

{question.question}

+
+ { + question.choix.map((choix, index) => ( + + )) + } +
+ +
+
+
+ + )} \ No newline at end of file diff --git a/front/src/components/NavBar.tsx b/front/src/components/NavBar.tsx index 4f1c35d..d6f6c87 100644 --- a/front/src/components/NavBar.tsx +++ b/front/src/components/NavBar.tsx @@ -1,17 +1,20 @@ -import LogoButton from '../components/LogoButton.tsx' -import ClickableLink from './ClickableLink.tsx'; +import { Link } from 'react-router'; import RoundButton from './RoundButton.tsx'; export default function NavBar(){ return ( -