From de50801cfca43b2e0d9fcb5a549b84099f3e0fa3 Mon Sep 17 00:00:00 2001 From: Puechberty Arthur Date: Wed, 1 Apr 2026 23:55:17 +0200 Subject: [PATCH] feat: refactor Docker setup, enhance README, and add legal pages with environment variable support --- Dockerfile | 46 +++---- LICENSE | 25 ++++ README.md | 88 ++++++------ docker-compose.yml | 40 +++++- next.config.ts | 2 + src/app/globals.css | 150 ++++++++++++++++++--- src/app/layout.tsx | 2 +- src/app/mentions-legales/page.tsx | 51 +++++++ src/app/page.tsx | 54 ++++++-- src/app/politique-confidentialite/page.tsx | 53 ++++++++ src/app/robots.ts | 3 +- src/app/sitemap.ts | 15 ++- src/lib/env.ts | 21 +++ 13 files changed, 443 insertions(+), 107 deletions(-) create mode 100644 LICENSE create mode 100644 src/app/mentions-legales/page.tsx create mode 100644 src/app/politique-confidentialite/page.tsx create mode 100644 src/lib/env.ts diff --git a/Dockerfile b/Dockerfile index 193b26c..818b55e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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"] \ No newline at end of file +CMD ["node", "server.js"] \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e73322f --- /dev/null +++ b/LICENSE @@ -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 d’une 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 +L’usage 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é d’une 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 d’aucune sorte. L’auteur décline toute responsabilité pour tout dommage direct ou indirect résultant de l’utilisation du code. \ No newline at end of file diff --git a/README.md b/README.md index cefead0..acf7995 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/docker-compose.yml b/docker-compose.yml index a47d63b..de0842e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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 \ No newline at end of file + 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 \ No newline at end of file diff --git a/next.config.ts b/next.config.ts index 5d5e669..d069780 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,6 +1,8 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { + output: 'standalone', + // Optimisations de production reactStrictMode: true, diff --git a/src/app/globals.css b/src/app/globals.css index 04ab7a9..33df9c1 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -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; + } } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 69aa3ed..d42ad02 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -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.'; diff --git a/src/app/mentions-legales/page.tsx b/src/app/mentions-legales/page.tsx new file mode 100644 index 0000000..bd2f575 --- /dev/null +++ b/src/app/mentions-legales/page.tsx @@ -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 ( +
+
+

Mentions legales

+

Derniere mise a jour: 01/04/2026

+
+ +
+
+

Editeur du site

+

Site: {SITE_URL.replace(/^https?:\/\//, '')}

+

Responsable de publication: Arthur P.

+

+ Contact: {CONTACT_URL.replace(/^https?:\/\//, '')} ou{' '} + {CONTACT_EMAIL} +

+
+ +
+

Hebergement

+

Service auto-heberge sur infrastructure Proxmox.

+
+ +
+

Propriete intellectuelle

+

+ 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. +

+
+ +
+

Liens externes

+

+ 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. +

+
+
+
+ ); +} \ No newline at end of file diff --git a/src/app/page.tsx b/src/app/page.tsx index ce4e478..6294dfa 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -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 ( <> @@ -145,17 +148,46 @@ export default function Home() { ); diff --git a/src/app/politique-confidentialite/page.tsx b/src/app/politique-confidentialite/page.tsx new file mode 100644 index 0000000..3bc9720 --- /dev/null +++ b/src/app/politique-confidentialite/page.tsx @@ -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 ( +
+
+

Politique de confidentialite

+

Derniere mise a jour: 01/04/2026

+
+ +
+
+

Donnees collectees

+

+ Cette application ne demande pas de creation de compte et ne collecte pas de donnees + personnelles identifiantes pour son fonctionnement standard. +

+
+ +
+

Stockage local

+

+ Certaines preferences utilisateur (theme, fuseau horaire, format d'affichage) + peuvent etre enregistrees localement dans votre navigateur afin d'ameliorer + votre experience. +

+
+ +
+

Journaux techniques

+

+ Comme tout service web, des journaux techniques minimum peuvent exister cote serveur + pour la securite, le diagnostic et la maintenance. +

+
+ +
+

Contact

+

+ Pour toute question relative a la confidentialite: {CONTACT_URL.replace(/^https?:\/\//, '')} ou{' '} + {CONTACT_EMAIL}. +

+
+
+
+ ); +} \ No newline at end of file diff --git a/src/app/robots.ts b/src/app/robots.ts index dd6a4f0..be2df68 100644 --- a/src/app/robots.ts +++ b/src/app/robots.ts @@ -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`, }; } diff --git a/src/app/sitemap.ts b/src/app/sitemap.ts index f46a5a8..81e5f97 100644 --- a/src/app/sitemap.ts +++ b/src/app/sitemap.ts @@ -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 diff --git a/src/lib/env.ts b/src/lib/env.ts new file mode 100644 index 0000000..032a00d --- /dev/null +++ b/src/lib/env.ts @@ -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}`; \ No newline at end of file