Files
Metify/src/components/meta-form.tsx

361 lines
12 KiB
TypeScript

"use client";
import { useEffect, useState, useMemo } from "react";
import { Card, CardContent } from "@/components/ui/card";
import { extractMetaData } from "@/app/actions";
import { ResultsSkeleton } from "./results-skeleton";
import { FaqDisplay } from "./faq-display";
import { HeadlineTree } from "./headline-tree";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { ImageAltDisplay } from "./image-alt-display";
import { TabIndicator } from "./tab-indicator";
import { getLengthIndicatorColor, type IndicatorColor } from "@/lib/analysis";
import { SchemaDisplay } from "./schema-display";
import { MetaFormInputs } from "./meta-form-inputs";
import { AnalysisTab } from "./analysis-tab";
import { SocialTab } from "./social-tab";
import { LinksDisplay } from "./links-display";
import { SystemDisplay } from "./system-display";
import { TrackingDisplay } from "./tracking-display";
import type { MetaData } from "@/lib/types";
export function MetaForm() {
const [url, setUrl] = useState("");
const [keyword, setKeyword] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [metaData, setMetaData] = useState<MetaData | null>(null);
const [isEditingTitle, setIsEditingTitle] = useState(false);
const [isEditingDescription, setIsEditingDescription] = useState(false);
const [editableTitle, setEditableTitle] = useState("");
const [editableDescription, setEditableDescription] = useState("");
const [imageError, setImageError] = useState(false);
useEffect(() => {
if (metaData) {
setEditableTitle(metaData.title);
setEditableDescription(metaData.description);
setImageError(false);
}
}, [metaData]);
const tabColors = useMemo(() => {
if (!metaData) return null;
// Analysis Tab
const titleStatus = getLengthIndicatorColor(editableTitle.length, "title");
const descStatus = getLengthIndicatorColor(
editableDescription.length,
"description"
);
const analysisStatuses: IndicatorColor[] = [titleStatus, descStatus];
let analysisColor: IndicatorColor;
if (
analysisStatuses.includes("red") ||
analysisStatuses.includes("gray")
) {
analysisColor = "red";
} else if (analysisStatuses.includes("yellow")) {
analysisColor = "yellow";
} else {
analysisColor = "green";
}
// Headlines Tab
let headlinesColor: IndicatorColor = "gray";
if (metaData.headlines && metaData.headlines.length > 0) {
const h1s = metaData.headlines.filter((h) => h.tag === "h1");
if (h1s.length === 0) {
headlinesColor = "red";
} else if (h1s.length > 1) {
headlinesColor = "yellow";
} else {
headlinesColor = "green";
}
}
// Images Tab
let imagesColor: IndicatorColor = "gray";
if (metaData.images && metaData.images.length > 0) {
const totalImages = metaData.images.length;
const missingAltCount = metaData.images.filter((img) => !img.alt).length;
if (missingAltCount === 0) {
imagesColor = "green";
} else {
const missingPercentage = missingAltCount / totalImages;
if (missingPercentage > 0.5) {
imagesColor = "red";
} else {
imagesColor = "yellow";
}
}
}
// Social Tab
let socialColor: IndicatorColor = "gray";
if (metaData.openGraph && metaData.twitter) {
const og = metaData.openGraph;
const tw = metaData.twitter;
const hasOgBasics = og.title && og.description && og.image;
const hasTwBasics = tw.card && tw.title && tw.description && tw.image;
if (hasOgBasics && hasTwBasics) {
socialColor = "green";
} else if (hasOgBasics || (tw.card && tw.title)) {
socialColor = "yellow";
} else {
socialColor = "red";
}
}
// Links Tab
let linksColor: IndicatorColor = "gray";
if (metaData.links && metaData.links.length > 0) {
const hasMissingNofollow = metaData.links.some(
(link) => link.type === "external" && !link.rel.includes("nofollow")
);
linksColor = hasMissingNofollow ? "yellow" : "green";
}
// FAQ Tab
let faqColor: IndicatorColor = "gray";
if (metaData.faq && metaData.faq.length > 0) {
faqColor = "green";
}
// Schema Tab
let schemaColor: IndicatorColor = "gray";
if (metaData.schema && metaData.schema.length > 0) {
schemaColor = "green";
}
// System Tab
const systemColor: IndicatorColor =
metaData.systems && metaData.systems.length > 0 ? "green" : "gray";
// Tracking Tab
const trackingColor: IndicatorColor =
metaData.tracking && metaData.tracking.length > 0 ? "green" : "gray";
return {
analysis: analysisColor,
headlines: headlinesColor,
images: imagesColor,
social: socialColor,
links: linksColor,
faq: faqColor,
schema: schemaColor,
system: systemColor,
tracking: trackingColor,
};
}, [metaData, editableTitle, editableDescription]);
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
setLoading(true);
setError(null);
setMetaData(null);
setIsEditingTitle(false);
setIsEditingDescription(false);
setImageError(false);
const result = await extractMetaData(url, keyword);
if (result.error) {
setError(result.error);
} else if (result.data) {
setMetaData(result.data as MetaData);
}
setLoading(false);
};
const handleClear = () => {
setUrl("");
setKeyword("");
setLoading(false);
setError(null);
setMetaData(null);
};
return (
<div className="w-full space-y-6">
<MetaFormInputs
url={url}
setUrl={setUrl}
keyword={keyword}
setKeyword={setKeyword}
handleSubmit={handleSubmit}
handleClear={handleClear}
loading={loading}
showClearButton={!!(url || metaData || error)}
/>
{loading && <ResultsSkeleton />}
{!loading && error && (
<Card className="border-destructive bg-destructive/10">
<CardContent className="p-4">
<p className="text-destructive text-center">{error}</p>
</CardContent>
</Card>
)}
{!loading && metaData && (
<Tabs defaultValue="analysis" className="w-full">
<TabsList className="mb-4">
<TabsTrigger value="analysis">
Meta Analysis
{tabColors && <TabIndicator color={tabColors.analysis} />}
</TabsTrigger>
{metaData.headlines && metaData.headlines.length > 0 && (
<TabsTrigger value="headlines">
Headlines
{tabColors && <TabIndicator color={tabColors.headlines} />}
</TabsTrigger>
)}
{metaData.images && metaData.images.length > 0 && (
<TabsTrigger value="images">
Images
{tabColors && <TabIndicator color={tabColors.images} />}
</TabsTrigger>
)}
{metaData.links && metaData.links.length > 0 && (
<TabsTrigger value="links">
Links
{tabColors && <TabIndicator color={tabColors.links} />}
</TabsTrigger>
)}
{metaData.openGraph && metaData.twitter && (
<TabsTrigger value="social">
Social
{tabColors && <TabIndicator color={tabColors.social} />}
</TabsTrigger>
)}
{metaData.schema && metaData.schema.length > 0 && (
<TabsTrigger value="schema">
Schema
{tabColors && <TabIndicator color={tabColors.schema} />}
</TabsTrigger>
)}
{metaData.systems && metaData.systems.length > 0 && (
<TabsTrigger value="system">
System
{tabColors && <TabIndicator color={tabColors.system} />}
</TabsTrigger>
)}
{metaData.tracking && metaData.tracking.length > 0 && (
<TabsTrigger value="tracking">
Tracking
{tabColors && <TabIndicator color={tabColors.tracking} />}
</TabsTrigger>
)}
{metaData.faq && metaData.faq.length > 0 && (
<TabsTrigger value="faq">
FAQ
{tabColors && <TabIndicator color={tabColors.faq} />}
</TabsTrigger>
)}
</TabsList>
<TabsContent value="analysis">
<AnalysisTab
metaData={metaData}
url={url}
editableTitle={editableTitle}
setEditableTitle={setEditableTitle}
isEditingTitle={isEditingTitle}
setIsEditingTitle={setIsEditingTitle}
editableDescription={editableDescription}
setEditableDescription={setEditableDescription}
isEditingDescription={isEditingDescription}
setIsEditingDescription={setIsEditingDescription}
imageError={imageError}
setImageError={setImageError}
/>
</TabsContent>
{metaData.headlines && metaData.headlines.length > 0 && (
<TabsContent value="headlines">
<Card className="w-full shadow-lg rounded-lg">
<CardContent className="p-0">
<HeadlineTree
headlines={metaData.headlines}
keyword={metaData.keyword}
/>
</CardContent>
</Card>
</TabsContent>
)}
{metaData.images && metaData.images.length > 0 && (
<TabsContent value="images">
<Card className="w-full shadow-lg rounded-lg">
<CardContent className="p-6">
<ImageAltDisplay
images={metaData.images}
keyword={metaData.keyword}
/>
</CardContent>
</Card>
</TabsContent>
)}
{metaData.links && metaData.links.length > 0 && (
<TabsContent value="links">
<Card className="w-full shadow-lg rounded-lg">
<CardContent className="p-6">
<LinksDisplay links={metaData.links} />
</CardContent>
</Card>
</TabsContent>
)}
{metaData.openGraph && metaData.twitter && (
<TabsContent value="social">
<SocialTab
openGraph={metaData.openGraph}
twitter={metaData.twitter}
/>
</TabsContent>
)}
{metaData.faq && metaData.faq.length > 0 && (
<TabsContent value="faq">
<Card className="w-full shadow-lg rounded-lg">
<CardContent className="p-6">
<FaqDisplay faqs={metaData.faq} />
</CardContent>
</Card>
</TabsContent>
)}
{metaData.schema && metaData.schema.length > 0 && (
<TabsContent value="schema">
<Card className="w-full shadow-lg rounded-lg">
<CardContent className="p-6">
<SchemaDisplay schemas={metaData.schema} />
</CardContent>
</Card>
</TabsContent>
)}
{metaData.systems && metaData.systems.length > 0 && (
<TabsContent value="system">
<SystemDisplay systems={metaData.systems} />
</TabsContent>
)}
{metaData.tracking && metaData.tracking.length > 0 && (
<TabsContent value="tracking">
<TrackingDisplay tools={metaData.tracking} />
</TabsContent>
)}
</Tabs>
)}
</div>
);
}