HEX
Server: LiteSpeed
System: Linux houston.panomity.com 6.8.0-100-generic #100-Ubuntu SMP PREEMPT_DYNAMIC Tue Jan 13 16:40:06 UTC 2026 x86_64
User: nudepix (1011)
PHP: 7.4.33
Disabled: pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,
Upload Files
File: //proc/self/root/tmp/bewertemich-UploadSuccess-loop.jsx
import React, { useEffect, useState } from 'react';
import { Link, useLocation, useParams } from 'react-router-dom';
import { buildShareUrl, buildUploadPath, getUploadEntry, trackEvent } from '../../utils/tracking.js';
import { buildPhotoInvitePlaybook } from './sharePlaybooks.js';

const copyText = async (text) => {
  try {
    await navigator.clipboard.writeText(text);
    return true;
  } catch {
    const input = document.createElement('input');
    input.value = text;
    document.body.appendChild(input);
    input.select();
    const ok = document.execCommand('copy');
    document.body.removeChild(input);
    return ok;
  }
};

export const UploadSuccessPage = () => {
  const { id } = useParams();
  const location = useLocation();
  const [photo, setPhoto] = useState(null);
  const [stats, setStats] = useState({ avg_score: null, votes: 0 });
  const [msg, setMsg] = useState('');
  const [copyState, setCopyState] = useState('');
  const numericId = Number(id);
  const entryPoint = getUploadEntry(location.search);

  useEffect(() => {
    let cancelled = false;
    const load = async () => {
      try {
        const photoRes = await fetch(`/api/photos/${numericId}`, { credentials: 'include' });
        const photoJson = await photoRes.json().catch(() => ({}));
        const statsRes = await fetch(`/api/votes/${numericId}`, { credentials: 'include' });
        const statsJson = await statsRes.json().catch(() => ({ avg_score: null, votes: 0 }));
        if (cancelled) return;
        if (!photoRes.ok || !photoJson?.photo) {
          setMsg('Foto nicht gefunden.');
          return;
        }
        setPhoto(photoJson.photo);
        setStats(statsJson || { avg_score: null, votes: 0 });
      } catch {
        if (!cancelled) setMsg('Foto nicht gefunden.');
      }
    };
    if (Number.isInteger(numericId) && numericId > 0) load();
    return () => { cancelled = true; };
  }, [numericId]);

  const copyShareUrl = buildShareUrl(numericId, { source: 'copy', surface: 'upload_success' });
  const nativeShareUrl = buildShareUrl(numericId, { source: 'native', surface: 'upload_success' });
  const sharePreviewUrl = numericId > 0 ? `/s/${numericId}/og.jpg` : '';
  const remainingVotes = Math.max(0, 5 - Number(stats?.votes || 0));
  const progressPercent = Math.max(8, Math.min(100, Math.round((Number(stats?.votes || 0) / 5) * 100)));
  const avgScore = stats.avg_score ? Number(stats.avg_score) : null;
  const scoreHeadline = !avgScore || Number(stats?.votes || 0) < 3
    ? 'Noch kein klares Signal'
    : avgScore >= 8
      ? 'Starker erster Eindruck'
      : avgScore >= 6.5
        ? 'Solider Start'
        : 'Da ist noch Luft nach oben';
  const scoreHint = remainingVotes > 0
    ? `Noch ${remainingVotes} Stimme${remainingVotes === 1 ? '' : 'n'} bis zur ersten belastbaren Tendenz.`
    : avgScore >= 8
      ? 'Das Bild wirkt aktuell stark. Jetzt kannst du es auch breiter teilen.'
      : 'Jetzt lohnt sich der Blick auf den Score und eventuell ein zweites Testbild.';
  const nextMove = Number(stats?.votes || 0) < 3
    ? 'Empfohlen jetzt: 3 direkte WhatsApp-Kontakte'
    : Number(stats?.votes || 0) < 5
      ? 'Empfohlen jetzt: Gruppenlink oder kurzer Reminder'
      : 'Empfohlen jetzt: breiter teilen oder A/B-Vergleich starten';
  const shareText = avgScore && Number(stats?.votes || 0) >= 5
    ? `Bewerte bitte mein Profilbild ehrlich. Aktuell hat es ${avgScore.toFixed(1)} Punkte.`
    : 'Bewerte bitte mein Profilbild ehrlich auf bewertemich.de.';
  const canNativeShare = typeof navigator !== 'undefined' && typeof navigator.share === 'function';
  const sharePlaybook = buildPhotoInvitePlaybook({
    photoId: numericId,
    avgScore,
    votes: Number(stats?.votes || 0),
    surface: 'upload_success'
  });
  const shareTemplates = [
    {
      id: 'freund',
      label: 'Direkt an Freunde',
      text: `Magst du mein Profilbild kurz ehrlich bewerten? Dauert nur ein paar Sekunden: ${copyShareUrl}`
    },
    {
      id: 'dating',
      label: 'Dating-Check',
      text: `Ich teste gerade mein Dating-Profilbild. Was ist dein ehrlicher Eindruck? ${copyShareUrl}`
    },
    {
      id: 'gruppe',
      label: 'Gruppe oder Story',
      text: `Kurzer Realitätscheck: Wie wirkt dieses Profilbild auf dich? ${copyShareUrl}`
    }
  ];

  const onCopy = async () => {
    const ok = await copyText(copyShareUrl);
    if (ok) {
      void trackEvent('share_click', {
        photoId: numericId,
        source: 'copy',
        surface: 'upload_success',
        entryPoint
      });
    }
    setCopyState(ok ? 'Link kopiert.' : 'Kopieren fehlgeschlagen.');
  };

  const onNativeShare = async () => {
    if (!canNativeShare) return;
    try {
      await navigator.share({ title: 'Bewerte mein Foto', text: shareText, url: nativeShareUrl });
      void trackEvent('share_click', {
        photoId: numericId,
        source: 'native',
        surface: 'upload_success',
        entryPoint
      });
    } catch {}
  };

  const copyTemplate = async (template, source = 'copy') => {
    const ok = await copyText(template);
    if (ok) {
      void trackEvent('share_click', {
        photoId: numericId,
        source,
        surface: 'upload_success',
        entryPoint,
        meta: { template: 'message' }
      });
    }
    setCopyState(ok ? 'Textvorlage kopiert.' : 'Kopieren fehlgeschlagen.');
  };

  if (!photo) {
    return (
      <div className="mx-auto max-w-4xl px-4 py-10">
        <div className="alert bg-base-100 border">
          <span>{msg || 'Lade...'}</span>
        </div>
      </div>
    );
  }

  return (
    <div className="mx-auto max-w-5xl px-4 py-10 space-y-6">
      <section className="rounded-3xl border bg-gradient-to-br from-primary to-secondary p-6 text-primary-content shadow-lg md:p-8">
        <div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
          <div className="max-w-2xl">
            <div className="badge border-white/30 bg-white/10 text-white">Schritt 2 von 3</div>
            <h1 className="mt-3 text-3xl font-black tracking-tight">Jetzt 5 Personen einladen</h1>
            <p className="mt-2 text-white/90">
              Der schnellste Weg zu aussagekräftigem Feedback ist dein persönlicher Einladungslink. Teile ihn erst in direkten Kontakten, dann in Gruppen oder Story. Die neue OG-Karte zeigt Score und klares Voting-CTA statt nur dein Rohbild.
            </p>
          </div>
          <div className="min-w-[240px] rounded-2xl bg-white/12 px-4 py-3 text-sm text-white/95">
            <div className="text-xs uppercase tracking-[0.2em] text-white/70">Fortschritt</div>
            <div className="mt-2 h-2 overflow-hidden rounded-full bg-white/20">
              <div className="h-full rounded-full bg-white" style={{ width: `${progressPercent}%` }} />
            </div>
            <div className="mt-2">{scoreHint}</div>
            <div className="mt-2 text-xs uppercase tracking-[0.16em] text-white/70">{nextMove}</div>
          </div>
        </div>
      </section>

      <section className="grid gap-6 md:grid-cols-[1.1fr_0.9fr]">
        <div className="space-y-4">
          <div className="rounded-2xl border bg-white p-4 shadow-sm">
            <img
              src={`/api/photos/file/${photo.id}`}
              alt="Dein hochgeladenes Foto"
              className="max-h-[70vh] w-full rounded-2xl border bg-white object-contain"
            />
            <div className="mt-4 flex flex-wrap items-center gap-3 text-sm text-gray-600">
              <span className="rounded-full bg-gray-100 px-3 py-1">Ø {avgScore ? avgScore.toFixed(1) : '-'}</span>
              <span className="rounded-full bg-gray-100 px-3 py-1">{stats.votes} Stimmen</span>
              <span className="rounded-full bg-gray-100 px-3 py-1">Invite first</span>
            </div>
          </div>

          <div className="rounded-3xl bg-slate-900 p-5 text-white shadow-lg">
            <div className="text-xs uppercase tracking-[0.2em] text-white/60">Ergebnis-Karte</div>
            <div className="mt-3 grid gap-4 md:grid-cols-[180px_1fr]">
              <img
                src={`/api/photos/file/${photo.id}`}
                alt="Ergebnis-Karte"
                className="aspect-square w-full rounded-2xl object-cover"
              />
              <div>
                <div className="text-2xl font-black">{scoreHeadline}</div>
                <p className="mt-2 text-sm text-white/80">{scoreHint}</p>
                <div className="mt-4 flex flex-wrap gap-2 text-sm">
                  <span className="rounded-full bg-white/10 px-3 py-1">Ø {avgScore ? avgScore.toFixed(1) : '-'}</span>
                  <span className="rounded-full bg-white/10 px-3 py-1">{stats.votes} Stimmen</span>
                  <span className="rounded-full bg-white/10 px-3 py-1">{remainingVotes > 0 ? `Noch ${remainingVotes}` : 'Signal vorhanden'}</span>
                </div>
                <div className="mt-4 text-sm text-white/75">
                  Beste Reihenfolge: zuerst 5 direkte Kontakte, danach Gruppe oder Story.
                </div>
              </div>
            </div>
          </div>

          <div className="rounded-3xl border bg-white p-5 shadow-sm">
            <div className="text-xs uppercase tracking-[0.2em] text-gray-500">Share-Preview</div>
            <h2 className="mt-2 text-xl font-bold">So sieht deine OG-Karte beim Teilen aus</h2>
            <p className="mt-2 text-sm text-gray-600">
              Messenger und Social-Previews ziehen jetzt eine Score-Karte mit CTA. Das konvertiert deutlich besser als ein blanker Link oder nur dein Rohbild.
            </p>
            <img src={sharePreviewUrl} alt="Share-Vorschau" className="mt-4 w-full rounded-2xl border shadow-sm" />
          </div>
        </div>

        <div className="space-y-4">
          <div className="rounded-2xl border bg-white p-5 shadow-sm">
            <h2 className="text-xl font-bold">Einladungslink</h2>
            <p className="mt-2 text-sm text-gray-600">
              Dieser Link zeigt beim Teilen eine echte Foto-Vorschau und fuehrt direkt zur Bewertung.
            </p>
            <input
              readOnly
              value={copyShareUrl}
              className="mt-4 w-full rounded-xl border border-gray-200 bg-gray-50 px-4 py-3 text-sm"
            />
            <div className="mt-4 grid gap-3 sm:grid-cols-2">
              <button type="button" className="btn btn-primary" onClick={onCopy}>Link kopieren</button>
              {canNativeShare ? (
                <button type="button" className="btn btn-ghost border" onClick={onNativeShare}>Direkt teilen</button>
              ) : (
                <a className="btn btn-ghost border" href={copyShareUrl}>Link öffnen</a>
              )}
              <a
                className="btn btn-ghost border"
                href={sharePlaybook[0]?.href}
                target="_blank"
                rel="noreferrer"
                onClick={() => {
                  void trackEvent('share_click', {
                    photoId: numericId,
                    source: sharePlaybook[0]?.source,
                    surface: 'upload_success',
                    entryPoint,
                    meta: { lane: 'primary' }
                  }, { transport: 'beacon' });
                }}
              >
                WhatsApp direkt
              </a>
              <a
                className="btn btn-ghost border"
                href={sharePlaybook[1]?.href}
                target="_blank"
                rel="noreferrer"
                onClick={() => {
                  void trackEvent('share_click', {
                    photoId: numericId,
                    source: sharePlaybook[1]?.source,
                    surface: 'upload_success',
                    entryPoint,
                    meta: { lane: 'primary' }
                  }, { transport: 'beacon' });
                }}
              >
                Telegram direkt
              </a>
            </div>
            {copyState && <div className="mt-3 text-sm text-gray-600">{copyState}</div>}
          </div>

          <div className="rounded-2xl border bg-white p-5 shadow-sm">
            <h2 className="text-xl font-bold">Kanal-Playbook für mehr Votes</h2>
            <p className="mt-2 text-sm text-gray-600">
              Erst direkte Kontakte, danach Gruppe, dann Reminder. So kommst du am schnellsten über die ersten 5 Stimmen.
            </p>
            <div className="mt-4 space-y-3">
              {sharePlaybook.map((action) => (
                <div key={action.id} className="rounded-2xl border border-gray-200 p-4">
                  <div className="flex items-center justify-between gap-3">
                    <div>
                      <div className="font-semibold">{action.label}</div>
                      <div className="text-xs uppercase tracking-[0.16em] text-gray-400">{action.goal}</div>
                    </div>
                    {'copyText' in action ? (
                      <button
                        type="button"
                        className="btn btn-sm btn-ghost border"
                        onClick={() => copyTemplate(action.copyText, action.source)}
                      >
                        Kopieren
                      </button>
                    ) : (
                      <a
                        className="btn btn-sm btn-ghost border"
                        href={action.href}
                        target="_blank"
                        rel="noreferrer"
                        onClick={() => {
                          void trackEvent('share_click', {
                            photoId: numericId,
                            source: action.source,
                            surface: 'upload_success',
                            entryPoint,
                            meta: { lane: 'playbook_open' }
                          }, { transport: 'beacon' });
                        }}
                      >
                        Öffnen
                      </a>
                    )}
                  </div>
                  <div className="mt-2 text-sm text-gray-600">{action.description}</div>
                </div>
              ))}
            </div>
          </div>

          <div className="rounded-2xl border bg-white p-5 shadow-sm">
            <h2 className="text-xl font-bold">Textvorlagen, die schnell funktionieren</h2>
            <div className="mt-4 space-y-3">
              {shareTemplates.map((template) => (
                <div key={template.id} className="rounded-2xl border border-gray-200 p-4">
                  <div className="flex items-center justify-between gap-3">
                    <div className="font-semibold">{template.label}</div>
                    <button type="button" className="btn btn-sm btn-ghost border" onClick={() => copyTemplate(template.text, 'copy_template')}>Kopieren</button>
                  </div>
                  <div className="mt-2 text-sm text-gray-600">{template.text}</div>
                </div>
              ))}
            </div>
          </div>

          <div className="rounded-2xl border bg-white p-5 shadow-sm">
            <h2 className="text-xl font-bold">Empfohlene Reihenfolge</h2>
            <ol className="mt-3 space-y-3 text-sm text-gray-700">
              <li>1. Schicke den Link zuerst an 3 direkte WhatsApp-Kontakte.</li>
              <li>2. Hole dir 2 weitere direkte Stimmen über Telegram oder Copy-Link.</li>
              <li>3. Wenn du noch unter 5 Stimmen bist, nutze Gruppe oder Reminder.</li>
            </ol>
            <div className="mt-4 flex flex-wrap gap-3">
              <Link to={`/rate/${photo.id}`} className="btn btn-primary">Zur Bewertungsseite</Link>
              <Link to={`/compare/new?photo=${photo.id}`} className="btn btn-ghost border">A/B-Vergleich starten</Link>
              <Link to={buildUploadPath('upload_success_repeat')} className="btn btn-ghost border">Noch ein Foto hochladen</Link>
            </div>
          </div>
        </div>
      </section>
    </div>
  );
};