feat: refactor Docker setup, enhance README, and add legal pages with environment variable support

This commit is contained in:
Puechberty Arthur
2026-04-01 23:55:17 +02:00
parent 05bfb07286
commit de50801cfc
13 changed files with 443 additions and 107 deletions
+22 -24
View File
@@ -1,34 +1,32 @@
# === Étape 1 : Build ===
FROM node:20-alpine AS builder
FROM node:22-alpine AS base
WORKDIR /app
ARG SITE_URL=https://clock.arthurp.fr
ARG CONTACT_URL=https://contact.arthurp.fr
ARG CONTACT_EMAIL=contact@arthurp.fr
ENV SITE_URL=${SITE_URL}
ENV CONTACT_URL=${CONTACT_URL}
ENV CONTACT_EMAIL=${CONTACT_EMAIL}
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.
+41 -47
View File
@@ -1,73 +1,67 @@
# Clock - Horloge En Ligne
# Clock
Application d'horloge en ligne (Next.js + TypeScript) avec affichage plein ecran, modes digital/analogique/flip, fuseaux horaires et themes.
Horloge en ligne construite avec Next.js, React et TypeScript.
## Projet en ligne
## Site public
Lien public: https://clock.arthurp.fr
https://clock.arthurp.fr
Ce lien est volontairement present dans ce README pour renforcer le backlink vers le projet en production.
## Fonctionnalites
- Horloge en temps reel (rafraichissement fin)
- Modes: `digital`, `analog`, `flip`
- Format horaire: `12h` / `24h`
- Affichage optionnel des secondes
- Selection de fuseau horaire (liste IANA)
- Themes visuels
- Parametres persistants dans le navigateur
- URL partageable avec les parametres
## SEO
- `robots.ts` et `sitemap.ts` configures
- Pages `loading`, `error`, `not-found`
- Balises et structure optimisees pour l'indexation
## Parametres URL
| Parametre | Valeurs | Description |
|---|---|---|
| `tz` | ex: `Europe/Paris` | Fuseau horaire |
| `type` | `digital`, `analog`, `flip` | Type d'horloge |
| `format` | `12h`, `24h` | Format horaire |
| `seconds` | `true`, `false` | Afficher les secondes |
| `theme` | id du theme | Theme visuel |
Exemple:
`https://clock.arthurp.fr?tz=Europe/Paris&type=analog&format=24h&seconds=true&theme=midnight`
## Lancer le projet
## Lancer en local
Prerequis:
- Node.js 18+
- Node.js 22+
- npm
Installation et dev:
Commandes:
```bash
npm install
npm run dev
```
Build production:
## Build production local
```bash
npm run build
npm start
```
## Docker
## Stack
Mode developpement:
- Next.js
- React
- TypeScript
- Tailwind CSS
```bash
docker compose --profile dev up --build
```
Mode production:
```bash
docker compose --profile prod up --build -d
```
Services compose:
- `clock-dev`: serveur dev sur `http://localhost:3000`
- `clock-prod`: serveur prod sur `http://localhost:3007`
Variables `.env` utilisees:
- `SITE_URL`
- `CONTACT_URL`
- `CONTACT_EMAIL`
- `NODE_ENV`
- `PORT`
- `HOSTNAME`
- `TZ`
- `NEXT_TELEMETRY_DISABLED`
## Pages legales
- `/mentions-legales`
- `/politique-confidentialite`
## Licence
MIT
Tous droits reserves - © 2026 Arthur P.
+35 -5
View File
@@ -1,11 +1,41 @@
services:
web:
build: .
clock-dev:
profiles: ["dev"]
build:
context: .
target: dev
args:
SITE_URL: ${SITE_URL}
CONTACT_URL: ${CONTACT_URL}
CONTACT_EMAIL: ${CONTACT_EMAIL}
container_name: clock-dev
ports:
- "3000:3000"
volumes:
- ./:/app
- .:/app
- /app/node_modules
- /app/.next
env_file:
- .env
environment:
- NODE_ENV=development
command: npm run dev
NEXT_TELEMETRY_DISABLED: "1"
NODE_ENV: development
clock-prod:
profiles: ["prod"]
build:
context: .
target: runner
args:
SITE_URL: ${SITE_URL}
CONTACT_URL: ${CONTACT_URL}
CONTACT_EMAIL: ${CONTACT_EMAIL}
container_name: clock-prod
restart: unless-stopped
ports:
- "3007:3000"
env_file:
- .env
environment:
NEXT_TELEMETRY_DISABLED: "1"
NODE_ENV: production
+2
View File
@@ -1,6 +1,8 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
output: 'standalone',
// Optimisations de production
reactStrictMode: true,
+133 -17
View File
@@ -309,33 +309,149 @@ select {
/* ===== Footer SEO ===== */
.seo-footer {
max-width: 900px;
max-width: 1100px;
margin: 0 auto;
padding: 2rem 1.5rem 3rem;
text-align: center;
color: rgba(148, 163, 184, 0.6);
font-size: 0.85rem;
border-top: 1px solid rgba(255, 255, 255, 0.06);
padding: 3rem 1.5rem 2.25rem;
color: rgba(148, 163, 184, 0.75);
border-top: 1px solid rgba(255, 255, 255, 0.08);
background: linear-gradient(
180deg,
rgba(15, 23, 42, 0) 0%,
rgba(15, 23, 42, 0.35) 20%,
rgba(15, 23, 42, 0.65) 100%
);
}
.seo-footer nav {
margin-top: 1rem;
.footer-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 2rem;
margin-bottom: 2rem;
}
.seo-footer nav ul {
display: flex;
justify-content: center;
gap: 1.5rem;
.seo-footer h2 {
font-size: 1rem;
font-weight: 650;
letter-spacing: 0.02em;
margin-bottom: 0.9rem;
color: rgba(248, 250, 252, 0.95);
}
.seo-footer ul {
list-style: none;
padding: 0;
margin: 0;
display: grid;
gap: 0.65rem;
}
.seo-footer nav a {
color: rgba(148, 163, 184, 0.7);
.seo-footer a {
color: rgba(148, 163, 184, 0.82);
text-decoration: none;
transition: color 0.2s;
transition: color 0.2s ease;
}
.seo-footer nav a:hover {
color: rgba(248, 250, 252, 0.9);
.seo-footer a:hover {
color: rgba(248, 250, 252, 0.98);
}
.footer-bottom {
border-top: 1px solid rgba(255, 255, 255, 0.08);
padding-top: 1rem;
font-size: 0.9rem;
display: grid;
gap: 0.35rem;
}
.footer-bottom p {
margin: 0;
}
/* ===== Pages legales ===== */
.legal-page {
width: min(100% - 2rem, 760px);
margin: 0 auto;
padding: 3rem 0 4rem;
color: rgba(226, 232, 240, 0.92);
}
.legal-header {
border-bottom: 1px solid rgba(255, 255, 255, 0.12);
padding-bottom: 1.1rem;
}
.legal-header h1 {
font-size: clamp(1.8rem, 3vw, 2.25rem);
line-height: 1.15;
letter-spacing: -0.01em;
color: rgba(248, 250, 252, 0.98);
font-weight: 650;
}
.legal-header p {
margin-top: 0.45rem;
color: rgba(148, 163, 184, 0.85);
font-size: 0.92rem;
}
.legal-sections {
margin-top: 1.8rem;
}
.legal-sections section {
padding-top: 1.1rem;
}
.legal-sections section + section {
border-top: 1px solid rgba(255, 255, 255, 0.08);
margin-top: 1.2rem;
}
.legal-sections h2 {
font-size: 1.15rem;
font-weight: 600;
color: rgba(248, 250, 252, 0.96);
margin-bottom: 0.5rem;
}
.legal-sections p {
line-height: 1.65;
color: rgba(203, 213, 225, 0.95);
}
.legal-sections a {
color: rgba(224, 242, 254, 0.94);
}
@media (max-width: 640px) {
.legal-page {
width: min(100% - 1.25rem, 760px);
padding: 2.25rem 0 3rem;
}
.legal-sections section {
padding-top: 0.95rem;
}
}
@media (max-width: 900px) {
.footer-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 640px) {
.seo-footer {
padding: 2.5rem 1rem 1.75rem;
}
.footer-grid {
grid-template-columns: 1fr;
gap: 1.5rem;
margin-bottom: 1.5rem;
}
.footer-bottom {
font-size: 0.85rem;
}
}
+1 -1
View File
@@ -1,8 +1,8 @@
import type { Metadata, Viewport } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import { SITE_URL } from '@/lib/env';
const SITE_URL = 'https://clock.arthurp.fr';
const SITE_NAME = 'Horloge en ligne';
const SITE_DESCRIPTION = 'Horloge en ligne gratuite avec affichage plein écran. Choisissez entre horloge numérique ou analogique, personnalisez les couleurs et le fuseau horaire. Heure exacte en temps réel.';
+51
View File
@@ -0,0 +1,51 @@
import type { Metadata } from 'next';
import { CONTACT_EMAIL, CONTACT_MAILTO, CONTACT_URL, SITE_URL } from '@/lib/env';
export const metadata: Metadata = {
title: 'Mentions legales',
description: `Mentions legales du site ${SITE_URL}`,
};
export default function MentionsLegalesPage() {
return (
<main className="legal-page">
<header className="legal-header">
<h1>Mentions legales</h1>
<p>Derniere mise a jour: 01/04/2026</p>
</header>
<div className="legal-sections">
<section>
<h2>Editeur du site</h2>
<p>Site: {SITE_URL.replace(/^https?:\/\//, '')}</p>
<p>Responsable de publication: Arthur P.</p>
<p>
Contact: <a className="underline underline-offset-4" href={CONTACT_URL}>{CONTACT_URL.replace(/^https?:\/\//, '')}</a> ou{' '}
<a className="underline underline-offset-4" href={CONTACT_MAILTO}>{CONTACT_EMAIL}</a>
</p>
</section>
<section>
<h2>Hebergement</h2>
<p>Service auto-heberge sur infrastructure Proxmox.</p>
</section>
<section>
<h2>Propriete intellectuelle</h2>
<p>
Les contenus presentes sur ce site (textes, elements graphiques et code source specifique)
restent proteges par les lois en vigueur. Toute reproduction non autorisee est interdite.
</p>
</section>
<section>
<h2>Liens externes</h2>
<p>
Le site peut contenir des liens vers des services externes. Le responsable du site ne peut
pas etre tenu responsable du contenu de ces services tiers.
</p>
</section>
</div>
</main>
);
}
+39 -7
View File
@@ -1,4 +1,5 @@
import { ClockApp } from '@/components/ClockApp';
import { CONTACT_EMAIL, CONTACT_MAILTO, CONTACT_URL } from '@/lib/env';
// Contenu SEO visible en bas de page pour l'indexation Google
function SEOContent() {
@@ -137,6 +138,8 @@ function SEOContent() {
}
export default function Home() {
const currentYear = new Date().getFullYear();
return (
<>
<a href="#main-clock" className="skip-to-content">
@@ -145,17 +148,46 @@ export default function Home() {
<ClockApp />
<SEOContent />
<footer className="seo-footer" role="contentinfo">
<p>
&copy; {new Date().getFullYear()} Horloge en ligne &mdash; Heure exacte gratuite en temps réel.
Horloge numérique, analogique et à bascule avec personnalisation complète.
</p>
<nav aria-label="Liens rapides">
<div className="footer-grid">
<section aria-labelledby="footer-navigation">
<h2 id="footer-navigation">Navigation</h2>
<nav aria-label="Navigation principale du site">
<ul>
<li><a href="#main-clock">Horloge</a></li>
<li><a href="#features">Fonctionnalités</a></li>
<li><a href="/">Accueil</a></li>
<li><a href="https://arthurp.fr" target="_blank" rel="noopener noreferrer">Projets</a></li>
<li><a href="#features">Fonctionnalites</a></li>
<li><a href="#faq">FAQ</a></li>
<li><a href={CONTACT_URL} target="_blank" rel="noopener noreferrer">Contact</a></li>
</ul>
</nav>
</section>
<section aria-labelledby="footer-contact">
<h2 id="footer-contact">Contact</h2>
<ul>
<li><a href={CONTACT_URL} target="_blank" rel="noopener noreferrer">{CONTACT_URL.replace(/^https?:\/\//, '')}</a></li>
<li><a href={CONTACT_MAILTO}>{CONTACT_EMAIL}</a></li>
<li><a href="https://arthurp.fr" target="_blank" rel="noopener noreferrer">arthurp.fr</a></li>
<li><a href="https://github.com/arthur-pbty" target="_blank" rel="noopener noreferrer">GitHub</a></li>
</ul>
</section>
<section aria-labelledby="footer-legal">
<h2 id="footer-legal">Informations</h2>
<ul>
<li><a href="/mentions-legales">Mentions legales</a></li>
<li><a href="/politique-confidentialite">Politique de confidentialite</a></li>
<li><a href="#main-clock">Horloge en direct</a></li>
</ul>
</section>
</div>
<div className="footer-bottom">
<p>&copy; {currentYear} Arthur P. Tous droits reserves.</p>
<p>
Fait avec <span aria-hidden="true"></span> et auto-heberge sur Proxmox.
</p>
</div>
</footer>
</>
);
@@ -0,0 +1,53 @@
import type { Metadata } from 'next';
import { CONTACT_EMAIL, CONTACT_MAILTO, CONTACT_URL, SITE_URL } from '@/lib/env';
export const metadata: Metadata = {
title: 'Politique de confidentialite',
description: `Politique de confidentialite du site ${SITE_URL}`,
};
export default function PolitiqueConfidentialitePage() {
return (
<main className="legal-page">
<header className="legal-header">
<h1>Politique de confidentialite</h1>
<p>Derniere mise a jour: 01/04/2026</p>
</header>
<div className="legal-sections">
<section>
<h2>Donnees collectees</h2>
<p>
Cette application ne demande pas de creation de compte et ne collecte pas de donnees
personnelles identifiantes pour son fonctionnement standard.
</p>
</section>
<section>
<h2>Stockage local</h2>
<p>
Certaines preferences utilisateur (theme, fuseau horaire, format d&apos;affichage)
peuvent etre enregistrees localement dans votre navigateur afin d&apos;ameliorer
votre experience.
</p>
</section>
<section>
<h2>Journaux techniques</h2>
<p>
Comme tout service web, des journaux techniques minimum peuvent exister cote serveur
pour la securite, le diagnostic et la maintenance.
</p>
</section>
<section>
<h2>Contact</h2>
<p>
Pour toute question relative a la confidentialite: <a className="underline underline-offset-4" href={CONTACT_URL}>{CONTACT_URL.replace(/^https?:\/\//, '')}</a> ou{' '}
<a className="underline underline-offset-4" href={CONTACT_MAILTO}>{CONTACT_EMAIL}</a>.
</p>
</section>
</div>
</main>
);
}
+2 -1
View File
@@ -1,4 +1,5 @@
import { MetadataRoute } from 'next';
import { SITE_URL } from '@/lib/env';
export default function robots(): MetadataRoute.Robots {
return {
@@ -17,6 +18,6 @@ export default function robots(): MetadataRoute.Robots {
allow: '/',
},
],
sitemap: 'https://clock.arthurp.fr/sitemap.xml',
sitemap: `${SITE_URL}/sitemap.xml`,
};
}
+14 -1
View File
@@ -1,7 +1,8 @@
import { MetadataRoute } from 'next';
import { SITE_URL } from '@/lib/env';
export default function sitemap(): MetadataRoute.Sitemap {
const baseUrl = 'https://clock.arthurp.fr';
const baseUrl = SITE_URL;
const lastModified = new Date('2026-03-01');
// Page principale
@@ -12,6 +13,18 @@ export default function sitemap(): MetadataRoute.Sitemap {
changeFrequency: 'weekly',
priority: 1,
},
{
url: `${baseUrl}/mentions-legales`,
lastModified,
changeFrequency: 'yearly',
priority: 0.4,
},
{
url: `${baseUrl}/politique-confidentialite`,
lastModified,
changeFrequency: 'yearly',
priority: 0.4,
},
];
// Pages par type d'horloge
+21
View File
@@ -0,0 +1,21 @@
const FALLBACK_SITE_URL = 'https://clock.arthurp.fr';
const FALLBACK_CONTACT_URL = 'https://contact.arthurp.fr';
const FALLBACK_CONTACT_EMAIL = 'contact@arthurp.fr';
function normalizeUrl(value: string | undefined, fallback: string): string {
if (!value) {
return fallback;
}
try {
const parsed = new URL(value);
return parsed.origin;
} catch {
return fallback;
}
}
export const SITE_URL = normalizeUrl(process.env.SITE_URL, FALLBACK_SITE_URL);
export const CONTACT_URL = normalizeUrl(process.env.CONTACT_URL, FALLBACK_CONTACT_URL);
export const CONTACT_EMAIL = process.env.CONTACT_EMAIL?.trim() || FALLBACK_CONTACT_EMAIL;
export const CONTACT_MAILTO = `mailto:${CONTACT_EMAIL}`;