first commit

This commit is contained in:
mrtoine 2025-09-20 14:16:14 +02:00
parent b216a187bd
commit f73c77f548
119 changed files with 4504 additions and 4829 deletions

View file

@ -0,0 +1,24 @@
async function GetAboutDatas(){
const res = await fetch(`${process.env.BACKEND_URL}/cv/about`, {
cache: process.env.CACHE
});
if(!res.ok){
throw new Error("Erreur lors de la lecture des données de contact.")
}
return res.json();
}
export default async function About(){
const about_text = await GetAboutDatas();
return (
<section className="dev-hero">
<div className="container container--narrow">
<span className="eyebrow">Développeur web</span>
<h1 className="headline headline--code">Anthony Violet</h1>
<p className="subheadline">{about_text}</p>
</div>
</section>
);
}

View file

@ -0,0 +1,104 @@
"use client";
import Link from "next/link";
import { useEffect, useState } from "react";
import { usePathname } from "next/navigation";
type ContactData = {
email?: string;
linkedin?: string;
github?: string;
};
export default function ContactComponent() {
const pathname = usePathname();
const onContactPage = pathname === "/contact";
const [data, setData] = useState<ContactData | null>(null);
useEffect(() => {
if (!onContactPage) return;
// Essaye avec BACKEND_URL, sinon fallback vers une route locale hypothétique
const backend = (process.env.BACKEND_URL as string) || "http://127.0.0.1:5000/api";
const url = backend ? `${backend}/contact` : "http://127.0.0.1:5000/api/contact";
fetch(url, { cache: "no-store" })
.then((res) => (res.ok ? res.json() : Promise.reject(new Error("fetch contact failed"))))
.then((json) => setData(json))
.catch(() => setData(null));
}, [onContactPage]);
// Si on n'est pas sur la page /contact => juste un lien
if (!onContactPage) {
return (
<div className="block">
<div className="btn-group">
<Link href="/contact" className="btn btn--primary btn--pill">
Me contacter
</Link>
</div>
</div>
);
}
// Page /contact => liens + formulaire
return (
<div className="block">
<div className="stack">
<div className="btn-group">
{data?.linkedin ? (
<a href={data.linkedin} target="_blank" rel="noopener noreferrer" className="link-button">
LinkedIn
</a>
) : null}
{data?.github ? (
<a href={data.github} target="_blank" rel="noopener noreferrer" className="link-button">
GitHub
</a>
) : null}
{data?.email ? (
<a href={`mailto:${data.email}`} className="link-button">
Email direct
</a>
) : null}
</div>
{data?.email ? (
<form action={`mailto:${data.email}`} method="post" encType="text/plain" className="stack">
<div>
<label htmlFor="name">Votre nom</label>
<input id="name" name="Nom" type="text" required />
</div>
<div>
<label htmlFor="email">Votre email</label>
<input id="email" name="Email" type="email" required />
</div>
<div>
<label htmlFor="subject">Sujet</label>
<input id="subject" name="Sujet" type="text" placeholder="Prise de contact" />
</div>
<div>
<label htmlFor="message">Message</label>
<textarea id="message" name="Message" required />
</div>
<div className="btn-group">
<button type="submit" className="btn btn--primary btn--pill">
Envoyer
</button>
<button type="reset" className="btn btn--ghost">
Réinitialiser
</button>
</div>
</form>
) : (
<p className="u-muted">Une erreur est survenue. Vous pouvez tout de même me contacter directement par email : <a href="mailto:violet.anthony90@gmail.com">Me joindre par email</a></p>
)}
</div>
</div>
);
}

View file

@ -0,0 +1,148 @@
"use client";
// app/components/Header.tsx
import Link from "next/link";
import { useEffect, useState } from "react";
export default function Header() {
const [theme, setTheme] = useState<"light" | "dark">("light");
const [isTop, setIsTop] = useState(true);
// Initialise le thème en fonction du localStorage ou de la préférence système
useEffect(() => {
const saved = typeof window !== "undefined" ? localStorage.getItem("theme") : null;
const prefersDark = typeof window !== "undefined" && window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches;
const initial = (saved === "light" || saved === "dark") ? (saved as "light" | "dark") : (prefersDark ? "dark" : "light");
setTheme(initial);
document.documentElement.setAttribute("data-theme", initial);
}, []);
// Détecte si l'on est tout en haut de la page
useEffect(() => {
const onScroll = () => setIsTop(window.scrollY <= 0);
onScroll(); // état initial au chargement
window.addEventListener("scroll", onScroll, { passive: true });
return () => window.removeEventListener("scroll", onScroll);
}, []);
const toggleTheme = () => {
const next = theme === "dark" ? "light" : "dark";
setTheme(next);
document.documentElement.setAttribute("data-theme", next);
try {
localStorage.setItem("theme", next);
} catch {}
};
return (
<header className={isTop ? "minimalist-pro is-top" : "minimalist-pro"}>
<div className="logo">
<Link href="/">
<span className="name">Anthony Violet</span>
<span className="tagline">Développeur Freelance & Digital Nomad</span>
</Link>
</div>
<nav>
<ul>
<li><Link href="/projects" >Projets</Link></li>
<li><Link href="/competences">Compétences</Link></li>
<li><Link href="#blog">Blog Voyage</Link></li>
<li><Link href="/contact">Contact</Link></li>
<li><Link href="https://fr.malt.be/profile/anthonyviolet1" target="_blank">Malt</Link></li>
<li><Link href="https://www.linkedin.com/in/anthony-violet/" target="_blank">LinkedIn</Link></li>
</ul>
</nav>
<div className="cta">
<button
type="button"
className="theme-toggle__btn"
aria-label={theme === "dark" ? "Activer le thème clair" : "Activer le thème sombre"}
onClick={toggleTheme}
title={theme === "dark" ? "Mode clair" : "Mode sombre"}
>
{theme === "dark" ? "☀️" : "🌙"}
</button>
<Link href="mailto:tonemail@domaine.com" className="button">Travaillons ensemble</Link>
</div>
</header>
/*<header className="cyberpunk">
<div className="glitch-logo">
<Link href="/">
<span data-text="Anthony Violet">Anthony Violet</span>
<span className="subtitle">// Développeur Full Stack & Créateur de Jeux</span>
</Link>
</div>
<nav className="neon-nav">
<ul>
<li><Link href="#projets" className="neon-link">/projets</Link></li>
<li><Link href="#tech" className="neon-link">/tech-stack</Link></li>
<li><Link href="#blog" className="neon-link">/toine-traveller</Link></li>
<li><Link href="#contact" className="neon-link">/contact@terminal</Link></li>
</ul>
</nav>
<div className="status-bar">
<span>En ligne </span>
<span>Disponible pour missions</span>
</div>
</header>
<header className="sidebar-header">
<div className="profile-pic">
<img src="ton-avatar.jpg" alt="Anthony Violet" />
</div>
<div className="profile-info">
<h1>Anthony Violet</h1>
<p>Freelance Dev | Digital Nomad | Builder de jeux & apps</p>
</div>
<nav>
<ul>
<li><Link href="#projets"><i className="icon-code"></i> Projets</Link></li>
<li><Link href="#about"><i className="icon-user"></i> À propos</Link></li>
<li><Link href="#blog"><i className="icon-globe"></i> Blog</Link></li>
<li><Link href="#contact"><i className="icon-mail"></i> contact</Link></li>
</ul>
</nav>
<div className="social-links">
<Link href="https://github.com/tonuser" target="_blank"><i className="icon-github"></i></Link>
<Link href="https://linkedin.com/in/anthony-violet" target="_blank"><i className="icon-linkedin"></i></Link>
</div>
</header>
<header className="interactive-header">
<div className="typewriter">
<h1>Bonjour, je suis <span className="typed-text">Anthony Violet</span></h1>
<p>Je code en <span className="tech-tag">JavaScript</span>, <span className="tech-tag">Python</span>, et j'explore le monde.</p>
</div>
<nav>
<ul>
<li><Link href="#projets">Mes réalisations</Link></li>
<li><Link href="#cv">Mon CV</Link></li>
<li><Link href="#blog">Mes aventures</Link></li>
</ul>
</nav>
<div className="language-switcher">
<button>FR</button>
<button>EN</button>
</div>
</header>
<header className="dark-header">
<div className="container">
<Link href="/" className="logo">
<img src="logo.svg" alt="Logo Anthony Violet" />
</Link>
<nav>
<ul>
<li><Link href="#projets">Projets</Link></li>
<li><Link href="#services">Services</Link></li>
<li><Link href="#blog">Blog</Link></li>
<li><Link href="#contact">Me contacter</Link></li>
</ul>
</nav>
</div>
<div className="theme-toggle">
<button id="theme-button"><i className="icon-moon"></i></button>
</div>
</header>*/
);
}

View file

@ -0,0 +1,35 @@
async function GetServices() {
const res = await fetch(`${process.env.BACKEND_URL}/services`, {
cache: process.env.CACHE
});
if (!res.ok) {
throw new Error("Erreur lors de la lecture des services du CV.")
}
return res.json();
}
export default async function Services() {
const services = await GetServices();
return (
<section id="services" className="section">
<div className="container">
<h2 className="section-title">Services proposés</h2>
<ul className="grid-3">
{services.map((service: any) => (
<li key={service.id} className="service-card card feature">
<span className="feature__icon" aria-hidden="true" />
<div>
<h3 className="feature__title">{service.name}</h3>
{"description" in service && service.description ? (
<p className="u-muted">{service.description}</p>
) : null}
</div>
</li>
))}
</ul>
</div>
</section>
)
}

View file

@ -0,0 +1,73 @@
async function GetSkills() {
const res = await fetch(`${process.env.BACKEND_URL}/cv/skills`, {
cache: process.env.CACHE
});
if (!res.ok) {
throw new Error("Erreur lors du chargement des skills du CV");
}
return res.json();
}
export default async function Skills(){
const skills = await GetSkills();
const obj: Record<string, string[]> = Array.isArray(skills) ? (skills[0] ?? {}) : {};
const entries = Object.entries(obj) as [string, string[]][];
if (entries.length === 0) {
return (
<section id="skills" className="section">
<div className="container">
<h2 className="section-title">Mes compétences</h2>
<p className="u-muted">Aucune compétence à afficher.</p>
</div>
</section>
);
}
return (
/*
*
{mySkillsEntries.length > 0 && (
<section id="competences" className="section">
<div className="container">
<h2 className="section-title">Compétences clés</h2>
<ul className="skill-grid">
{mySkillsEntries.map(([category, items]) => (
<li key={category} className="skill-card">
<h3 className="skill-card__title">{category}</h3>
<div className="skill-card__tags">
{items.map((it) => (
<span key={it} className="badge">{it}</span>
))}
</div>
<div className="skill-meter" aria-hidden="true">
<div className="skill-meter__bar" style={{["--level" as any]: "75%"}}/>
</div>
</li>
))}
</ul>
</div>
</section>
)}
* */
<section id="competences" className="section">
<div className="container">
<h2 className="section-title">Mes compétences</h2>
<ul className="skill-grid">
{entries.map(([category, skills]) => (
<li key={category} className="skill-card">
<h3 className="skill-card__title">{category}</h3>
<div className="skill-card__tags">
{skills.map((skill: string) => (
<span key={skill} className="badge">{skill}</span>
))}
</div>
</li>
))}
</ul>
</div>
</section>
);
}