Merge branch 'main' of https://app.gitpasha.com/mohammedsaid18/dashboard
هذا الالتزام موجود في:
18
src/App.css
18
src/App.css
@@ -24,6 +24,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1500px) {
|
||||
.container {
|
||||
max-width: 1500px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1600px) {
|
||||
.container {
|
||||
max-width: 1600px;
|
||||
@@ -40,3 +46,15 @@
|
||||
focus: ring-0 !important;
|
||||
focus: border-none !important;
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) and (max-width: 1250px) {
|
||||
.pr-m-5 {
|
||||
padding-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) and (max-width: 1296px) {
|
||||
.w-m-40 {
|
||||
width: 40% !important;
|
||||
}
|
||||
}
|
||||
@@ -95,14 +95,14 @@ const CampaignTracker = ({ handleMenuClick }: { handleMenuClick: () => void }) =
|
||||
</div>
|
||||
|
||||
{/* Progress Sections */}
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-8">
|
||||
{progressSections.map((section, index) => {
|
||||
const progressPercentage = (section.current / section.total) * 100
|
||||
|
||||
return (
|
||||
<div key={index} className="space-y-1">
|
||||
{/* Section Title and Progress */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<h3 className="text-sm font-semibold text-gray-900">
|
||||
{section.title}: {section.current}/{section.total}
|
||||
</h3>
|
||||
|
||||
@@ -9,13 +9,16 @@ import {
|
||||
Info,
|
||||
ArrowUp,
|
||||
Link,
|
||||
ChevronDown
|
||||
ChevronDown,
|
||||
MapPin,
|
||||
Sparkles
|
||||
} from "lucide-react";
|
||||
import MetricCard from "./shared/MetricCard";
|
||||
import UrlDropdown from "./shared/UrlDropdown";
|
||||
import HeaderPage from "./shared/HeaderPage";
|
||||
import Google from "../assets/icons/google-original-logo.svg";
|
||||
import Gemini from "../assets/icons/gemini.svg";
|
||||
import Gemini2 from "../assets/icons/gemini2.svg";
|
||||
import Perplexity from "../assets/icons/perplexity-dark-logo.svg";
|
||||
import Vector from "../assets/icons/Vector.png";
|
||||
import Vector2 from "../assets/icons/Vector2.png";
|
||||
@@ -236,6 +239,103 @@ const CARDS = [
|
||||
}
|
||||
];
|
||||
|
||||
const competitors = [
|
||||
{
|
||||
domain: "Cherrybekaertla.com",
|
||||
googleRank: 1,
|
||||
mapPins: 7,
|
||||
hasAi: true,
|
||||
hasCircle: true,
|
||||
hasDiamond: true,
|
||||
hasTree: true,
|
||||
hasLLM: true,
|
||||
tags: [
|
||||
{
|
||||
content: [
|
||||
{ icon: "Google", value: "1" }
|
||||
],
|
||||
color: "#4285F4"
|
||||
},
|
||||
{
|
||||
content: [
|
||||
{ icon: "Map", value: "7", }
|
||||
],
|
||||
color: "#4285F4"
|
||||
},
|
||||
{
|
||||
content: [
|
||||
{ icon: "Ai" },
|
||||
{ icon: "Perplexity" },
|
||||
{ icon: "GPT" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
domain: "Gerbercompany.com",
|
||||
googleRank: 1,
|
||||
mapPins: 7,
|
||||
hasAi: true,
|
||||
hasCircle: true,
|
||||
hasDiamond: true,
|
||||
hasTree: true,
|
||||
hasLLM: true,
|
||||
tags: [
|
||||
{
|
||||
content: [
|
||||
{ icon: "Google", value: "1" }
|
||||
],
|
||||
color: "#4285F4"
|
||||
},
|
||||
{
|
||||
content: [
|
||||
{ icon: "Map", value: "7", }
|
||||
],
|
||||
color: "#4285F4"
|
||||
},
|
||||
{
|
||||
content: [
|
||||
{ icon: "Ai" },
|
||||
{ icon: "Perplexity" },
|
||||
{ icon: "GPT" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
domain: "Mysite.com",
|
||||
googleRank: 1,
|
||||
mapPins: 7,
|
||||
hasAi: true,
|
||||
hasCircle: true,
|
||||
hasDiamond: true,
|
||||
hasTree: true,
|
||||
hasLLM: true,
|
||||
tags: [
|
||||
{
|
||||
content: [
|
||||
{ icon: "Google", value: "1" }
|
||||
],
|
||||
color: "#4285F4"
|
||||
},
|
||||
{
|
||||
content: [
|
||||
{ icon: "Map", value: "7", }
|
||||
],
|
||||
color: "#4285F4"
|
||||
},
|
||||
{
|
||||
content: [
|
||||
{ icon: "Ai" },
|
||||
{ icon: "Perplexity" },
|
||||
{ icon: "GPT" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
const pillClass = (val: string) =>
|
||||
val === 'Done' ? 'bg-green-100 text-green-700 font-bold'
|
||||
: val === 'In Progress' ? 'bg-blue-100 text-blue-700 font-bold'
|
||||
@@ -247,6 +347,7 @@ export default function DashboardGrid() {
|
||||
const [activeCard, setActiveCard] = useState<string | null>(null);
|
||||
const [hoverState, setHoverState] = useState<HoverState>({ cardId: null, ringIndex: null });
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const [showKeywordAnalysis, setShowKeywordAnalysis] = useState<string | null>(null);
|
||||
|
||||
const handleCardClick = (campaignName: string, paused: boolean) => {
|
||||
if (paused) return;
|
||||
@@ -275,6 +376,14 @@ export default function DashboardGrid() {
|
||||
setHoverState({ cardId, ringIndex });
|
||||
};
|
||||
|
||||
const handleResumeClick = (campaignName: string) => {
|
||||
setShowKeywordAnalysis(campaignName);
|
||||
};
|
||||
|
||||
const handleCloseKeywordAnalysis = () => {
|
||||
setShowKeywordAnalysis(null);
|
||||
};
|
||||
|
||||
const TagComponent = ({ content, color = "#4285F4", faded = false }: Tag & { faded?: boolean }) => {
|
||||
const getIconContent = (item: TagContent) => {
|
||||
switch (item.icon) {
|
||||
@@ -282,7 +391,7 @@ export default function DashboardGrid() {
|
||||
return (
|
||||
<>
|
||||
<img src={Google} alt="Google" className="w-5 h-5" />
|
||||
{item.value && <span className="text-black font-bold text-md">{item.value}</span>}
|
||||
{item.value && <span className="text-[#17171B] font-semibold text-md">{item.value}</span>}
|
||||
{item.extra && (
|
||||
<span className={`ml-1 text-md ${item.extra.startsWith('+') ? 'text-green-600' : 'text-red-600'} font-bold`}>
|
||||
{item.extra}
|
||||
@@ -293,7 +402,7 @@ export default function DashboardGrid() {
|
||||
case "Map":
|
||||
return (
|
||||
<>
|
||||
<img src={Map} alt="Map" className="w-5 h-5" />
|
||||
<img src={Map} alt="Map" className="w-5 h-6" />
|
||||
{item.value && <span className="text-black font-bold text-md">{item.value}</span>}
|
||||
{item.extra && (
|
||||
<span className={`ml-1 text-md ${item.extra.startsWith('+') ? 'text-green-600' : 'text-red-600'} font-bold`}>
|
||||
@@ -303,11 +412,41 @@ export default function DashboardGrid() {
|
||||
</>
|
||||
);
|
||||
case "Ai":
|
||||
return <img src={Gemini} alt="AI" className="w-5 h-5" />;
|
||||
return (
|
||||
<>
|
||||
<img src={Gemini2} alt="AI" className="w-5 h-5" />
|
||||
{item.value && <span className="text-black font-bold text-md">{item.value}</span>}
|
||||
{item.extra && (
|
||||
<span className={`ml-1 text-md ${item.extra.startsWith('+') ? 'text-green-600' : 'text-red-600'} font-bold`}>
|
||||
{item.extra}
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
case "GPT":
|
||||
return <img src={GPT} alt="GPT" className="w-5 h-5" />;
|
||||
return (
|
||||
<>
|
||||
<img src={GPT} alt="GPT" className="w-5 h-5" />
|
||||
{item.value && <span className="text-black font-bold text-md">{item.value}</span>}
|
||||
{item.extra && (
|
||||
<span className={`ml-1 text-md ${item.extra.startsWith('+') ? 'text-green-600' : 'text-red-600'} font-bold`}>
|
||||
{item.extra}
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
case "Perplexity":
|
||||
return <img src={Perplexity} alt="Perplexity" className="w-5 h-5" />;
|
||||
return (
|
||||
<>
|
||||
<img src={Perplexity} alt="Perplexity" className="w-5 h-5" />
|
||||
{item.value && <span className="text-black font-bold text-md">{item.value}</span>}
|
||||
{item.extra && (
|
||||
<span className={`ml-1 text-md ${item.extra.startsWith('+') ? 'text-green-600' : 'text-red-600'} font-bold`}>
|
||||
{item.extra}
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
default:
|
||||
return item.text || null;
|
||||
}
|
||||
@@ -401,14 +540,60 @@ export default function DashboardGrid() {
|
||||
<div className={`relative w-full h-full transition-transform duration-700 ease-in-out [transform-style:preserve-3d] ${selectedCampaigns.includes(card.title) ? '[transform:rotateY(180deg)]' : ''
|
||||
}`}>
|
||||
{/* Front of card */}
|
||||
<div className={`absolute w-full h-full [backface-visibility:hidden] bg-white rounded-2xl shadow-lg border transition-all duration-300 ease-in-out cursor-pointer ${card.faded ? "opacity-60" : ""}
|
||||
${selectedCampaigns.includes(card.title) ? 'shadow-2xl scale-[1.02]' : 'hover:scale-105 hover:shadow-xl'
|
||||
<div className={`absolute w-full h-full [backface-visibility:hidden] bg-white rounded-2xl border transition-all duration-300 ease-in-out cursor-pointer ${card.faded ? "opacity- 60" : ""}
|
||||
${selectedCampaigns.includes(card.title) ? 'shadow-md scale-[1.02]' : 'hover:scale-135 hover:shadow-md'
|
||||
}`}
|
||||
style={{
|
||||
transform: selectedCampaigns.includes(card.title)
|
||||
? 'translateY(-5px) scale(1.02)'
|
||||
: 'translateY(0px) scale(1)'
|
||||
}}>
|
||||
{
|
||||
card.paused && showKeywordAnalysis === card.title
|
||||
?
|
||||
<div className="bg-[#f6f8fe] rounded-xl shadow-sm border border-gray-200 p-6 px-20 h-full">
|
||||
{/* New Keyword Badge */}
|
||||
|
||||
<div className="flex justify-center mb-4">
|
||||
<div className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-sm font-medium text-[#197232]" style={{ background: "linear-gradient(90deg, #d6fbe2 30%, #edf9d5 100%)" }}>
|
||||
<Sparkles className="w-4 h-4" />
|
||||
NEW High-Value Keyword Found
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Keyword Title */}
|
||||
<h2 className="text-2xl font-bold text-gray-900 text-center mb-6">Tax Prep Attorney Los Angeles</h2>
|
||||
|
||||
{/* Competitors List */}
|
||||
<div className="space-y-4 mb-6">
|
||||
{competitors.map((competitor, index) => (
|
||||
<div key={index} className="space-y-2">
|
||||
<div className="text-md text-gray-600 font-bold">{competitor.domain}</div>
|
||||
<div className="flex items-center gap-3 flex-wrap">
|
||||
{competitor.tags.map((tag, i) => (
|
||||
<TagComponent
|
||||
key={i}
|
||||
{...tag}
|
||||
faded={card.faded}
|
||||
/>
|
||||
))}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Bottom CTA */}
|
||||
<div className="text-center">
|
||||
<p className="text-md text-gray-500 ">Your competitors own this keyword.</p>
|
||||
<p className="text-md font-semibold text-[#2B2D3B] mb-3">Add it to capture visibility and sales.</p>
|
||||
<button className="inline-flex items-center gap-2 px-6 py-2 bg-indigo-600 hover:bg-indigo-700 text-white font-medium rounded-lg transition-colors">
|
||||
<span className="text-lg">+</span>
|
||||
Add Keyword
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
:
|
||||
<div className="p-5 flex flex-col items-center h-full">
|
||||
{card.badge && (
|
||||
<div className="absolute top-0 right-0 overflow-hidden w-32 h-32 pointer-events-none">
|
||||
@@ -447,13 +632,27 @@ export default function DashboardGrid() {
|
||||
{card.actionLink}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{card.paused && (
|
||||
<div className="mt-1 text-center">
|
||||
<div className="text-gray-400 text-sm">Campaign paused</div>
|
||||
<button className="text-blue-600 text-sm hover:underline transition-all duration-300 hover:text-blue-800">Resume</button>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleResumeClick(card.title);
|
||||
}}
|
||||
className="text-blue-600 text-sm hover:underline transition-all duration-300 hover:text-blue-800"
|
||||
>
|
||||
Resume
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
)
|
||||
|
||||
}
|
||||
</div>
|
||||
|
||||
}
|
||||
|
||||
</div>
|
||||
|
||||
{/* Back of card */}
|
||||
@@ -536,6 +735,7 @@ export default function DashboardGrid() {
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -64,6 +64,7 @@ export default function MultiRingCircularProgress({ card, hoveredIndex, onHover,
|
||||
onMouseEnter={() => onHover(index)}
|
||||
onMouseLeave={() => onHover(null)}
|
||||
fill={getHoverColor(ring, index)}
|
||||
className="focus:outline-none focus:ring-0 focus:border-none"
|
||||
// pointerEvents={'none'}
|
||||
>
|
||||
<Cell
|
||||
|
||||
@@ -28,13 +28,14 @@ const BacklinkTypeDropdown = ({ options, title, subTitle }: { options: Record<st
|
||||
return (
|
||||
<div className='relative' ref={dropdownRef}>
|
||||
<button
|
||||
className='border rounded-lg px-3 py-1 bg-white text-gray-500 font-medium flex items-center text-md'
|
||||
className={`border rounded-lg px-3 py-1 bg-white text-gray-500 font-medium flex items-center text-md ${dropdownOpen ? "border-2 border-solid border-blue-500" : ""}`}
|
||||
onClick={() => setDropdownOpen(v => !v)}
|
||||
>
|
||||
{title} <ChevronDown className='ml-2' />
|
||||
{title}
|
||||
<ChevronDown className='ml-2' />
|
||||
</button>
|
||||
{dropdownOpen && (
|
||||
<div className='absolute z-10 bg-white shadow-lg border rounded-lg top-[110%] right-0 px-4 py-3 min-w-[150px]'>
|
||||
<div className={`absolute z-10 bg-white shadow-lg border rounded-lg top-[110%] right-0 px-4 py-3 min-w-[150px] `}>
|
||||
<div className='font-semibold text-md text-gray-700 mb-2'>{subTitle}:</div>
|
||||
{Object.keys(checkedRings).map((ring, idx) => (
|
||||
<label
|
||||
|
||||
@@ -35,7 +35,7 @@ function ProgressMetrics() {
|
||||
<div className="w-[100%]">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-lg 3xl:text-4xl font-bold text-gray-900 mb-1">60%</div>
|
||||
<div className="text-lg 3xl:text-3xl font-bold text-gray-900 mb-1">120/250 <span className="text-gray-500">links</span></div>
|
||||
<div className="text-md 3xl:text-3xl font-bold text-gray-900 mb-1">120/250 <span className="text-gray-500">links</span></div>
|
||||
</div>
|
||||
<div className="text-xs 2xl:text-sm text-gray-500 mb-2">until content publishing + LLM seeding unlocked</div>
|
||||
{/* Progress bar */}
|
||||
@@ -49,7 +49,7 @@ function ProgressMetrics() {
|
||||
|
||||
</div>
|
||||
|
||||
<div className="flex items-center md:w-1/3 w-full md:border-r-2 border-0 border-solid border-gray-300 2xl:gap-12 lg:gap-6 gap-3">
|
||||
<div className="flex items-center md:w-1/3 w-full md:border-r-2 border-0 border-solid border-gray-300 2xl:gap-12 lg:gap-3 gap-3 pr-m-5 w-m-40">
|
||||
{/* 75% Keyword Visibility */}
|
||||
<div className="w-[30%]">
|
||||
<div className="text-xl 2xl:text-3xl font-bold text-gray-900 mb-1">75%</div>
|
||||
@@ -57,16 +57,12 @@ function ProgressMetrics() {
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3 w-[100%]">
|
||||
<div className="flex items-center gap-3 flex-wrap">
|
||||
<div className="flex items-center gap-3 ">
|
||||
<MetricCard
|
||||
img={Google} tooltipText="Google" valueChange="+1" size="sm" disabled />
|
||||
|
||||
<MetricCard
|
||||
img={Map} tooltipText="Map" valueChange="+1" size="sm" disabled value="Map" />
|
||||
|
||||
{/* <MetricCard
|
||||
img={Gemini} tooltipText="ChatGPT" valueChange="+1" size="sm" extraStyle="border-2 border-solid border-gray-300 rounded-lg " /> */}
|
||||
|
||||
<MetricCard
|
||||
img={ChatGPT} tooltipText="ChatGPT" valueChange="+1" size="sm" />
|
||||
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import Vector from "../assets/icons/Vector.png"
|
||||
import Vector2 from "../assets/icons/Vector2.png"
|
||||
import BacklinkTypeDropdown from '../components/Portfolio/BacklinkTypeDropdown';
|
||||
import RoadmapSidebar from '../components/Strategy/RoadmapSidebar';
|
||||
import HeaderPage from '../components/shared/HeaderPage';
|
||||
import ContainerPage from '../components/shared/ContainerPage';
|
||||
import Amazing from "../assets/icons/amazon.png"
|
||||
import Amazing from "../assets/icons/amazon.svg"
|
||||
import { CircleCheck } from 'lucide-react';
|
||||
// import Pinterest from "../assets/icons/pinterest.png"
|
||||
// import AppleMusic from "../assets/icons/apple-music.png"
|
||||
// import Substack from "../assets/icons/substack.png"
|
||||
// import Pin from "../assets/icons/pin.png"
|
||||
import Progress from "../assets/icons/InProgress.svg"
|
||||
import No from "../assets/icons/NA.svg"
|
||||
import Share2 from "../assets/icons/Share2.png"
|
||||
@@ -108,7 +102,7 @@ function Portfolio() {
|
||||
|
||||
<BacklinkTypeDropdown options={{ DA: true, DR: true, Traffic: true }} title="Traffic" subTitle="Show rings:" />
|
||||
|
||||
<button className='bg-[#fff] text-gray-500 border px-3 sm:px-4 py-1 rounded-lg text-sm sm:text-md font-bold transition-colors flex items-center gap-2'>
|
||||
<button className='bg-[#fff] text-gray-500 border px-3 sm:px-4 py-2 rounded-lg text-sm sm:text-md font-bold transition-colors flex items-center gap-2'>
|
||||
<img src={Share2} alt="" className='w-4 h-4' />
|
||||
<span>Export</span>
|
||||
</button>
|
||||
|
||||
المرجع في مشكلة جديدة
حظر مستخدم