From fdf6452b352bee2597df15d79f4e20acb5b6163e Mon Sep 17 00:00:00 2001 From: Florian Wilde Date: Fri, 14 Aug 2026 22:44:23 +0200 Subject: [PATCH] Typen Social Media und Video MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Social Media buendelt Profile auf einer Seite. Wer „@firma" eintippt, meint das Profil beim gewaehlten Netzwerk — daraus wird die volle Adresse. Statt nachgezeichneter Markenlogos steht ein Kreis in der Hausfarbe mit Monogramm; ein schlecht nachgebautes Logo sieht schlechter aus als ein sauberes Zeichen. Video erkennt YouTube, Vimeo und direkte Dateien. Der Rahmen zum Anbieter laedt erst auf Klick, und davor steht, dass dabei Daten uebertragen werden — diese Seite bekommen Fremde nach einem Scan zu sehen, ungefragt einen Drittanbieter einzubinden waere nicht in Ordnung. Videodateien laden mit preload="none": wer unterwegs scannt, zahlt das Volumen. --- src/components/landing/index.ts | 4 + src/components/landing/social-landing.tsx | 52 ++++++ src/components/landing/video-landing.tsx | 67 ++++++++ src/components/landing/video-player.tsx | 56 +++++++ src/components/qr-forms/index.ts | 4 + src/components/qr-forms/social-form.tsx | 170 +++++++++++++++++++ src/components/qr-forms/video-form.tsx | 80 +++++++++ src/lib/qr/registry.ts | 16 +- src/lib/qr/social-networks.ts | 192 ++++++++++++++++++++++ src/lib/qr/types/social.ts | 97 +++++++++++ src/lib/qr/types/video.ts | 133 +++++++++++++++ 11 files changed, 859 insertions(+), 12 deletions(-) create mode 100644 src/components/landing/social-landing.tsx create mode 100644 src/components/landing/video-landing.tsx create mode 100644 src/components/landing/video-player.tsx create mode 100644 src/components/qr-forms/social-form.tsx create mode 100644 src/components/qr-forms/video-form.tsx create mode 100644 src/lib/qr/social-networks.ts create mode 100644 src/lib/qr/types/social.ts create mode 100644 src/lib/qr/types/video.ts diff --git a/src/components/landing/index.ts b/src/components/landing/index.ts index d1f9fc1..f3693c7 100644 --- a/src/components/landing/index.ts +++ b/src/components/landing/index.ts @@ -1,7 +1,9 @@ import type { ComponentType } from "react"; import { LinksLanding } from "@/components/landing/links-landing"; +import { SocialLanding } from "@/components/landing/social-landing"; import type { LandingProps } from "@/components/landing/types"; +import { VideoLanding } from "@/components/landing/video-landing"; import type { QrTypeId } from "@/lib/qr/type-module"; /** @@ -14,6 +16,8 @@ import type { QrTypeId } from "@/lib/qr/type-module"; export const qrLandings: Partial>> = { links: LinksLanding, + social: SocialLanding, + video: VideoLanding, }; export type { LandingProps }; diff --git a/src/components/landing/social-landing.tsx b/src/components/landing/social-landing.tsx new file mode 100644 index 0000000..bc9b598 --- /dev/null +++ b/src/components/landing/social-landing.tsx @@ -0,0 +1,52 @@ +import { Icon } from "@/components/icons"; +import { LandingShell } from "@/components/landing/shell"; +import type { LandingProps } from "@/components/landing/types"; +import { + getSocialNetwork, + socialHandleLabel, +} from "@/lib/qr/social-networks"; +import { socialDataSchema } from "@/lib/qr/types/social"; + +export function SocialLanding({ data }: LandingProps) { + const { title, description, profiles } = socialDataSchema.parse(data); + + return ( + + + + ); +} diff --git a/src/components/landing/video-landing.tsx b/src/components/landing/video-landing.tsx new file mode 100644 index 0000000..23b03d3 --- /dev/null +++ b/src/components/landing/video-landing.tsx @@ -0,0 +1,67 @@ +import { Icon } from "@/components/icons"; +import { LandingShell } from "@/components/landing/shell"; +import type { LandingProps } from "@/components/landing/types"; +import { EmbeddedVideo } from "@/components/landing/video-player"; +import { resolveVideoSource, videoDataSchema } from "@/lib/qr/types/video"; + +export function VideoLanding({ data }: LandingProps) { + const { url, title, description } = videoDataSchema.parse(data); + const quelle = resolveVideoSource(url); + + return ( + + Bei {quelle.label} öffnen + + ) : null + } + > + {quelle.kind === "youtube" || quelle.kind === "vimeo" ? ( + + ) : quelle.kind === "file" ? ( + + ) : ( + + + + + + Video ansehen + + {quelle.label} + + + + )} + + ); +} diff --git a/src/components/landing/video-player.tsx b/src/components/landing/video-player.tsx new file mode 100644 index 0000000..5ca1b6a --- /dev/null +++ b/src/components/landing/video-player.tsx @@ -0,0 +1,56 @@ +"use client"; + +import { useState } from "react"; + +import { Icon } from "@/components/icons"; + +/** + * Eingebettetes Video mit Zwei-Klick-Lösung. + * + * Ein Rahmen von YouTube oder Vimeo baut beim Laden eine Verbindung zum + * Anbieter auf — auf einer Seite, die Fremde nach einem Scan zu sehen + * bekommen, ist das ohne Zustimmung nicht in Ordnung. Deshalb erscheint der + * Rahmen erst nach einem Klick, und es steht vorher da, was dabei passiert. + */ +export function EmbeddedVideo({ + embedUrl, + provider, + title, +}: { + embedUrl: string; + provider: string; + title: string; +}) { + const [gestartet, setGestartet] = useState(false); + + if (gestartet) { + return ( +
+