هذا الالتزام موجود في:
Your Name
2025-10-26 08:07:11 +00:00
الأصل 954fc4c34a
التزام 73e2f11fe5
3 ملفات معدلة مع 149 إضافات و16 حذوفات

عرض الملف

@@ -6,11 +6,12 @@ import { ActionBadge } from "./shared/ActionBadge";
interface KeywordRowProps { interface KeywordRowProps {
keyword: KeywordRowType; keyword: KeywordRowType;
hasGmb: boolean;
} }
export function KeywordRow({ keyword }: KeywordRowProps) { export function KeywordRow({ keyword, hasGmb }: KeywordRowProps) {
return ( return (
<div className="grid grid-cols-12 gap-4 justify-items-cente px-6 pr-[52px] py-3 border-t border-grey-300 min-h-[54px] hover:bg-gray-100 transition-colors"> <div className={`grid ${hasGmb ? 'grid-cols-12' : 'grid-cols-11'} gap-4 justify-items-cente px-6 pr-[52px] py-3 border-t border-grey-300 min-h-[54px] hover:bg-gray-100 transition-colors`}>
<div className="flex items-center gap-1 mr-20 col-span-2" > <div className="flex items-center gap-1 mr-20 col-span-2" >
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" className="shrink-0"> <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" className="shrink-0">
@@ -27,9 +28,11 @@ export function KeywordRow({ keyword }: KeywordRowProps) {
<MetricBadge type="google" value={keyword.googlePos} /> <MetricBadge type="google" value={keyword.googlePos} />
</div> </div>
<div> {hasGmb && (
{keyword.mapsPos && <MetricBadge type="maps" value={keyword.mapsPos} />} <div>
</div> {keyword.mapsPos && <MetricBadge type="maps" value={keyword.mapsPos} />}
</div>
)}
<div> <div>
<VisibilityIndicator platform="aiOverview" visible={keyword.aiOverview === "visible"} /> <VisibilityIndicator platform="aiOverview" visible={keyword.aiOverview === "visible"} />

عرض الملف

@@ -6,9 +6,10 @@ import MetricTooltip from "./shared/MetricTooltip";
interface KeywordSectionProps { interface KeywordSectionProps {
section: KeywordSectionType; section: KeywordSectionType;
hasGmb: boolean;
} }
export function KeywordSection({ section }: KeywordSectionProps) { export function KeywordSection({ section, hasGmb }: KeywordSectionProps) {
const [isExpanded, setIsExpanded] = useState(section.expanded); const [isExpanded, setIsExpanded] = useState(section.expanded);
return ( return (
@@ -46,9 +47,11 @@ export function KeywordSection({ section }: KeywordSectionProps) {
<div> <div>
<span className="text-xs font-semibold text-grey-800 leading-[18px]">G Pos #</span> <span className="text-xs font-semibold text-grey-800 leading-[18px]">G Pos #</span>
</div> </div>
<div> {hasGmb && (
<span className="text-xs font-semibold text-grey-800 leading-[18px]">Maps</span> <div>
</div> <span className="text-xs font-semibold text-grey-800 leading-[18px]">Maps</span>
</div>
)}
<div> <div>
<span className="text-xs font-semibold text-grey-800 leading-[18px]">AI Overview</span> <span className="text-xs font-semibold text-grey-800 leading-[18px]">AI Overview</span>
</div> </div>
@@ -75,7 +78,7 @@ export function KeywordSection({ section }: KeywordSectionProps) {
<div className="bg-grey-50"> <div className="bg-grey-50">
{section.keywords.map((keyword) => ( {section.keywords.map((keyword) => (
<KeywordRow key={keyword.id} keyword={keyword} /> <KeywordRow key={keyword.id} keyword={keyword} hasGmb={hasGmb} />
))} ))}
</div> </div>

عرض الملف

@@ -1,22 +1,149 @@
import React from 'react' import React, { useState, useEffect } from 'react'
import { keywordSections } from "../assets/keyword-data";
import { KeywordSection } from "../components/KeywordSection"; import { KeywordSection } from "../components/KeywordSection";
import ContainerPage from '../components/shared/ContainerPage'; import ContainerPage from '../components/shared/ContainerPage';
import HeaderPage from '../components/shared/HeaderPage'; import HeaderPage from '../components/shared/HeaderPage';
interface ApiKeywordResponse {
domain: string;
results: {
bofu: string[];
mofu: string[];
tofu: string[];
};
}
interface GMBResponse {
has_gmb: boolean;
}
const Keywords = () => { const Keywords = () => {
const [keywordSections, setKeywordSections] = useState<any[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [hasGmb, setHasGmb] = useState<boolean>(false);
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true);
// Fetch GMB status
const gmbResponse = await fetch('https://has-gmb-e7d107688dea.hosted.ghaymah.systems/has_gmb?domain=https://www.rjmedspa.com');
const gmbData: GMBResponse = await gmbResponse.json();
setHasGmb(gmbData.has_gmb);
// Fetch keywords
const keywordsResponse = await fetch('https://keyword-funnel-api-abbc7b57dc18.hosted.ghaymah.systems/get_categorized_keywords?domain=https://www.rjmedspa.com/');
const keywordsData: ApiKeywordResponse = await keywordsResponse.json();
// Transform API data to match expected format
const transformedSections = [
{
id: "bofu",
title: "Transactional, Immediate Business Impact, Highest Competition Opportunities (Bottom of Funnel - BoFu)",
subtitle: "High Conversion Intent. Immediate Revenue Drivers.",
expanded: true,
keywords: keywordsData.results.bofu.map((keyword: string, index: number) => ({
id: `bofu-${index}`,
keyword,
searchVolume: 0,
googlePos: 0,
mapsPos: null,
aiOverview: "invisible",
chatGPT: "invisible",
gemini: "invisible",
perplexity: "invisible",
competition: "medium",
promptExplorer: "coming-soon",
status: "Add-Keyword"
}))
},
{
id: "mofu",
title: "Consideration Opportunities (Middle of Funnel - MoFu)",
subtitle: "Research and Comparison Intent. Nurturing Leads.",
expanded: false,
keywords: keywordsData.results.mofu.map((keyword: string, index: number) => ({
id: `mofu-${index}`,
keyword,
searchVolume: 0,
googlePos: 0,
mapsPos: null,
aiOverview: "invisible",
chatGPT: "invisible",
gemini: "invisible",
perplexity: "invisible",
competition: "medium",
promptExplorer: "coming-soon",
status: "Add-Keyword"
}))
},
{
id: "tofu",
title: "Educational Opportunities (Top of Funnel - ToFu)",
subtitle: "Informational Intent. Essential for building E-E-A-T and AI Trust.",
expanded: false,
keywords: keywordsData.results.tofu.map((keyword: string, index: number) => ({
id: `tofu-${index}`,
keyword,
searchVolume: 0,
googlePos: 0,
mapsPos: null,
aiOverview: "invisible",
chatGPT: "invisible",
gemini: "invisible",
perplexity: "invisible",
competition: "medium",
promptExplorer: "coming-soon",
status: "Add-Keyword"
}))
}
];
setKeywordSections(transformedSections);
setLoading(false);
} catch (err) {
setError('Failed to fetch data');
setLoading(false);
}
};
fetchData();
}, []);
if (loading) {
return (
<ContainerPage>
<HeaderPage title="Keyword Visibility Matrix" buttonShow={true} />
<div className="flex justify-center items-center h-64">
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500"></div>
</div>
</ContainerPage>
);
}
if (error) {
return (
<ContainerPage>
<HeaderPage title="Keyword Visibility Matrix" buttonShow={true} />
<div className="flex justify-center items-center h-64 text-red-500">
{error}
</div>
</ContainerPage>
);
}
return ( return (
<ContainerPage> <ContainerPage>
<HeaderPage title="Keyword Visibility Matrix" buttonShow={true} /> <HeaderPage title="Keyword Visibility Matrix" buttonShow={true} />
<div className="flex flex-col"> <div className="flex flex-col">
{keywordSections.map((section) => ( {keywordSections.map((section) => (
<KeywordSection key={section.id} section={section} /> <KeywordSection key={section.id} section={section} hasGmb={hasGmb} />
))} ))}
</div> </div>
</ContainerPage> </ContainerPage>
) )
} }
export default Keywords export default Keywords