Refactor Docker setup, enhance README, add legal pages, and implement site footer

This commit is contained in:
Puechberty Arthur
2026-04-01 22:58:00 +02:00
parent 9d6ac9b46d
commit 19652d5fb9
11 changed files with 537 additions and 53 deletions
+16 -24
View File
@@ -1,34 +1,26 @@
# === Étape 1 : Build ===
FROM node:20-alpine AS builder
FROM node:22-alpine AS base
WORKDIR /app
ENV NEXT_TELEMETRY_DISABLED=1
# Copier les fichiers de dépendances pour profiter du cache Docker
COPY package*.json ./
# Installer uniquement ce qu'il faut pour le build
FROM base AS deps
COPY package.json package-lock.json ./
RUN npm ci
# Copier tout le code
FROM deps AS dev
COPY . .
EXPOSE 3000
CMD ["npm", "run", "dev", "--", "--hostname", "0.0.0.0", "--port", "3000"]
# Build Next.js pour la production
FROM deps AS builder
COPY . .
RUN npm run build
# === Étape 2 : Runner léger ===
FROM node:20-alpine AS runner
WORKDIR /app
# Copier uniquement ce qui est nécessaire pour la prod
COPY --from=builder /app/package*.json ./
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/public ./public
# Mode production
FROM base AS runner
ENV NODE_ENV=production
ENV HOSTNAME=0.0.0.0
ENV PORT=3000
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
EXPOSE 3000
# Lancer le serveur Next.js
CMD ["npm", "start"]
CMD ["node", "server.js"]
+25
View File
@@ -0,0 +1,25 @@
Copyright (c) 2026 Arthur
Licence pour tous les projets Arthur
1. Définition
Cette licence définit les droits et obligations concernant l'utilisation, la modification et la redistribution du code fourni par l'auteur.
2. Autorisation d'utilisation
Vous êtes libre d'utiliser ce code pour vos projets personnels ou commerciaux. L'utilisation doit inclure une mention de l'auteur dune manière libre (ex: "inspiré de ArthurP").
3. Modification
Vous pouvez modifier, adapter ou améliorer le code pour vos besoins. Les modifications doivent être identifiées comme telles et ne doivent pas être présentées comme l'original.
4. Redistribution
- Le code original **ne peut pas être redistribué tel quel**.
- Les versions modifiées peuvent être partagées, sous réserve de mentionner l'auteur original.
5. Usage commercial
Lusage commercial des versions modifiées est autorisé. Vous pouvez générer des revenus avec votre version modifiée.
6. Attribution
L'auteur original doit être cité dune manière libre, mais visible, sur tout projet utilisant ce code ou ses dérivés.
7. Responsabilité
Le code est fourni "tel quel", sans garantie daucune sorte. Lauteur décline toute responsabilité pour tout dommage direct ou indirect résultant de lutilisation du code.
+16 -22
View File
@@ -1,10 +1,11 @@
# BlocNote
Bloc-notes Markdown en ligne, rapide et sans inscription.
Bloc-notes Markdown en ligne, rapide, sans inscription et pensé pour l'auto-hébergement.
## Site public
## Site
- Production: https://blocnote.arthurp.fr
- Contact: https://contact.arthurp.fr ou contact@arthurp.fr
## Stack technique
@@ -13,24 +14,24 @@ Bloc-notes Markdown en ligne, rapide et sans inscription.
- TypeScript
- ESLint
## Fonctionnalites
## Fonctionnalités
- Edition Markdown avec apercu en temps reel
- Édition Markdown avec aperçu en temps réel
- Sauvegarde automatique des notes
- Recherche et tri des notes
- Notes epinglees
- Notes épinglées
- Export d'une note
- Mode clair / sombre
- Raccourcis clavier
## Lancement local
Pre-requis:
Prérequis:
- Node.js 22+
- npm
Installation et demarrage:
Installation et démarrage:
```bash
npm ci
@@ -39,25 +40,18 @@ npm run dev
Application disponible sur http://localhost:3000
## Scripts
## Docker
Deux services sont fournis via [docker-compose.yml](docker-compose.yml):
- blocnote-dev pour le développement sur le port 3000
- blocnote-prod pour l'exécution de production sur le port 3016
```bash
npm run dev # mode developpement
npm run lint # verification lint
npm run build # build production
npm run start # lancer le build production
docker compose --profile dev up
docker compose --profile prod up -d
```
## Variables d'environnement
Le projet peut utiliser:
- NEXT_PUBLIC_SITE_URL (exemple: https://blocnote.arthurp.fr)
## Docker
Un environnement Docker est disponible via [docker-compose.yml](docker-compose.yml).
```bash
docker compose up -d
```
+93
View File
@@ -0,0 +1,93 @@
import Link from "next/link";
const navigationLinks = [
{ href: "/", label: "Accueil" },
{ href: "/mentions-legales", label: "Mentions légales" },
{ href: "/confidentialite", label: "Confidentialité" },
];
const contactLinks = [
{ href: "https://contact.arthurp.fr", label: "contact.arthurp.fr", external: true },
{ href: "mailto:contact@arthurp.fr", label: "contact@arthurp.fr", external: true },
];
export default function SiteFooter() {
const year = new Date().getFullYear();
return (
<footer className="site-footer" aria-label="Pied de page">
<div className="site-footer-inner">
<div className="site-footer-brand">
<div className="site-footer-logo">
<img
className="site-footer-mark"
src="/icon.svg"
alt=""
aria-hidden="true"
width={34}
height={34}
/>
<span>BlocNote</span>
</div>
<p className="site-footer-description">
BlocNote est un bloc-notes Markdown rapide, auto-hébergé et pensé pour écrire sans friction, organiser ses idées et garder le contrôle sur ses données.
</p>
<div className="site-footer-badges">
<span className="site-footer-badge">Auto-hébergé sur Proxmox</span>
<span className="site-footer-badge">Fait avec </span>
</div>
</div>
<nav className="site-footer-column" aria-label="Navigation du site">
<h2 className="site-footer-heading">Navigation</h2>
<div className="site-footer-links">
{navigationLinks.map((link) => (
<Link key={link.href} href={link.href} className="site-footer-link">
{link.label}
</Link>
))}
</div>
</nav>
<div className="site-footer-column">
<h2 className="site-footer-heading">Contact</h2>
<div className="site-footer-links">
{contactLinks.map((link) =>
link.external ? (
<a
key={link.href}
href={link.href}
className="site-footer-link site-footer-link-muted"
target="_blank"
rel="noreferrer"
>
{link.label}
</a>
) : (
<Link key={link.href} href={link.href} className="site-footer-link site-footer-link-muted">
{link.label}
</Link>
)
)}
</div>
</div>
<div className="site-footer-column">
<h2 className="site-footer-heading">Service</h2>
<div className="site-footer-links">
<span className="site-footer-link site-footer-link-muted">Edition Markdown</span>
<span className="site-footer-link site-footer-link-muted">Sauvegarde locale</span>
<span className="site-footer-link site-footer-link-muted">Export et recherche</span>
</div>
</div>
</div>
<div className="site-footer-bottom">
<span>
© {year} <strong>BlocNote</strong>. Tous droits réservés.
</span>
<span>Un outil sobre pour écrire vite, garder ses notes et rester indépendant.</span>
</div>
</footer>
);
}
+56
View File
@@ -0,0 +1,56 @@
import type { Metadata } from "next";
import Link from "next/link";
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || "https://blocnote.arthurp.fr";
export const metadata: Metadata = {
title: "Confidentialité",
description: "Politique de confidentialité de BlocNote.",
alternates: {
canonical: `${siteUrl}/confidentialite`,
},
};
export default function ConfidentialitePage() {
return (
<main className="legal-page">
<div className="legal-page-hero">
<span className="legal-page-kicker">Vie privée</span>
<h1 className="legal-page-title">Confidentialité</h1>
<p className="legal-page-intro">
BlocNote est conçu pour garder les notes localement dans le navigateur et éviter la collecte de données inutile.
</p>
</div>
<div className="legal-page-grid">
<section className="legal-card">
<h2>Stockage des notes</h2>
<p>
Les notes sont enregistrées dans le navigateur via <Link className="legal-page-link" href="/">localStorage</Link>. Elles ne sont pas envoyées à un serveur BlocNote pour la synchronisation.
</p>
</section>
<section className="legal-card">
<h2>Données collectées</h2>
<p>
L'application n'exige pas de compte utilisateur. Les contenus créés restent sous le contrôle direct de la personne qui utilise le site.
</p>
</section>
<section className="legal-card">
<h2>Partage et support</h2>
<p>
Si vous souhaitez signaler un souci ou poser une question liée à la confidentialité, contactez <a className="legal-page-link" href="https://contact.arthurp.fr" target="_blank" rel="noreferrer">contact.arthurp.fr</a> ou <a className="legal-page-link" href="mailto:contact@arthurp.fr">contact@arthurp.fr</a>.
</p>
</section>
<section className="legal-card">
<h2>Évolution</h2>
<p>
Cette page peut évoluer si le fonctionnement technique du service change. La version la plus récente reste celle publiée sur le site.
</p>
</section>
</div>
</main>
);
}
+232 -2
View File
@@ -64,17 +64,25 @@
}
html, body {
height: 100%;
overflow: hidden;
min-height: 100%;
}
body {
font-family: var(--font-sans);
background: var(--bg-primary);
color: var(--text-primary);
overflow-x: hidden;
overflow-y: auto;
transition: background var(--transition), color var(--transition);
}
.site-shell {
display: flex;
flex-direction: column;
min-height: 100vh;
width: 100%;
}
/* ===== Layout ===== */
.app-container {
display: flex;
@@ -82,6 +90,200 @@ body {
overflow: hidden;
}
/* ===== Site Footer ===== */
.site-footer {
border-top: 1px solid var(--border-color);
background:
radial-gradient(circle at top left, rgba(59, 130, 246, 0.14), transparent 34%),
linear-gradient(180deg, var(--bg-secondary), var(--bg-primary));
padding: 40px 24px 28px;
}
.site-footer-inner {
max-width: 1200px;
margin: 0 auto;
display: grid;
grid-template-columns: minmax(0, 1.5fr) repeat(3, minmax(0, 1fr));
gap: 28px;
}
.site-footer-brand {
display: flex;
flex-direction: column;
gap: 16px;
}
.site-footer-logo {
display: inline-flex;
align-items: center;
gap: 10px;
font-size: 22px;
font-weight: 700;
letter-spacing: -0.4px;
}
.site-footer-mark {
display: block;
width: 34px;
height: 34px;
border-radius: 10px;
}
.site-footer-description {
max-width: 34rem;
font-size: 15px;
line-height: 1.7;
color: var(--text-secondary);
}
.site-footer-badges {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.site-footer-badge {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 8px 12px;
border: 1px solid var(--border-color);
border-radius: 999px;
background: var(--bg-primary);
color: var(--text-secondary);
font-size: 12px;
}
.site-footer-column {
display: flex;
flex-direction: column;
gap: 14px;
}
.site-footer-heading {
font-size: 13px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--text-tertiary);
}
.site-footer-links {
display: flex;
flex-direction: column;
gap: 10px;
}
.site-footer-link {
color: var(--text-primary);
text-decoration: none;
font-size: 15px;
line-height: 1.5;
transition: color var(--transition);
}
.site-footer-link:hover {
color: var(--accent);
}
.site-footer-link-muted {
color: var(--text-secondary);
}
.site-footer-bottom {
max-width: 1200px;
margin: 28px auto 0;
padding-top: 20px;
border-top: 1px solid var(--border-light);
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
flex-wrap: wrap;
color: var(--text-tertiary);
font-size: 13px;
}
.site-footer-bottom strong {
color: var(--text-primary);
}
.legal-page {
flex: 1;
width: 100%;
max-width: 1100px;
margin: 0 auto;
padding: 80px 24px 96px;
}
.legal-page-hero {
display: flex;
flex-direction: column;
gap: 14px;
max-width: 46rem;
margin-bottom: 32px;
}
.legal-page-kicker {
font-size: 12px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.12em;
color: var(--accent);
}
.legal-page-title {
font-size: clamp(2rem, 4vw, 3.2rem);
line-height: 1.05;
letter-spacing: -0.05em;
}
.legal-page-intro {
font-size: 18px;
line-height: 1.7;
color: var(--text-secondary);
}
.legal-page-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 18px;
}
.legal-card {
padding: 24px;
border: 1px solid var(--border-color);
border-radius: var(--radius-lg);
background: linear-gradient(180deg, var(--bg-secondary), var(--bg-primary));
box-shadow: var(--shadow-sm);
}
.legal-card h2 {
margin-bottom: 12px;
font-size: 20px;
letter-spacing: -0.02em;
}
.legal-card p,
.legal-card li {
font-size: 15px;
line-height: 1.7;
color: var(--text-secondary);
}
.legal-card ul {
padding-left: 18px;
}
.legal-page-link {
color: var(--accent);
text-decoration: none;
}
.legal-page-link:hover {
text-decoration: underline;
}
/* ===== Loading ===== */
.loading-screen {
display: flex;
@@ -898,6 +1100,26 @@ body {
.preview-pane {
padding: 14px 16px;
}
.site-footer {
padding: 32px 20px 24px;
}
.site-footer-inner {
grid-template-columns: 1fr 1fr;
}
.site-footer-brand {
grid-column: 1 / -1;
}
.legal-page {
padding: 64px 20px 88px;
}
.legal-page-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 480px) {
@@ -913,6 +1135,14 @@ body {
width: 48px;
min-width: 48px;
}
.site-footer-inner {
grid-template-columns: 1fr;
}
.site-footer-bottom {
align-items: flex-start;
}
}
/* ===== Accessibility ===== */
+5
View File
@@ -1,6 +1,7 @@
import type { Metadata, Viewport } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import SiteFooter from "./components/SiteFooter";
const geistSans = Geist({
variable: "--font-geist-sans",
@@ -95,6 +96,7 @@ export const metadata: Metadata = {
},
icons: {
icon: [
{ url: "/icon.svg", type: "image/svg+xml" },
{ url: "/favicon.ico", sizes: "any" },
{ url: "/icon-192.png", sizes: "192x192", type: "image/png" },
{ url: "/icon-512.png", sizes: "512x512", type: "image/png" },
@@ -151,7 +153,10 @@ export default function RootLayout({
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
<a href="#main-content" className="skip-to-content">Aller au contenu principal</a>
<div className="site-shell">
{children}
<SiteFooter />
</div>
</body>
</html>
);
+56
View File
@@ -0,0 +1,56 @@
import type { Metadata } from "next";
import Link from "next/link";
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || "https://blocnote.arthurp.fr";
export const metadata: Metadata = {
title: "Mentions légales",
description: "Mentions légales du site BlocNote.",
alternates: {
canonical: `${siteUrl}/mentions-legales`,
},
};
export default function MentionsLegalesPage() {
return (
<main className="legal-page">
<div className="legal-page-hero">
<span className="legal-page-kicker">Informations légales</span>
<h1 className="legal-page-title">Mentions légales</h1>
<p className="legal-page-intro">
Les informations ci-dessous résument l'éditeur du site, les contacts utiles et le cadre technique de BlocNote.
</p>
</div>
<div className="legal-page-grid">
<section className="legal-card">
<h2>Éditeur du site</h2>
<p>
BlocNote est édité par Arthur P. Le site est accessible à l'adresse <Link className="legal-page-link" href="/">blocnote.arthurp.fr</Link>.
</p>
</section>
<section className="legal-card">
<h2>Contact</h2>
<p>
Pour toute demande, utilisez <a className="legal-page-link" href="https://contact.arthurp.fr" target="_blank" rel="noreferrer">contact.arthurp.fr</a> ou écrivez à <a className="legal-page-link" href="mailto:contact@arthurp.fr">contact@arthurp.fr</a>.
</p>
</section>
<section className="legal-card">
<h2>Hébergement</h2>
<p>
Le site est auto-hébergé sur Proxmox, avec une mise en production Docker pour servir l'application Next.js.
</p>
</section>
<section className="legal-card">
<h2>Propriété intellectuelle</h2>
<p>
Le contenu, l'identité visuelle et le code source de BlocNote restent protégés par le droit d'auteur. Toute réutilisation doit respecter les droits du titulaire.
</p>
</section>
</div>
</main>
);
}
+12
View File
@@ -9,5 +9,17 @@ export default function sitemap(): MetadataRoute.Sitemap {
changeFrequency: "weekly",
priority: 1.0,
},
{
url: `${siteUrl}/mentions-legales`,
lastModified: new Date(),
changeFrequency: "yearly",
priority: 0.3,
},
{
url: `${siteUrl}/confidentialite`,
lastModified: new Date(),
changeFrequency: "yearly",
priority: 0.3,
},
];
}
+24 -4
View File
@@ -1,11 +1,31 @@
services:
web:
build: .
blocnote-dev:
profiles: ["dev"]
build:
context: .
target: dev
container_name: blocnote-dev
ports:
- "3000:3000"
volumes:
- ./:/app
- /app/node_modules
- /app/.next
environment:
- NODE_ENV=development
command: npm run dev
NEXT_TELEMETRY_DISABLED: "1"
NODE_ENV: development
blocnote-prod:
profiles: ["prod"]
build:
context: .
target: runner
container_name: blocnote-prod
restart: unless-stopped
ports:
- "3016:3000"
env_file:
- .env
environment:
NEXT_TELEMETRY_DISABLED: "1"
NODE_ENV: production
+1
View File
@@ -3,6 +3,7 @@ import type { NextConfig } from "next";
const nextConfig: NextConfig = {
compress: true,
poweredByHeader: false,
output: "standalone",
async headers() {
return [
{