% ============================================
% STEP 01: PORTFOLIO-SEITE - VON NULL ZUM LIVE-DEPLOYMENT
% ============================================

\section{Portfolio-Seite: Von Null zum Live-Deployment}
\label{sec:step01}

In diesem Tutorial bauen wir eine komplette Portfolio-Webseite mit React, Vite und Tailwind CSS – von der ersten Codezeile bis zur automatisch deployten Live-Seite per Gitea CI/CD.

\subsection{Projektstruktur (Monorepo mit pnpm)}

Wir verwenden ein \textbf{Monorepo} mit \texttt{pnpm} als Package-Manager. Das ermöglicht, mehrere Projekte (Frontend, Backend) in einem Repository zu verwalten.

\textbf{Ordnerstruktur nach diesem Schritt:}
\begin{verbatim}
portfolio/
├── apps/
│   └── web/                  # React-Frontend mit Vite + Tailwind
│       ├── src/
│       │   ├── App.tsx        # Hauptkomponente
│       │   ├── index.css      # Tailwind-Import
│       │   └── main.tsx       # Einstiegspunkt
│       ├── package.json       # Frontend-Abhängigkeiten
│       └── vite.config.ts     # Vite + Tailwind Konfiguration
├── .gitea/
│   └── workflows/
│       └── deploy.yaml       # CI/CD Pipeline
├── .gitignore
├── .npmrc                     # pnpm Build-Scripts erlauben
├── Dockerfile                 # Docker-Build für Produktion
├── package.json               # Root-Konfiguration
├── pnpm-lock.yaml             # Lockfile (automatisch erstellt)
└── pnpm-workspace.yaml        # Workspace-Definition
\end{verbatim}

\subsection{Schritt 1: Monorepo initialisieren}

\begin{lstlisting}[language=Bash, caption={Monorepo mit pnpm einrichten}]
cd ~/projects
mkdir portfolio
cd portfolio

# pnpm initialisieren
pnpm init

# Workspace-Struktur definieren
cat > pnpm-workspace.yaml << 'EOF'
packages:
  - "apps/*"
EOF

# Root package.json anpassen
cat > package.json << 'EOF'
{
  "name": "portfolio",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "dev": "pnpm --filter web dev",
    "build": "pnpm --filter web build"
  }
}
EOF

# Apps-Ordner für das Frontend
mkdir -p apps/web
\end{lstlisting}

\textbf{Erklärung der Dateien:}
\begin{itemize}
    \item \texttt{pnpm-workspace.yaml} – Teilt pnpm mit, dass alle Ordner unter \texttt{apps/} eigenständige Pakete sind
    \item \texttt{package.json} – Root-Konfiguration mit praktischen Scripts. \texttt{--filter web} führt den Befehl nur im \texttt{apps/web}-Paket aus
\end{itemize}

\subsection{Schritt 2: React + Vite + TypeScript einrichten}

\begin{lstlisting}[language=Bash, caption={Vite-Projekt erstellen}]
cd ~/projects/portfolio

# Vite-Projekt mit React und TypeScript erstellen
pnpm create vite apps/web --template react-swc-ts

# In den web-Ordner wechseln
cd apps/web

# Abhängigkeiten installieren
pnpm install
\end{lstlisting}

\textbf{Erklärung:}
\begin{itemize}
    \item \texttt{pnpm create vite} – Erstellt ein neues Vite-Projekt im angegebenen Ordner
    \item \texttt{--template react-swc-ts} – Verwendet die Vorlage mit React, SWC (schneller Compiler) und TypeScript
    \item \texttt{pnpm install} – Installiert alle Abhängigkeiten aus \texttt{package.json}
\end{itemize}

\subsection{Schritt 3: Tailwind CSS einrichten}

\begin{lstlisting}[language=Bash, caption={Tailwind CSS installieren und konfigurieren}]
cd ~/projects/portfolio/apps/web

# Tailwind-Pakete installieren
pnpm add tailwindcss @tailwindcss/vite

# vite.config.ts überschreiben
cat > vite.config.ts << 'EOF'
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";

export default defineConfig({
  plugins: [react(), tailwindcss()],
});
EOF

# index.css anpassen (nur Tailwind-Import)
cat > src/index.css << 'EOF'
@import "tailwindcss";
EOF
\end{lstlisting}

\textbf{Erklärung:}
\begin{itemize}
    \item \texttt{tailwindcss} – Das Tailwind CSS Framework (Version 4)
    \item \texttt{@tailwindcss/vite} – Das offizielle Vite-Plugin für Tailwind CSS. Es verarbeitet die Tailwind-Klassen direkt beim Build.
    \item \texttt{@import "tailwindcss"} – Importiert alle Tailwind-Basis-Styles, Komponenten und Utilities
\end{itemize}

\textbf{Wichtig:} Tailwind 4 verwendet \texttt{@import "tailwindcss"} statt der alten \texttt{@tailwind base/components/utilities}-Direktiven. Kein \texttt{tailwind.config.js} mehr nötig!

\subsection{Schritt 4: Erste App-Komponente}

\begin{lstlisting}[language=Bash, caption={Minimale App.tsx}]
cat > src/App.tsx << 'EOF'
function App() {
  return (
    <div className="min-h-screen bg-gray-950 text-white flex items-center justify-center">
      <h1 className="text-4xl font-bold"> Portfolio</h1>
    </div>
  );
}

export default App;
EOF
\end{lstlisting}

\textbf{Lokal testen:}
\begin{lstlisting}[language=Bash, caption={Entwicklungsserver starten}]
cd ~/projects/portfolio
pnpm run dev
\end{lstlisting}

Im Browser: \texttt{http://localhost:5173}

\subsection{Schritt 5: Git initialisieren}

\begin{lstlisting}[language=Bash, caption={Git Repository einrichten}]
cd ~/projects/portfolio

# .gitignore erstellen
cat > .gitignore << 'EOF'
node_modules
dist
.vs
.idea
*.db
EOF

# Git initialisieren und ersten Commit machen
git init
git add .
git commit -m "Initial commit: Monorepo mit React + Vite + Tailwind"
\end{lstlisting}

\subsection{Schritt 6: Gitea-Repository anlegen und pushen}

\begin{enumerate}
    \item Im Browser \texttt{http://185.209.229.167:3000} öffnen
    \item Rechts oben auf \textbf{+} → \textbf{New Repository}
    \item Name: \texttt{portfolio}, auf \textbf{Create Repository} klicken
\end{enumerate}

\begin{lstlisting}[language=Bash, caption={Gitea als Remote hinzufügen und pushen}]
git remote add gitea http://185.209.229.167:3000/robre/portfolio.git
git push gitea master
\end{lstlisting}

\textbf{Credential Helper (damit Git sich Username/Passwort merkt):}
\begin{lstlisting}[language=Bash, caption={Git Credential Helper aktivieren}]
git config --global credential.helper store
\end{lstlisting}

Beim nächsten Push einmalig Username (\texttt{robre}) und Gitea-Passwort eingeben – danach nie wieder.

\subsection{Schritt 7: Dockerfile für Produktion}

Da das Portfolio nur aus statischen Dateien besteht (nach dem Vite-Build), brauchen wir einen zweistufigen Docker-Build:

\begin{lstlisting}[language=Dockerfile, caption={Dockerfile für das Portfolio}]
FROM node:22-alpine AS build
WORKDIR /app
COPY pnpm-lock.yaml pnpm-workspace.yaml package.json .npmrc ./
COPY apps/web/package.json apps/web/
RUN npm install -g pnpm && pnpm install --no-frozen-lockfile
COPY apps/web/ apps/web/
WORKDIR /app/apps/web
RUN pnpm run build

FROM nginx:stable-alpine
COPY --from=build /app/apps/web/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
\end{lstlisting}

\textbf{Zeile für Zeile erklärt:}
\begin{itemize}
    \item \texttt{FROM node:22-alpine AS build} – Leichtes Node.js-Image für den Build
    \item \texttt{COPY pnpm-lock.yaml pnpm-workspace.yaml package.json .npmrc ./} – Konfigurationsdateien für pnpm. Ohne \texttt{pnpm-workspace.yaml} findet pnpm die Pakete nicht!
    \item \texttt{COPY apps/web/package.json apps/web/} – Nur package.json zuerst kopieren (Docker-Cache für schnellere Builds)
    \item \texttt{RUN npm install -g pnpm \&\& pnpm install --no-frozen-lockfile} – pnpm installieren und Abhängigkeiten installieren
    \item \texttt{COPY apps/web/ apps/web/} – Restlichen Code kopieren
    \item \texttt{WORKDIR /app/apps/web} – Ins Frontend-Verzeichnis wechseln
    \item \texttt{RUN pnpm run build} – Produktions-Build mit Vite (erstellt \texttt{dist/})
    \item \texttt{FROM nginx:stable-alpine} – Neues, schlankes Image für den Webserver
    \item \texttt{COPY --from=build /app/apps/web/dist /usr/share/nginx/html} – Nur den Build-Output kopieren
    \item \texttt{EXPOSE 80 / CMD ["nginx", "-g", "daemon off;"]} – Nginx starten
\end{itemize}

\subsection{Schritt 8: .npmrc für Build-Scripts}

\begin{lstlisting}[language=Bash, caption={Build-Scripts erlauben}]
cat > .npmrc << 'EOF'
pnpm.onlyBuiltDependencies=*
EOF
\end{lstlisting}

\textbf{Erklärung:} pnpm blockt standardmäßig Build-Scripts aus Sicherheitsgründen. Diese Datei erlaubt alle Build-Scripts – notwendig für Pakete wie \texttt{@swc/core} oder \texttt{esbuild}.

\subsection{Schritt 9: CI/CD-Pipeline mit Gitea Actions}

\begin{lstlisting}[language=Bash, caption={Workflow-Ordner erstellen}]
mkdir -p .gitea/workflows
\end{lstlisting}

\begin{lstlisting}[language=YAML, caption={.gitea/workflows/deploy.yaml}]
name: Deploy Portfolio
on:
  push:
    branches: [ "master" ]

jobs:
  deploy:
    runs-on: ubuntu-latest
    container:
      image: catthehacker/ubuntu:act-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Build and Deploy
        run: |
          docker build -t portfolio:latest .
          docker stop portfolio 2>/dev/null || true
          docker rm portfolio 2>/dev/null || true
          docker run -d --name portfolio -p 8081:80 portfolio:latest
\end{lstlisting}

\textbf{Erklärung:}
\begin{itemize}
    \item \texttt{on: push: branches: ["master"]} – Der Workflow läuft bei jedem Push auf master
    \item \texttt{runs-on: ubuntu-latest} – Virtuelle Maschine für den Job
    \item \texttt{container: image: catthehacker/ubuntu:act-latest} – Docker-Image mit Ubuntu + Docker CLI
    \item \texttt{actions/checkout@v4} – Checkt den Code aus dem Repository aus
    \item \texttt{docker build -t portfolio:latest .} – Baut das Docker-Image
    \item \texttt{docker stop/rm 2>/dev/null || true} – Stoppt alten Container (ignoriert Fehler, falls nicht existiert)
    \item \texttt{docker run -d --name portfolio -p 8081:80 portfolio:latest} – Startet neuen Container auf Port 8081
\end{itemize}

\subsection{Schritt 10: Firewall öffnen und deployen}

\begin{lstlisting}[language=Bash, caption={Port 8081 freigeben}]
ssh testserver "ufw allow 8081/tcp"
\end{lstlisting}

\begin{lstlisting}[language=Bash, caption={Alles pushen – löst Pipeline aus!}]
git add .
git commit -m "Dockerfile + CI/CD Pipeline hinzugefügt"
git push gitea master
\end{lstlisting}

\subsection{Aufgetretene Fehler und ihre Lösungen}

\subsubsection{Fehler 1: pnpm-workspace.yaml not found}
\textbf{Fehlermeldung:} \texttt{"/pnpm-workspace.yaml": not found}

\textbf{Ursache:} Die Datei wurde nie erstellt, weil \texttt{git init} zurückgesetzt wurde.

\textbf{Lösung:} \texttt{pnpm-workspace.yaml} manuell erstellen und committen.

\subsubsection{Fehler 2: pnpm-lock.yaml not found}
\textbf{Fehlermeldung:} \texttt{"/pnpm-lock.yaml": not found}

\textbf{Ursache:} \texttt{pnpm install} wurde nie im Root ausgeführt, daher kein Lockfile.

\textbf{Lösung:} \texttt{pnpm install} im Root ausführen, dann die erstellte \texttt{pnpm-lock.yaml} committen.

\subsubsection{Fehler 3: ERR\_PNPM\_IGNORED\_BUILDS}
\textbf{Fehlermeldung:} \texttt{[ERR\_PNPM\_IGNORED\_BUILDS] Ignored build scripts}

\textbf{Ursache:} pnpm blockt Build-Scripts aus Sicherheitsgründen.

\textbf{Lösung:} \texttt{.npmrc} mit \texttt{pnpm.onlyBuiltDependencies=*} erstellen.

\subsection{Die Seite erreichen}

\textbf{Im Browser:}
\begin{lstlisting}[language=Bash, caption={Portfolio-URL}]
http://185.209.229.167:8081
\end{lstlisting}

\textbf{Container-Status prüfen:}
\begin{lstlisting}[language=Bash, caption={Container-Check}]
ssh testserver "docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}' | grep portfolio"
\end{lstlisting}

\textbf{Erwartete Ausgabe:}
\begin{verbatim}
portfolio   Up 2 minutes   0.0.0.0:8081->80/tcp
\end{verbatim}

\textbf{Port-Mapping lesen:}
\begin{itemize}
    \item \texttt{0.0.0.0:8081->80/tcp} – Von außen über Port 8081 erreichbar, intern läuft Nginx auf Port 80
    \item Die \texttt{0.0.0.0} bedeutet: Auf ALLEN Netzwerkschnittstellen des Servers (IPv4 und IPv6)
\end{itemize}

\subsection{Vollständiger Code: App.tsx mit Tailwind}

\begin{lstlisting}[language=TypeScript, caption={Vollständige App.tsx mit Tailwind-Styling}]
function App() {
  return (
    <div className="min-h-screen bg-gradient-to-br from-slate-900 via-purple-900 to-slate-900 text-white p-8">
      <div className="max-w-4xl mx-auto">
        <header className="text-center mb-16 pt-20">
          <span className="inline-block px-4 py-1 rounded-full bg-emerald-500/20 text-emerald-300 text-sm font-medium mb-4 animate-pulse">
              Portfolio 2026
          </span>
          <h1 className="text-7xl font-bold mb-4 bg-gradient-to-r from-emerald-400 via-cyan-400 to-purple-400 text-transparent bg-clip-text">
            Mein Portfolio
          </h1>
          <p className="text-xl text-gray-300">
            Full-Stack Entwickler & DevOps Enthusiast
          </p>
        </header>

        <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
          <div className="group bg-white/5 backdrop-blur rounded-xl p-8 text-center hover:bg-white/10 transition-all duration-300 border border-white/10 hover:border-emerald-500 hover:scale-105 cursor-pointer">
            <span className="text-5xl block mb-4"> </span>
            <h2 className="text-2xl font-semibold group-hover:text-emerald-400 transition-colors">
              Projekte
            </h2>
            <p className="text-gray-400 mt-2">React, .NET, Docker</p>
          </div>

          <div className="group bg-white/5 backdrop-blur rounded-xl p-8 text-center hover:bg-white/10 transition-all duration-300 border border-white/10 hover:border-cyan-500 hover:scale-105 cursor-pointer">
            <span className="text-5xl block mb-4"> </span>
            <h2 className="text-2xl font-semibold group-hover:text-cyan-400 transition-colors">
              Skills
            </h2>
            <p className="text-gray-400 mt-2">TypeScript, C#, SQL</p>
          </div>

          <div className="group bg-white/5 backdrop-blur rounded-xl p-8 text-center hover:bg-white/10 transition-all duration-300 border border-white/10 hover:border-purple-500 hover:scale-105 cursor-pointer">
            <span className="text-5xl block mb-4"> </span>
            <h2 className="text-2xl font-semibold group-hover:text-purple-400 transition-colors">
              Kontakt
            </h2>
            <p className="text-gray-400 mt-2">Immer erreichbar</p>
          </div>
        </div>

        <div className="mt-16 p-8 bg-white/5 rounded-xl backdrop-blur border border-white/10">
          <h3 className="text-xl font-bold mb-4">  Tailwind Farb-Test</h3>
          <div className="flex flex-wrap gap-2">
            {[
              "bg-red-500", "bg-orange-500", "bg-yellow-500", "bg-green-500",
              "bg-emerald-500", "bg-cyan-500", "bg-blue-500", "bg-purple-500",
              "bg-pink-500", "bg-rose-500"
            ].map((color) => (
              <div
                key={color}
                className={`w-12 h-12 rounded-lg ${color} hover:scale-125 transition-transform cursor-pointer`}
                title={color}
              />
            ))}
          </div>
        </div>
      </div>
    </div>
  );
}

export default App;
\end{lstlisting}

\subsection{Tailwind-Klassen im Überblick}

\begin{table}[h]
\centering
\caption{Verwendete Tailwind-Klassen und ihre Bedeutung}
\begin{tabular}{@{}lp{7cm}@{}}
\toprule
\textbf{Klasse} & \textbf{Bedeutung} \\
\midrule
\texttt{min-h-screen} & Mindesthöhe = Bildschirmhöhe \\
\texttt{bg-gradient-to-br} & Hintergrund-Farbverlauf von oben-links nach unten-rechts \\
\texttt{from-/via-/to-COLOR} & Farben des Farbverlaufs \\
\texttt{text-transparent bg-clip-text} & Text mit Farbverlauf füllen \\
\texttt{backdrop-blur} & Hintergrund-Weichzeichner (Glassmorphismus) \\
\texttt{bg-white/5} & Weiß mit 5\% Deckkraft \\
\texttt{group} & Parent für Gruppen-Hover-Effekte \\
\texttt{group-hover:text-COLOR} & Textfarbe ändert sich bei Hover auf Parent \\
\texttt{group-hover:scale-105} & Vergrößerung bei Hover auf Parent \\
\texttt{transition-all duration-300} & Sanfte Übergänge über 300ms \\
\texttt{animate-pulse} & Pulsierende Animation \\
\bottomrule
\end{tabular}
\end{table}

\subsection{Zusammenfassung}

In diesem Tutorial haben wir:
\begin{itemize}
    \item Ein Monorepo mit pnpm Workspace eingerichtet
    \item React + Vite + TypeScript + Tailwind CSS 4 installiert
    \item Ein Dockerfile für den Produktions-Build erstellt
    \item Eine CI/CD-Pipeline mit Gitea Actions konfiguriert
    \item Die Seite automatisch bei jedem Push deployed
    \item Drei typische Fehler analysiert und behoben
    \item Eine vollständige Portfolio-Landingpage mit Tailwind gestaltet
\end{itemize}

\textbf{Die Seite ist live unter:} \texttt{http://185.209.229.167:8081}

\textbf{Die Pipeline läuft bei jedem Push auf master automatisch!}