generated from lucien/actix-react-template
Compare commits
52 commits
back_end_s
...
main
Author | SHA1 | Date | |
---|---|---|---|
1d207b56a6 | |||
6228e839d7 | |||
![]() |
75c29c719a | ||
![]() |
2b71492587 | ||
![]() |
0508ddaa7f | ||
![]() |
b2cb9ca5c8 | ||
![]() |
1e95438b2c | ||
![]() |
0fa1a9b6f3 | ||
![]() |
6405c6db09 | ||
![]() |
f8ffe3e16f | ||
![]() |
d685e00bc3 | ||
![]() |
3f9617549d | ||
![]() |
0e31e90af9 | ||
![]() |
6e77233ac0 | ||
1ec6b1b0da | |||
![]() |
4682f8a61e | ||
b8fa053ca3 | |||
![]() |
53853dcce5 | ||
![]() |
ea2bb7ac3c | ||
![]() |
e1bcb4b022 | ||
![]() |
cbae2746fd | ||
![]() |
12fa0684a8 | ||
![]() |
3794ccd70d | ||
b252122272 | |||
![]() |
5bb2ac3f9b | ||
467d560b15 | |||
ac3fc5c391 | |||
![]() |
71a829e726 | ||
d6f2d6cc09 | |||
![]() |
1de47f3fb8 | ||
![]() |
17affb3297 | ||
![]() |
bf73ed1698 | ||
![]() |
e53fc84ac3 | ||
![]() |
5b812000b8 | ||
![]() |
e2261f829c | ||
![]() |
2adacceac5 | ||
![]() |
de11f64cc3 | ||
![]() |
4e7259ae28 | ||
![]() |
e121090433 | ||
![]() |
9562eb98dc | ||
![]() |
f77b96db25 | ||
![]() |
6e67e1238b | ||
![]() |
7d8dd263eb | ||
![]() |
a239d17697 | ||
![]() |
b3d966e0ae | ||
![]() |
2317a3683e | ||
![]() |
36dae30b4e | ||
![]() |
5c4ac78dee | ||
![]() |
f3dc0e99f8 | ||
![]() |
3cdab2e303 | ||
![]() |
265157bbe1 | ||
![]() |
ebf4620640 |
35 changed files with 741 additions and 48 deletions
|
@ -9,6 +9,4 @@ actix-web = "4"
|
|||
sqlite = "0.36.1"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0.133"
|
||||
rss = "2.0.11"
|
||||
reqwest = "0.12.9"
|
||||
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
use sqlite::{Connection, OpenFlags};
|
||||
use rss::{Channel, Item};
|
||||
use reqwest;
|
||||
|
||||
pub fn init() -> sqlite::Result<()> {
|
||||
let conn = Connection::open_with_flags(
|
||||
|
@ -23,18 +21,3 @@ pub fn init() -> sqlite::Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn update_articles( channel: &Channel) {
|
||||
let conn = Connection::open_with_flags(
|
||||
"./data/data.db",
|
||||
OpenFlags::new()
|
||||
.with_create()
|
||||
.with_read_write()
|
||||
);
|
||||
|
||||
let flux_rss = reqwest::get("https://www.lemonde.fr/rss/une.xml")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let channel = Channel::read_from(&flux_rss[..]);
|
||||
}
|
||||
|
|
|
@ -122,6 +122,8 @@ async fn main() -> Result<(), std::io::Error> {
|
|||
.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"))
|
||||
})
|
||||
.bind(("0.0.0.0", 2486))?
|
||||
.run()
|
||||
|
|
|
@ -3,10 +3,22 @@ services:
|
|||
build:
|
||||
context: .
|
||||
dockerfile: dockerfile
|
||||
container_name: web
|
||||
network: host
|
||||
container_name: nuitdelinfo
|
||||
restart: always
|
||||
ports:
|
||||
- 8080:8080
|
||||
volumes:
|
||||
- ./back/data:/app/data
|
||||
networks:
|
||||
- traefik
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.nuitdelinfo.rule=Host(`nuitdelinfo.leizour.fr`)"
|
||||
- "traefik.http.routers.nuitdelinfo.entrypoints=websecure"
|
||||
- "traefik.http.routers.nuitdelinfo.tls=true"
|
||||
- "traefik.http.routers.nuitdelinfo.tls.certresolver=myresolver"
|
||||
- "traefik.http.services.nuitdelinfo.loadbalancer.server.port=2486"
|
||||
|
||||
networks:
|
||||
traefik:
|
||||
external: true
|
||||
|
||||
|
|
|
@ -10,9 +10,9 @@ RUN cargo build --release
|
|||
|
||||
FROM debian:bookworm-slim
|
||||
WORKDIR /app
|
||||
RUN apt-get update & apt-get install -y extra-runtime-dependencies & rm -rf /var/lib/apt/lists/*
|
||||
RUN apt update && apt install -y libsqlite3-0
|
||||
COPY --from=front /app/dist /app/public
|
||||
COPY --from=back /app/target/release/back /app/back
|
||||
EXPOSE 8080
|
||||
EXPOSE 2486
|
||||
CMD ["/app/back"]
|
||||
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite + React + TS</title>
|
||||
<title>EcoMarin</title>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0,0&icon_names=sailing" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
|
|
@ -17,7 +17,8 @@
|
|||
"react-dom": "^18.3.1",
|
||||
"react-router": "^7.0.2",
|
||||
"three": "^0.171.0",
|
||||
"three-stdlib": "^2.34.0"
|
||||
"three-stdlib": "^2.34.0",
|
||||
"react-router-dom": "^6.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.15.0",
|
||||
|
@ -30,6 +31,7 @@
|
|||
"globals": "^15.12.0",
|
||||
"typescript": "~5.6.2",
|
||||
"typescript-eslint": "^8.15.0",
|
||||
"vite": "^6.0.1"
|
||||
"vite": "^6.0.1",
|
||||
"react-router-dom": "^6.2.1"
|
||||
}
|
||||
}
|
||||
|
|
BIN
front/public/pictures/sea.gif
Normal file
BIN
front/public/pictures/sea.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 868 KiB |
|
@ -1,6 +1,4 @@
|
|||
import React from 'react'
|
||||
import { LineBasicMaterial, Line, Group } from 'three'
|
||||
import { useFrame } from '@react-three/fiber'
|
||||
import * as THREE from 'three'
|
||||
|
||||
export default function Axes() {
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import { Group, Mesh, MeshStandardMaterial, BufferGeometry } from 'three'
|
||||
import { useGLTF } from '@react-three/drei'
|
||||
|
||||
export default function Character() {
|
||||
// import glb file
|
||||
|
||||
// load the glb file in "/models/BASEmodel.glb"
|
||||
const { nodes, materials, scene } = useGLTF('/models/man.glb')
|
||||
const { scene } = useGLTF('/models/man.glb')
|
||||
|
||||
// rotate the character
|
||||
scene.rotation.x = -Math.PI / 2
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
|
||||
import React from 'react'
|
||||
import { Group } from 'three'
|
||||
import * as THREE from 'three'
|
||||
|
||||
|
|
|
@ -1,23 +1,22 @@
|
|||
import React from 'react'
|
||||
import { Group } from 'three'
|
||||
import * as THREE from 'three'
|
||||
|
||||
interface MarkerProps {
|
||||
position: [number, number, number],
|
||||
position: number[],
|
||||
color: string,
|
||||
onClick?: () => void
|
||||
}
|
||||
|
||||
export default function Marker({ position, color, onClick }: MarkerProps) {
|
||||
|
||||
const [positionState, setPositionState] = React.useState(position)
|
||||
const [positionState, setPositionState] = React.useState(new THREE.Vector3(...position))
|
||||
|
||||
// Return the marker object
|
||||
// return <primitive object={marker} />
|
||||
return (
|
||||
<mesh position={positionState} rotation={[Math.PI,0,0]} onClick={onClick} onPointerOver={(e) => setPositionState([positionState[0], positionState[1], positionState[2] + 0.1])} onPointerOut={(e) => setPositionState(position)}>
|
||||
<mesh position={positionState} rotation={[Math.PI,0,0]} onClick={onClick} onPointerOver={() => setPositionState(positionState.clone().setZ(positionState.z + 0.1))} onPointerOut={() => setPositionState(new THREE.Vector3(...position))}>
|
||||
<coneGeometry args={[0.15, 0.6, 6]} />
|
||||
<meshStandardMaterial color={color} side={THREE.DoubleSide} />
|
||||
</mesh>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
import React, { useEffect, useRef } from 'react';
|
||||
import * as THREE from 'three';
|
||||
import { Water, WaterOptions } from 'three/examples/jsm/objects/Water.js';
|
||||
import { WaterMesh, WaterMeshOptions } from 'three/examples/jsm/objects/Water2Mesh.js';
|
||||
|
||||
const Ocean: React.FC = () => {
|
||||
|
||||
|
|
49
front/src/components/ArticleCard.tsx
Normal file
49
front/src/components/ArticleCard.tsx
Normal file
|
@ -0,0 +1,49 @@
|
|||
import { ArticlePreview } from '../types'
|
||||
import ButtonLink from './ButtonLink'
|
||||
|
||||
export default function ArticleCard({ articlePreview }: { articlePreview: ArticlePreview }) {
|
||||
return (
|
||||
<div style={{
|
||||
backgroundColor: 'var(--color-yellow)',
|
||||
borderRadius: '8px',
|
||||
color: 'var(--color-black)',
|
||||
fontFamily: '"Haas Grot Text R Web", "Helvetica Neue", Helvetica, Arial, sans-serif',
|
||||
fontSize: '14px',
|
||||
lineHeight: '20px',
|
||||
margin: '20px',
|
||||
padding: '1.25em',
|
||||
paddingBottom: '1em',
|
||||
flex: '0 1 30%',
|
||||
textAlign: 'left',
|
||||
}}>
|
||||
<img src="https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fimages8.alphacoders.com%2F438%2F438396.jpg&f=1&nofb=1&ipt=14796576c139b58db62d2d2098392ef2fbaee77911040f7c0d98ab65a69c4644&ipo=images" alt="Article preview" style={{
|
||||
borderRadius: '8px',
|
||||
width: '100%',
|
||||
height: 'auto',
|
||||
marginBottom: '10px',
|
||||
flexShrink: 0,
|
||||
}} />
|
||||
<div style={{ }}>
|
||||
<h2 style={{
|
||||
fontSize: '25px',
|
||||
fontWeight: '500',
|
||||
lineHeight: '30px',
|
||||
margin: '0',
|
||||
padding: '0',
|
||||
marginBottom: '10px',
|
||||
}}>{articlePreview.title}</h2>
|
||||
<p style={{
|
||||
margin: '0',
|
||||
padding: '0',
|
||||
// limit the preview to 200 characters
|
||||
overflow: 'hidden',
|
||||
display: '-webkit-box',
|
||||
WebkitLineClamp: 3,
|
||||
lineClamp: 3,
|
||||
WebkitBoxOrient: 'vertical',
|
||||
}}>{articlePreview.preview}</p>
|
||||
<div style={{ float: 'right', marginTop: '.5rem' }}><ButtonLink url={`/article/${articlePreview.id}`} color='secondary' text='Read more...'/></div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
83
front/src/components/ArticlesSection.tsx
Normal file
83
front/src/components/ArticlesSection.tsx
Normal file
|
@ -0,0 +1,83 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import ArticleCard from "./ArticleCard";
|
||||
import { ArticlePreview } from "../types";
|
||||
|
||||
export default function ArticlesSection() {
|
||||
|
||||
const [articlePreviews, setArticlePreviews] = useState<ArticlePreview[]>([]);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [error, setError] = useState<string | null>(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(_ => articles)
|
||||
.then((data: ArticlePreview[]) => setArticlePreviews(data))
|
||||
.catch(_ => setError('Failed to fetch articles'))
|
||||
.finally(() => setLoading(false));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="row" id="articles" style={{
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
justifyContent: 'space-around',
|
||||
backgroundColor: 'var(--color-verydarkblue)',
|
||||
flex: 1,
|
||||
}}>
|
||||
<h1 style={{
|
||||
color: 'var(--color-yellow)',
|
||||
fontFamily: '"Haas Grot Text R Web", "Helvetica Neue", Helvetica, Arial, sans-serif',
|
||||
fontSize: '32px',
|
||||
fontWeight: '500',
|
||||
lineHeight: '30px',
|
||||
margin: '20px',
|
||||
padding: '10px',
|
||||
paddingBottom: '0',
|
||||
marginBottom: '10px',
|
||||
textAlign: 'center',
|
||||
width: '100%',
|
||||
}}>Articles</h1>
|
||||
<div style={{width: '100%', display: 'flex', justifyContent: 'space-around', flexWrap: 'wrap'}}>
|
||||
|
||||
{articlePreviews.map(articlePreview => (
|
||||
<ArticleCard key={articlePreview.id} articlePreview={articlePreview} />
|
||||
))}
|
||||
</div>
|
||||
{!loading && !error && articlePreviews.length === 0 && <p style={{color: "var(--color-lightblue)", fontSize: 22}}>No articles found</p>}
|
||||
{loading && <p style={{color: "var(--color-lightblue)", fontSize: 22}}>Loading...</p>}
|
||||
{error && <p style={{color: "var(--color-lightblue)", fontSize: 22}}>{error}</p>}
|
||||
</div>
|
||||
)
|
||||
}
|
36
front/src/components/Button.tsx
Normal file
36
front/src/components/Button.tsx
Normal file
|
@ -0,0 +1,36 @@
|
|||
import { ReactNode } from 'react';
|
||||
|
||||
interface ButtonProps {
|
||||
color: 'primary' | 'secondary';
|
||||
children: ReactNode;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
export default function Button({ color, children, url }: ButtonProps) {
|
||||
return (
|
||||
<a style={{
|
||||
backgroundColor: color === 'primary' ? 'var(--color-yellow)' : 'var(--color-darkblue)',
|
||||
borderRadius: '8px',
|
||||
borderWidth: '0',
|
||||
color: color === 'primary' ? 'var(--color-black)' : 'var(--color-white)',
|
||||
cursor: 'pointer',
|
||||
display: 'inline-block',
|
||||
fontFamily: '"Haas Grot Text R Web", "Helvetica Neue", Helvetica, Arial, sans-serif',
|
||||
fontSize: '20px',
|
||||
fontWeight: '500',
|
||||
lineHeight: '20px',
|
||||
listStyle: 'none',
|
||||
margin: '0',
|
||||
padding: '10px 12px',
|
||||
textAlign: 'center',
|
||||
transition: 'all 200ms',
|
||||
verticalAlign: 'baseline',
|
||||
whiteSpace: 'nowrap',
|
||||
userSelect: 'none',
|
||||
WebkitUserSelect: 'none',
|
||||
touchAction: 'manipulation',
|
||||
textDecoration: 'none',
|
||||
}} href={url} className="ni-button" role="button">{children}</a>
|
||||
)
|
||||
}
|
||||
|
34
front/src/components/ButtonLink.tsx
Normal file
34
front/src/components/ButtonLink.tsx
Normal file
|
@ -0,0 +1,34 @@
|
|||
interface ButtonLinkProps {
|
||||
url: string;
|
||||
color: 'primary' | 'secondary';
|
||||
text: string;
|
||||
}
|
||||
|
||||
export default function Button({ url, color, text }: ButtonLinkProps) {
|
||||
return (
|
||||
<a style={{
|
||||
backgroundColor: color === 'primary' ? 'var(--color-yellow)' : 'var(--color-darkblue)',
|
||||
borderRadius: '8px',
|
||||
borderWidth: '0',
|
||||
color: color === 'primary' ? 'var(--color-black)' : 'var(--color-white)',
|
||||
cursor: 'pointer',
|
||||
display: 'inline-block',
|
||||
fontFamily: '"Haas Grot Text R Web", "Helvetica Neue", Helvetica, Arial, sans-serif',
|
||||
fontSize: '14px',
|
||||
fontWeight: '500',
|
||||
lineHeight: '20px',
|
||||
listStyle: 'none',
|
||||
margin: '0',
|
||||
padding: '10px 12px',
|
||||
textAlign: 'center',
|
||||
transition: 'all 200ms',
|
||||
verticalAlign: 'baseline',
|
||||
whiteSpace: 'nowrap',
|
||||
userSelect: 'none',
|
||||
WebkitUserSelect: 'none',
|
||||
touchAction: 'manipulation',
|
||||
textDecoration: 'none',
|
||||
}} href={url} className="ni-button">{text}</a>
|
||||
)
|
||||
}
|
||||
|
31
front/src/components/ClickableLink.tsx
Normal file
31
front/src/components/ClickableLink.tsx
Normal file
|
@ -0,0 +1,31 @@
|
|||
interface ClickableLinkProps {
|
||||
url:string;
|
||||
text:string;
|
||||
}
|
||||
|
||||
export default function ClickableLink({url, text}: ClickableLinkProps) {
|
||||
return (<div>
|
||||
<a href={url}><button style={{
|
||||
backgroundColor: "transparent",
|
||||
borderRadius: '8px',
|
||||
borderWidth: '0',
|
||||
color:"white",
|
||||
cursor: 'pointer',
|
||||
display: 'inline-block',
|
||||
fontFamily: '"Haas Grot Text R Web", "Helvetica Neue", Helvetica, Arial, sans-serif',
|
||||
fontSize: '18px',
|
||||
fontWeight: '0',
|
||||
lineHeight: '20px',
|
||||
listStyle: 'none',
|
||||
margin: '0',
|
||||
padding: '10px 12px',
|
||||
textAlign: 'center',
|
||||
transition: 'all 200ms',
|
||||
verticalAlign: 'baseline',
|
||||
whiteSpace: 'nowrap',
|
||||
userSelect: 'none',
|
||||
WebkitUserSelect: 'none',
|
||||
touchAction: 'manipulation',
|
||||
}} className="LinkNavbar">{text}</button></a>
|
||||
</div>)
|
||||
}
|
21
front/src/components/Footer.tsx
Normal file
21
front/src/components/Footer.tsx
Normal file
|
@ -0,0 +1,21 @@
|
|||
import LogoButton from "./LogoButton"
|
||||
|
||||
interface FooterProps {
|
||||
bgcolor:string
|
||||
}
|
||||
|
||||
export default function Footer ({bgcolor}:FooterProps) {
|
||||
return (
|
||||
<footer style={{display: "flex", justifyContent: "space-between", backgroundColor:bgcolor}}>
|
||||
<div style={{ display : "flex", alignItems: "center", flexDirection: "column", margin:"5px", width:"10%"}}><p>ENSIBS</p><p>RedCRAB</p></div>
|
||||
<p>Made with ❤️ by RedCRAB</p>
|
||||
<div style={{ display : "flex", alignItems: "center", flexDirection: "row", margin:"5px", width:"10%"}}>
|
||||
<LogoButton
|
||||
url="https://www-ensibs.univ-ubs.fr/fr/index.html"
|
||||
logo = "https://www-ensibs.univ-ubs.fr/skins/ENSIBS/resources/img/logo.png"
|
||||
style={{ width: "100%", height: "auto",
|
||||
padding: "10px"}}/>
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
}
|
87
front/src/components/FstSection.tsx
Normal file
87
front/src/components/FstSection.tsx
Normal file
|
@ -0,0 +1,87 @@
|
|||
|
||||
import NavBar from "./NavBar";
|
||||
import { Link } from "react-router";
|
||||
|
||||
export default function FstSection () {
|
||||
return (
|
||||
<div style={{
|
||||
textAlign: "center",
|
||||
color: "yellow",
|
||||
backgroundImage: "url('/pictures/sea.gif')",
|
||||
backgroundSize: "cover",
|
||||
backgroundPosition: "center",
|
||||
backgroundRepeat: "no-repeat",
|
||||
height: "75vh"}}>
|
||||
<NavBar />
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<div style={{height:"150px"}} />
|
||||
<h1
|
||||
style={{
|
||||
fontSize: "3em",
|
||||
fontWeight: "bold",
|
||||
textShadow: "2px 2px 4px #000000",
|
||||
fontFamily: "Helvetica",
|
||||
}}
|
||||
|
||||
>Help us to save our oceans</h1><br />
|
||||
<div style={{display:"flex", justifyContent: "center", gap: "20px"}}>
|
||||
{/* <Button color="primary" children={"Aller au jeu"} url="/game" />
|
||||
<Button color="secondary" children={"Lire les articles"} url="/" /> */}
|
||||
<Link to="/game" style={{
|
||||
backgroundColor: 'var(--color-yellow)',
|
||||
borderRadius: '8px',
|
||||
borderWidth: '0',
|
||||
color: 'var(--color-black)',
|
||||
cursor: 'pointer',
|
||||
display: 'inline-block',
|
||||
fontFamily: '"Haas Grot Text R Web", "Helvetica Neue", Helvetica, Arial, sans-serif',
|
||||
fontSize: '20px',
|
||||
fontWeight: '500',
|
||||
lineHeight: '20px',
|
||||
listStyle: 'none',
|
||||
margin: '0',
|
||||
padding: '10px 12px',
|
||||
textAlign: 'center',
|
||||
transition: 'all 200ms',
|
||||
verticalAlign: 'baseline',
|
||||
whiteSpace: 'nowrap',
|
||||
userSelect: 'none',
|
||||
WebkitUserSelect: 'none',
|
||||
touchAction: 'manipulation',
|
||||
textDecoration: 'none',
|
||||
}}>Aller au jeu</Link>
|
||||
<Link to="/articles" style={{
|
||||
backgroundColor: 'var(--color-darkblue)',
|
||||
borderRadius: '8px',
|
||||
borderWidth: '0',
|
||||
color: 'var(--color-white)',
|
||||
cursor: 'pointer',
|
||||
display: 'inline-block',
|
||||
fontFamily: '"Haas Grot Text R Web", "Helvetica Neue", Helvetica, Arial, sans-serif',
|
||||
fontSize: '20px',
|
||||
fontWeight: '500',
|
||||
lineHeight: '20px',
|
||||
listStyle: 'none',
|
||||
margin: '0',
|
||||
padding: '10px 12px',
|
||||
textAlign: 'center',
|
||||
transition: 'all 200ms',
|
||||
verticalAlign: 'baseline',
|
||||
whiteSpace: 'nowrap',
|
||||
userSelect: 'none',
|
||||
WebkitUserSelect: 'none',
|
||||
touchAction: 'manipulation',
|
||||
textDecoration: 'none',
|
||||
}}>Lire les articles</Link>
|
||||
|
||||
</div>
|
||||
</div></div>
|
||||
)
|
||||
}
|
13
front/src/components/LogoButton.tsx
Normal file
13
front/src/components/LogoButton.tsx
Normal file
|
@ -0,0 +1,13 @@
|
|||
import * as React from 'react';
|
||||
|
||||
interface LogoButtonProps {
|
||||
url:string;
|
||||
logo:string;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
export default function LogoButton ({url, logo, style}: LogoButtonProps) {
|
||||
return (
|
||||
<a href={url}><img src={logo} style={{ ...style }}/></a>
|
||||
)
|
||||
}
|
20
front/src/components/NavBar.tsx
Normal file
20
front/src/components/NavBar.tsx
Normal file
|
@ -0,0 +1,20 @@
|
|||
import { Link } from 'react-router';
|
||||
import RoundButton from './RoundButton.tsx';
|
||||
|
||||
export default function NavBar(){
|
||||
return (
|
||||
<nav style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "0.5em 1em"}}>
|
||||
<div style={{ display : "flex", alignItems: "center", flexDirection: "row"}}>
|
||||
{/* <LogoButton url="/" logo = "https://archlinux.org/static/hetzner_logo.41114a37d25f.png"/> */}
|
||||
<span className="material-symbols-outlined"
|
||||
style={{fontSize: "4em", color: "white", margin: "0.5em"}}>sailing</span>
|
||||
<Link to="/" style={{ textDecoration: "none", color: "white", fontSize: "1.5em", margin: "0.5em", fontFamily: '"Haas Grot Text R Web", "Helvetica Neue", Helvetica, Arial, sans-serif'}}>Home</Link>
|
||||
<Link to="/game" style={{ textDecoration: "none", color: "white", fontSize: "1.5em", margin: "0.5em", fontFamily: '"Haas Grot Text R Web", "Helvetica Neue", Helvetica, Arial, sans-serif'}}>Game</Link>
|
||||
<Link to="/articles" style={{ textDecoration: "none", color: "white", fontSize: "1.5em", margin: "0.5em", fontFamily: '"Haas Grot Text R Web", "Helvetica Neue", Helvetica, Arial, sans-serif'}}>Articles</Link>
|
||||
<Link to="/chaos" style={{ textDecoration: "none", color: "white", fontSize: "1.5em", margin: "0.5em", fontFamily: '"Haas Grot Text R Web", "Helvetica Neue", Helvetica, Arial, sans-serif'}}>Le Chaos</Link>
|
||||
</div>
|
||||
<div style={{ display : "flex", alignItems: "center", flexDirection: "row"}}>
|
||||
<RoundButton url="https://www.apple.com/macos/macos-sequoia/" bgcolor="var(--color-white)" text="?"/>
|
||||
</div>
|
||||
</nav>);
|
||||
}
|
35
front/src/components/RoundButton.tsx
Normal file
35
front/src/components/RoundButton.tsx
Normal file
|
@ -0,0 +1,35 @@
|
|||
interface RoundButtonProps {
|
||||
url:string;
|
||||
bgcolor:string;
|
||||
text:string;
|
||||
}
|
||||
|
||||
export default function RoundButton({url, bgcolor, text}: RoundButtonProps) {
|
||||
return (<div>
|
||||
<a href={url}><button style={{
|
||||
backgroundColor: bgcolor,
|
||||
borderStyle: 'solid',
|
||||
borderColor:"var(--color-lightblue)",
|
||||
borderRadius: '50%',
|
||||
width: '45px',
|
||||
height: '45px',
|
||||
borderWidth: '1',
|
||||
cursor: 'pointer',
|
||||
display: 'inline-block',
|
||||
fontFamily: '"Haas Grot Text R Web", "Helvetica Neue", Helvetica, Arial, sans-serif',
|
||||
fontSize: '18px',
|
||||
fontWeight: '750',
|
||||
lineHeight: '20px',
|
||||
listStyle: 'none',
|
||||
margin: '0',
|
||||
padding: '10px 12px',
|
||||
textAlign: 'center',
|
||||
transition: 'all 200ms',
|
||||
verticalAlign: 'baseline',
|
||||
whiteSpace: 'nowrap',
|
||||
userSelect: 'none',
|
||||
WebkitUserSelect: 'none',
|
||||
touchAction: 'manipulation',
|
||||
}} className="RoundButtonNavBar">{text}</button></a>
|
||||
</div>)
|
||||
}
|
16
front/src/components/chaos/MonInput.tsx
Normal file
16
front/src/components/chaos/MonInput.tsx
Normal file
|
@ -0,0 +1,16 @@
|
|||
//import { useState } from "react";
|
||||
|
||||
interface MonInputProps {
|
||||
text: string;
|
||||
new_focus: () => void;
|
||||
police: string;
|
||||
}
|
||||
|
||||
export default function MonInput({text, new_focus, police}: MonInputProps) {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<input readOnly value={text} onFocus={new_focus} style={{fontFamily: police,width:1000}}></input>
|
||||
</div>
|
||||
)
|
||||
}
|
26
front/src/components/chaos/monButton.tsx
Normal file
26
front/src/components/chaos/monButton.tsx
Normal file
|
@ -0,0 +1,26 @@
|
|||
|
||||
interface MonButtonProps {
|
||||
letter: string;
|
||||
changetext: (arg0: string) => void;
|
||||
sizeFrontw: number;
|
||||
sizeFronth: number;
|
||||
rdmFront: () => void;
|
||||
color: string;
|
||||
}
|
||||
|
||||
|
||||
export default function MonButton({letter,changetext,sizeFrontw,sizeFronth,rdmFront,color}: MonButtonProps) {
|
||||
|
||||
|
||||
|
||||
function clicked() {rdmFront();
|
||||
changetext(letter)}
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button className="key" onClick={clicked} style={{width:sizeFrontw,height:sizeFronth,background:color}}>{letter}</button>
|
||||
</div>
|
||||
)
|
||||
}
|
12
front/src/components/chaos/style.css
Normal file
12
front/src/components/chaos/style.css
Normal file
|
@ -0,0 +1,12 @@
|
|||
|
||||
#keys {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto 1fr;
|
||||
|
||||
|
||||
}
|
||||
|
||||
.key {
|
||||
font-size: 10px;
|
||||
border: solid black;
|
||||
}
|
19
front/src/index.css
Normal file
19
front/src/index.css
Normal file
|
@ -0,0 +1,19 @@
|
|||
/* Define theme colors */
|
||||
:root {
|
||||
--color-verydarkblue: #00204a;
|
||||
--color-darkblue: #005792;
|
||||
--color-lightblue: #00bbf0;
|
||||
--color-yellow: #fdb44b;
|
||||
--color-white: #ffffff;
|
||||
--color-black: #000000;
|
||||
--color-gray: #f5f5f5;
|
||||
--color-red: #ff0000;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
footer {
|
||||
text-align: center;
|
||||
}
|
|
@ -2,13 +2,23 @@ import { BrowserRouter, Route, Routes } from "react-router";
|
|||
import { createRoot } from 'react-dom/client'
|
||||
import MainPage from "./pages/MainPage.tsx";
|
||||
import GamePage from "./pages/GamePage.tsx";
|
||||
import ChaosPage from "./pages/ChaosPage.tsx";
|
||||
|
||||
import ArticlePage from "./pages/ArticlePage.tsx";
|
||||
import './index.css'
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
// Main page
|
||||
<Route path="/" element={<MainPage />} />
|
||||
// Game page
|
||||
<Route path="/game" element={<GamePage />} />
|
||||
<Route path="/chaos" element={<ChaosPage />} />
|
||||
// Article page (dynamic route)
|
||||
<Route path="/article/:id" element={<ArticlePage />} />
|
||||
// Not found
|
||||
<Route path="*" element={<div>Not Found</div>} />
|
||||
</Routes>
|
||||
</BrowserRouter>,
|
||||
</BrowserRouter>
|
||||
)
|
||||
|
|
78
front/src/pages/ArticlePage.tsx
Normal file
78
front/src/pages/ArticlePage.tsx
Normal file
|
@ -0,0 +1,78 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import NavBar from '../components/NavBar';
|
||||
import Footer from '../components/Footer';
|
||||
|
||||
interface Article {
|
||||
id: string;
|
||||
title: string;
|
||||
content: string;
|
||||
author: string;
|
||||
publishedAt: string;
|
||||
editedAt: string;
|
||||
}
|
||||
|
||||
const ArticlePage: React.FC = () => {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const [article, setArticle] = useState<Article | null>(null);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetch(`/api/article/${id}`)
|
||||
.then(response => response.json())
|
||||
.then(data => setArticle(data))
|
||||
.catch(_ => setError('Failed to fetch article'))
|
||||
.finally(() => setLoading(false));
|
||||
}, [id]);
|
||||
|
||||
if (loading) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <div>{error}</div>;
|
||||
}
|
||||
|
||||
if (!article) {
|
||||
return <div>Article not found</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
minHeight: '100vh'
|
||||
}}>
|
||||
<NavBar/>
|
||||
<div style={{
|
||||
borderRadius: '8px',
|
||||
color: 'var(--color-black)',
|
||||
fontFamily: '"Haas Grot Text R Web", "Helvetica Neue", Helvetica, Arial, sans-serif',
|
||||
fontSize: '14px',
|
||||
lineHeight: '20px',
|
||||
padding: '1.25em',
|
||||
paddingBottom: '1em',
|
||||
textAlign: 'left',
|
||||
maxWidth: '60rem',
|
||||
margin: 'auto',
|
||||
}}>
|
||||
<img src="https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fimages8.alphacoders.com%2F438%2F438396.jpg&f=1&nofb=1&ipt=14796576c139b58db62d2d2098392ef2fbaee77911040f7c0d98ab65a69c4644&ipo=images" alt="Article preview" style={{
|
||||
borderRadius: '8px',
|
||||
width: '100%',
|
||||
height: 'auto',
|
||||
marginBottom: '10px',
|
||||
}} />
|
||||
<div>
|
||||
<h1>{article.title}</h1>
|
||||
<p>écrit par {article.author} le {new Date(article.publishedAt).toLocaleDateString()} (Dernière modification le : {new Date(article.editedAt || article.publishedAt).toLocaleDateString()})</p>
|
||||
<hr/>
|
||||
<p>{article.content}</p>
|
||||
</div>
|
||||
</div>
|
||||
<Footer bgcolor='lightblue'/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ArticlePage;
|
86
front/src/pages/ChaosPage.tsx
Normal file
86
front/src/pages/ChaosPage.tsx
Normal file
|
@ -0,0 +1,86 @@
|
|||
import MonButton from "../components/chaos/monButton";
|
||||
import MonInput from "../components/chaos/MonInput";
|
||||
import "../components/chaos/style.css"
|
||||
import { useState } from "react";
|
||||
|
||||
|
||||
export default function ChaosPage(){
|
||||
|
||||
const [array_letter,setArray_letter]=useState(["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"," ","!"]);
|
||||
|
||||
const [sizeFrontw,setSizeFrontw] = useState(16)
|
||||
const [sizeFronth,setSizeFronth] = useState(20)
|
||||
const [color,setColor] = useState("#ffffff")
|
||||
|
||||
function randomFront(){setSizeFronth(Math.floor(Math.random() * (1000)));
|
||||
setSizeFrontw(Math.floor(Math.random() * (1000)));
|
||||
setColor(`#${Math.floor(Math.random() * 16777215).toString(16)}`)}
|
||||
|
||||
function shuffleArray(arr: string[]) {
|
||||
for (let i = arr.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1)); // Choisir un index aléatoire
|
||||
[arr[i], arr[j]] = [arr[j], arr[i]]; // Échanger les éléments
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
|
||||
// console.log(shuffledArray); // Affiche un tableau mélangé
|
||||
|
||||
|
||||
const [entry1,setEntry1] = useState("")
|
||||
function E1(ent:string) {
|
||||
setEntry1(entry1+ent);
|
||||
}
|
||||
const [connarddefocus,setFocus] = useState(()=>E1)
|
||||
|
||||
const [entry2,setEntry2] = useState("")
|
||||
function E2(ent:string) {
|
||||
setEntry2(entry2+ent);
|
||||
}
|
||||
|
||||
function changeFocus(E: (ent:string)=>void) {
|
||||
setArray_letter(shuffleArray(array_letter));
|
||||
setFocus(()=>E);
|
||||
}
|
||||
|
||||
const [entry3,setEntry3] = useState("")
|
||||
function E3(ent:string) {
|
||||
setEntry3(entry3+ent);
|
||||
}
|
||||
|
||||
const [tel,setTel] = useState(0)
|
||||
|
||||
|
||||
return(
|
||||
<div>
|
||||
<h1>Chaos Page</h1>
|
||||
<p>Quel est votre nom ?</p>
|
||||
<MonInput text={entry1} new_focus={()=>changeFocus(E1)} police={""}/>
|
||||
<p>Quel adjectif désigne le mieux Xi Junpin ?</p>
|
||||
<MonInput text={entry2} new_focus={()=>changeFocus(E2)} police={""}/>
|
||||
<p>Combien font 1+1 ?</p>
|
||||
<MonInput text={entry3} new_focus={()=>changeFocus(E3)} police={"Wingdings"}/>
|
||||
<div id = "keys">
|
||||
{array_letter.map((letter) => {return <MonButton
|
||||
letter={letter}
|
||||
changetext={connarddefocus}
|
||||
sizeFrontw={sizeFrontw}
|
||||
sizeFronth={sizeFronth}
|
||||
rdmFront={randomFront}
|
||||
color={color}/> })}
|
||||
</div>
|
||||
<fieldset>
|
||||
<legend>Formulaire super mega bien</legend>
|
||||
<input type="text" placeholder="nom"/>
|
||||
<input type="text" placeholder="prenom"/>
|
||||
<input type="text" placeholder="email"/>
|
||||
<input type="text" placeholder="mdp"/>
|
||||
<input type="text" placeholder="mdp2"/>
|
||||
<label htmlFor="phone">phone : {tel}</label>
|
||||
<input type="range" min="0" max="9999999999" step="1" name="phone" id="phone" value={tel} onChange={(e)=>setTel(parseInt(e.target.value))}/>
|
||||
<input type="submit" value="envoyer"/>
|
||||
</fieldset>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,9 +1,7 @@
|
|||
import { Canvas } from "@react-three/fiber";
|
||||
import { OrbitControls, PerspectiveCamera, Sky } from "@react-three/drei";
|
||||
import * as THREE from "three";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import Ocean from "../components/3d/Ocean";
|
||||
import Axes from "../components/3d/Axes";
|
||||
import Character from "../components/3d/Character";
|
||||
import Floor from "../components/3d/Floor";
|
||||
import Marker from "../components/3d/Marker";
|
||||
|
@ -139,7 +137,7 @@ export default function GamePage() {
|
|||
<Sky sunPosition={[100, 10, 100]} />
|
||||
<OrbitControls />
|
||||
<Ocean />
|
||||
<Axes />
|
||||
{/* <Axes /> */}
|
||||
<Character />
|
||||
<Floor />
|
||||
{/* <Marker position={[0, 1, 0]} color="red" /> */}
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
|
||||
|
||||
import ArticlesSection from '../components/ArticlesSection.tsx'
|
||||
import Footer from '../components/Footer.tsx'
|
||||
import FstSection from '../components/FstSection.tsx'
|
||||
|
||||
export default function MainPage() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Main Page</h1>
|
||||
<>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', minHeight: '100vh' }}>
|
||||
<FstSection />
|
||||
<ArticlesSection />
|
||||
<Footer bgcolor="lightblue"/>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
13
front/src/types.ts
Normal file
13
front/src/types.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
interface Article {
|
||||
id: number;
|
||||
title: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
interface ArticlePreview {
|
||||
id: number;
|
||||
title: string;
|
||||
preview: string;
|
||||
}
|
||||
|
||||
export type { Article, ArticlePreview };
|
|
@ -4,4 +4,28 @@ import react from '@vitejs/plugin-react'
|
|||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
server: {
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://127.0.0.1:2486',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
ws: true,
|
||||
/*
|
||||
configure: (proxy, _options) => {
|
||||
proxy.on('error', (err, _req, _res) => {
|
||||
console.log('proxy error', err);
|
||||
});
|
||||
proxy.on('proxyReq', (proxyReq, req, _res) => {
|
||||
console.log('Sending Request to the Target:', req.method, req.url);
|
||||
});
|
||||
proxy.on('proxyRes', (proxyRes, req, _res) => {
|
||||
console.log('Received Response from the Target:', proxyRes.statusCode, req.url);
|
||||
});
|
||||
},
|
||||
*/
|
||||
//rewrite: path => path.replace(/^\/api/, '')
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
|
|
6
package-lock.json
generated
Normal file
6
package-lock.json
generated
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "projet-nuitinfo-2024",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue