feat: agregar rutas dinámicas para videos en la sección de ayuda
This commit is contained in:
@@ -0,0 +1,272 @@
|
|||||||
|
import { Metadata } from "next";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { notFound } from "next/navigation";
|
||||||
|
import type { Video, VideoCategory } from "../../videoData";
|
||||||
|
import { videoCategories, getAllVideos } from "../../videoData";
|
||||||
|
|
||||||
|
// Static generation: Next.js builds a page for every video at build time
|
||||||
|
export function generateStaticParams() {
|
||||||
|
return getAllVideos().map((video) => ({ videoId: video.id }));
|
||||||
|
}
|
||||||
|
|
||||||
|
// SEO metadata per video
|
||||||
|
export async function generateMetadata({
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
params: Promise<{ videoId: string }>;
|
||||||
|
}): Promise<Metadata> {
|
||||||
|
const { videoId } = await params;
|
||||||
|
const video = getAllVideos().find((v) => v.id === videoId);
|
||||||
|
|
||||||
|
if (!video) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const category = videoCategories.find((c) => c.videos.some((v) => v.id === videoId));
|
||||||
|
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "https://turnosxpress.com.ar";
|
||||||
|
const url = `${baseUrl}/landing/help/video/${videoId}`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: `${video.title} | TurnosXpress`,
|
||||||
|
description: video.description,
|
||||||
|
alternates: {
|
||||||
|
canonical: url,
|
||||||
|
},
|
||||||
|
openGraph: {
|
||||||
|
type: "video.other",
|
||||||
|
title: video.title,
|
||||||
|
description: video.description,
|
||||||
|
images: [{ url: video.thumbnail, width: 480, height: 360, alt: video.title }],
|
||||||
|
siteName: "TurnosXpress",
|
||||||
|
locale: "es_AR",
|
||||||
|
},
|
||||||
|
twitter: {
|
||||||
|
card: "summary_large_image",
|
||||||
|
title: video.title,
|
||||||
|
description: video.description,
|
||||||
|
images: [video.thumbnail],
|
||||||
|
},
|
||||||
|
robots: {
|
||||||
|
index: true,
|
||||||
|
follow: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const allVideos = getAllVideos();
|
||||||
|
|
||||||
|
// Build flat array with category context for each video
|
||||||
|
interface VideoWithContext {
|
||||||
|
video: Video;
|
||||||
|
category: VideoCategory;
|
||||||
|
index: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getVideoWithContext(videoId: string): VideoWithContext | null {
|
||||||
|
let flatIndex = 0;
|
||||||
|
for (const cat of videoCategories) {
|
||||||
|
const idx = cat.videos.findIndex((v) => v.id === videoId);
|
||||||
|
if (idx !== -1) {
|
||||||
|
return {
|
||||||
|
video: cat.videos[idx],
|
||||||
|
category: cat,
|
||||||
|
index: flatIndex,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
flatIndex += cat.videos.length;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function VideoPage({
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
params: Promise<{ videoId: string }>;
|
||||||
|
}) {
|
||||||
|
const { videoId } = await params;
|
||||||
|
const ctx = getVideoWithContext(videoId);
|
||||||
|
|
||||||
|
if (!ctx) {
|
||||||
|
notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
const { video, category, index } = ctx;
|
||||||
|
const prevVideo = index > 0 ? allVideos[index - 1] : null;
|
||||||
|
const nextVideo = index < allVideos.length - 1 ? allVideos[index + 1] : null;
|
||||||
|
|
||||||
|
// JSON-LD VideoObject schema for Google rich results
|
||||||
|
const jsonLd = {
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "VideoObject",
|
||||||
|
name: video.title,
|
||||||
|
description: video.description,
|
||||||
|
thumbnailUrl: video.thumbnail,
|
||||||
|
contentUrl: `https://www.youtube.com/watch?v=${video.id}`,
|
||||||
|
embedUrl: `https://www.youtube.com/embed/${video.id}`,
|
||||||
|
uploadDate: "2025-01-01",
|
||||||
|
publisher: {
|
||||||
|
"@type": "Organization",
|
||||||
|
name: "TurnosXpress",
|
||||||
|
logo: {
|
||||||
|
"@type": "ImageObject",
|
||||||
|
url: `${process.env.NEXT_PUBLIC_BASE_URL || "https://turnosxpress.com.ar"}/favicon.ico`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
category: category.name,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* JSON-LD structured data */}
|
||||||
|
<script
|
||||||
|
type="application/ld+json"
|
||||||
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div style={{ maxWidth: 900, margin: "0 auto", padding: "2rem 1.5rem" }}>
|
||||||
|
{/* Breadcrumb nav */}
|
||||||
|
<nav
|
||||||
|
style={{
|
||||||
|
marginBottom: "2rem",
|
||||||
|
fontSize: "0.875rem",
|
||||||
|
color: "var(--gray-dark)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
href="/landing/help"
|
||||||
|
style={{ color: "var(--wine-dark)", textDecoration: "none" }}
|
||||||
|
>
|
||||||
|
Centro de Ayuda
|
||||||
|
</Link>
|
||||||
|
{" / "}
|
||||||
|
<span style={{ color: "var(--black)" }}>{video.title}</span>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{/* Category badge */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "inline-block",
|
||||||
|
padding: "0.35rem 1rem",
|
||||||
|
borderRadius: 50,
|
||||||
|
background: category.color,
|
||||||
|
color: "white",
|
||||||
|
fontSize: "0.8rem",
|
||||||
|
fontWeight: 700,
|
||||||
|
marginBottom: "1.25rem",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{category.icon} {category.name}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Title */}
|
||||||
|
<h1
|
||||||
|
style={{
|
||||||
|
fontSize: "2rem",
|
||||||
|
fontWeight: 800,
|
||||||
|
color: "var(--wine-superdark)",
|
||||||
|
marginBottom: "0.75rem",
|
||||||
|
lineHeight: 1.3,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{video.title}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
{/* Description */}
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: "1.05rem",
|
||||||
|
color: "var(--gray-dark)",
|
||||||
|
lineHeight: 1.6,
|
||||||
|
marginBottom: "2rem",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{video.description}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* YouTube embed */}
|
||||||
|
<div style={{ position: "relative", paddingBottom: "56.25%", height: 0, overflow: "hidden", borderRadius: 16, boxShadow: "0 8px 30px rgba(0,0,0,0.12)", marginBottom: "2rem" }}>
|
||||||
|
<iframe
|
||||||
|
src={`https://www.youtube.com/embed/${video.id}?rel=0&modestbranding=1`}
|
||||||
|
title={video.title}
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||||
|
allowFullScreen
|
||||||
|
style={{ position: "absolute", top: 0, left: 0, width: "100%", height: "100%", border: 0 }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Prev/Next navigation */}
|
||||||
|
{(prevVideo || nextVideo) && (
|
||||||
|
<div style={{ display: "flex", gap: "1rem", flexWrap: "wrap" }}>
|
||||||
|
{prevVideo && (
|
||||||
|
<Link
|
||||||
|
href={`/landing/help/video/${prevVideo.id}`}
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
minWidth: 200,
|
||||||
|
padding: "1rem 1.25rem",
|
||||||
|
borderRadius: 12,
|
||||||
|
border: "1px solid var(--white-darkest)",
|
||||||
|
background: "var(--white)",
|
||||||
|
textDecoration: "none",
|
||||||
|
transition: "all 0.2s ease",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ fontSize: "0.8rem", color: "var(--gray-dark)", marginBottom: "0.25rem" }}>
|
||||||
|
{"← Anterior"}
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: "0.95rem", fontWeight: 700, color: "var(--wine-superdark)" }}>
|
||||||
|
{prevVideo.title}
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
{nextVideo && (
|
||||||
|
<Link
|
||||||
|
href={`/landing/help/video/${nextVideo.id}`}
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
minWidth: 200,
|
||||||
|
padding: "1rem 1.25rem",
|
||||||
|
borderRadius: 12,
|
||||||
|
border: "1px solid var(--white-darkest)",
|
||||||
|
background: "var(--white)",
|
||||||
|
textDecoration: "none",
|
||||||
|
textAlign: "right",
|
||||||
|
transition: "all 0.2s ease",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ fontSize: "0.8rem", color: "var(--gray-dark)", marginBottom: "0.25rem" }}>
|
||||||
|
{"Siguiente →"}
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: "0.95rem", fontWeight: 700, color: "var(--wine-superdark)" }}>
|
||||||
|
{nextVideo.title}
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Back to help CTA */}
|
||||||
|
<div style={{ textAlign: "center", marginTop: "3rem", paddingTop: "2rem", borderTop: "1px solid var(--white-darkest)" }}>
|
||||||
|
<p style={{ color: "var(--gray-dark)", marginBottom: "1rem" }}>
|
||||||
|
Querés ver más tutoriales?
|
||||||
|
</p>
|
||||||
|
<Link
|
||||||
|
href="/landing/help"
|
||||||
|
style={{
|
||||||
|
display: "inline-block",
|
||||||
|
padding: "0.75rem 2rem",
|
||||||
|
background: "linear-gradient(135deg, var(--wine-red), var(--wine-dark))",
|
||||||
|
color: "white",
|
||||||
|
borderRadius: 50,
|
||||||
|
textDecoration: "none",
|
||||||
|
fontWeight: 700,
|
||||||
|
fontSize: "1rem",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Ver todos los tutoriales
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import { NextResponse } from "next/server";
|
|||||||
import ApiServerService from "@core/Services/Api.Server.Service";
|
import ApiServerService from "@core/Services/Api.Server.Service";
|
||||||
import { PublicServicesResults } from "@models/Service.model";
|
import { PublicServicesResults } from "@models/Service.model";
|
||||||
import { sliderFrames } from "@core/app/Services/SliderFrames";
|
import { sliderFrames } from "@core/app/Services/SliderFrames";
|
||||||
|
import { getAllVideos } from "@core/app/landing/help/videoData";
|
||||||
|
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "https://turnosxpress.com.ar";
|
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "https://turnosxpress.com.ar";
|
||||||
@@ -42,6 +43,25 @@ export async function GET() {
|
|||||||
changeFrequency: "weekly",
|
changeFrequency: "weekly",
|
||||||
priority: 0.8,
|
priority: 0.8,
|
||||||
},
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// Dynamic video routes (one per tutorial)
|
||||||
|
const allVideos = getAllVideos();
|
||||||
|
const videoRoutes: SitemapRoute[] = allVideos.map((video) => ({
|
||||||
|
url: `${baseUrl}/landing/help/video/${video.id}`,
|
||||||
|
changeFrequency: "monthly",
|
||||||
|
priority: 0.6,
|
||||||
|
images: [
|
||||||
|
{
|
||||||
|
url: video.thumbnail,
|
||||||
|
title: video.title,
|
||||||
|
caption: video.description,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Login and signup routes (lower priority)
|
||||||
|
const authRoutes: SitemapRoute[] = [
|
||||||
{
|
{
|
||||||
url: `${baseUrl}/landing/login`,
|
url: `${baseUrl}/landing/login`,
|
||||||
changeFrequency: "monthly",
|
changeFrequency: "monthly",
|
||||||
@@ -52,6 +72,10 @@ export async function GET() {
|
|||||||
changeFrequency: "monthly",
|
changeFrequency: "monthly",
|
||||||
priority: 0.5,
|
priority: 0.5,
|
||||||
},
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// Marketing pages
|
||||||
|
const marketingRoutes: SitemapRoute[] = [
|
||||||
{
|
{
|
||||||
url: `${baseUrl}/landing/offer-appointments`,
|
url: `${baseUrl}/landing/offer-appointments`,
|
||||||
changeFrequency: "weekly",
|
changeFrequency: "weekly",
|
||||||
@@ -107,7 +131,7 @@ export async function GET() {
|
|||||||
console.error("Error generating sitemap:", error);
|
console.error("Error generating sitemap:", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
const allRoutes = [...staticRoutes, ...serviceRoutes];
|
const allRoutes = [...staticRoutes, ...videoRoutes, ...authRoutes, ...marketingRoutes, ...serviceRoutes];
|
||||||
|
|
||||||
const sitemapXml = `<?xml version="1.0" encoding="UTF-8"?>
|
const sitemapXml = `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
|
||||||
|
|||||||
Reference in New Issue
Block a user