From e40c494e0a4b0e47d6ca8378925ceedb497e4c08 Mon Sep 17 00:00:00 2001 From: "[dyad]" Date: Tue, 20 Jan 2026 14:37:17 +0100 Subject: [PATCH] [dyad] Added status indicators to tabs - wrote 4 file(s) --- src/components/length-indicator.tsx | 17 ++++-- src/components/meta-form.tsx | 86 +++++++++++++++++++++++++++-- src/components/tab-indicator.tsx | 26 +++++++++ src/lib/analysis.ts | 30 ++++++++++ 4 files changed, 147 insertions(+), 12 deletions(-) create mode 100644 src/components/tab-indicator.tsx create mode 100644 src/lib/analysis.ts diff --git a/src/components/length-indicator.tsx b/src/components/length-indicator.tsx index 72c1dae..0b04584 100644 --- a/src/components/length-indicator.tsx +++ b/src/components/length-indicator.tsx @@ -7,6 +7,7 @@ import { TooltipTrigger, } from "@/components/ui/tooltip"; import { cn } from "@/lib/utils"; +import { getLengthIndicatorColor } from "@/lib/analysis"; interface LengthIndicatorProps { length: number; @@ -24,20 +25,24 @@ const DESCRIPTION_THRESHOLDS = { }; export function LengthIndicator({ length, type }: LengthIndicatorProps) { + const color = getLengthIndicatorColor(length, type); + + const colorClassMap = { + green: "bg-green-500", + yellow: "bg-yellow-500", + red: "bg-red-500", + gray: "bg-gray-400", + }; + const thresholds = type === "title" ? TITLE_THRESHOLDS : DESCRIPTION_THRESHOLDS; - - let colorClass = "bg-red-500"; let tooltipText = "Length is not optimal"; if (length === 0) { tooltipText = "Not found"; - colorClass = "bg-gray-400"; } else if (length >= thresholds.good.min && length <= thresholds.good.max) { - colorClass = "bg-green-500"; tooltipText = "Optimal length"; } else if (length >= thresholds.ok.min && length <= thresholds.ok.max) { - colorClass = "bg-yellow-500"; tooltipText = "Length could be improved"; } else if (length < thresholds.ok.min) { tooltipText = "Too short"; @@ -49,7 +54,7 @@ export function LengthIndicator({ length, type }: LengthIndicatorProps) { -
+

diff --git a/src/components/meta-form.tsx b/src/components/meta-form.tsx index cdfb4ea..b1b7587 100644 --- a/src/components/meta-form.tsx +++ b/src/components/meta-form.tsx @@ -1,6 +1,6 @@ "use client"; -import { useEffect, useState } from "react"; +import { useEffect, useState, useMemo } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; @@ -14,7 +14,11 @@ import { ImageOff, Search, } from "lucide-react"; -import { extractMetaData, type HeadlineNode, type ImageAltData } from "@/app/actions"; +import { + extractMetaData, + type HeadlineNode, + type ImageAltData, +} from "@/app/actions"; import { LengthIndicator } from "./length-indicator"; import { CopyButton } from "./copy-button"; import { SerpPreview } from "./serp-preview"; @@ -23,6 +27,8 @@ 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"; interface MetaData { title: string; @@ -57,6 +63,62 @@ export function MetaForm() { } }, [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 missingAltCount = metaData.images.filter((img) => !img.alt).length; + imagesColor = missingAltCount > 0 ? "red" : "green"; + } + + // FAQ Tab + let faqColor: IndicatorColor = "gray"; + if (metaData.faq && metaData.faq.length > 0) { + faqColor = "green"; + } + + return { + analysis: analysisColor, + headlines: headlinesColor, + images: imagesColor, + faq: faqColor, + }; + }, [metaData, editableTitle, editableDescription]); + const handleSubmit = async (event: React.FormEvent) => { event.preventDefault(); setLoading(true); @@ -149,15 +211,27 @@ export function MetaForm() { {!loading && metaData && ( - Meta Analysis + + {tabColors && } + Meta Analysis + {metaData.headlines && metaData.headlines.length > 0 && ( - Headlines + + {tabColors && } + Headlines + )} {metaData.images && metaData.images.length > 0 && ( - Images + + {tabColors && } + Images + )} {metaData.faq && metaData.faq.length > 0 && ( - FAQ + + {tabColors && } + FAQ + )} diff --git a/src/components/tab-indicator.tsx b/src/components/tab-indicator.tsx new file mode 100644 index 0000000..527cdd7 --- /dev/null +++ b/src/components/tab-indicator.tsx @@ -0,0 +1,26 @@ +"use client"; + +import { cn } from "@/lib/utils"; +import type { IndicatorColor } from "@/lib/analysis"; + +interface TabIndicatorProps { + color: IndicatorColor; +} + +export function TabIndicator({ color }: TabIndicatorProps) { + const colorClassMap = { + green: "bg-green-500", + yellow: "bg-yellow-500", + red: "bg-red-500", + gray: "bg-gray-400", + }; + + return ( +

+ ); +} \ No newline at end of file diff --git a/src/lib/analysis.ts b/src/lib/analysis.ts new file mode 100644 index 0000000..ccb8499 --- /dev/null +++ b/src/lib/analysis.ts @@ -0,0 +1,30 @@ +export type IndicatorColor = "green" | "yellow" | "red" | "gray"; + +const TITLE_THRESHOLDS = { + good: { min: 30, max: 60 }, + ok: { min: 15, max: 70 }, +}; + +const DESCRIPTION_THRESHOLDS = { + good: { min: 120, max: 158 }, + ok: { min: 50, max: 170 }, +}; + +export function getLengthIndicatorColor( + length: number, + type: "title" | "description" +): IndicatorColor { + const thresholds = + type === "title" ? TITLE_THRESHOLDS : DESCRIPTION_THRESHOLDS; + + if (length === 0) { + return "gray"; + } + if (length >= thresholds.good.min && length <= thresholds.good.max) { + return "green"; + } + if (length >= thresholds.ok.min && length <= thresholds.ok.max) { + return "yellow"; + } + return "red"; +} \ No newline at end of file