Updated the design step 1 is finsih now I'll check it with mohammed
12
src/App.tsx
@@ -7,6 +7,8 @@ import Portfolio from './pages/Portfolio';
|
||||
|
||||
import Settings from './pages/Settings';
|
||||
import Strategy from './pages/Strategy';
|
||||
import Login from './pages/login';
|
||||
import ProtectedRoute from './components/ProtectedRoute';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
@@ -15,10 +17,12 @@ function App() {
|
||||
<div className="min-h-screen bg-white">
|
||||
<Header />
|
||||
<Routes>
|
||||
<Route path="/" element={<Dashboard />} />
|
||||
<Route path="/portfolio" element={<Portfolio />} />
|
||||
<Route path="/strategy" element={<Strategy />} />
|
||||
<Route path="/settings" element={<Settings />} />
|
||||
<Route path="/login" element={<Login />} />
|
||||
|
||||
<Route path="/" element={<ProtectedRoute><Dashboard /></ProtectedRoute>} />
|
||||
<Route path="/portfolio" element={<ProtectedRoute><Portfolio /></ProtectedRoute>} />
|
||||
<Route path="/strategy" element={<ProtectedRoute><Strategy /></ProtectedRoute>} />
|
||||
<Route path="/settings" element={<ProtectedRoute><Settings /></ProtectedRoute>} />
|
||||
</Routes>
|
||||
<Footer />
|
||||
</div>
|
||||
|
||||
ثنائية
src/assets/icons/Google.png
Normal file
|
بعد العرض: | الارتفاع: | الحجم: 598 B |
ثنائية
src/assets/icons/InProgress.png
Normal file
|
بعد العرض: | الارتفاع: | الحجم: 574 B |
ثنائية
src/assets/icons/Share2.png
Normal file
|
بعد العرض: | الارتفاع: | الحجم: 543 B |
ثنائية
src/assets/icons/amazon.png
Normal file
|
بعد العرض: | الارتفاع: | الحجم: 658 B |
ثنائية
src/assets/icons/gemini-border.png
Normal file
|
بعد العرض: | الارتفاع: | الحجم: 943 B |
ثنائية
src/assets/icons/gemini.png
Normal file
|
بعد العرض: | الارتفاع: | الحجم: 553 B |
ثنائية
src/assets/icons/gemini1.png
Normal file
|
بعد العرض: | الارتفاع: | الحجم: 652 B |
ثنائية
src/assets/icons/iconheader/Info-badge.png
Normal file
|
بعد العرض: | الارتفاع: | الحجم: 458 B |
ثنائية
src/assets/icons/iconheader/active.png
Normal file
|
بعد العرض: | الارتفاع: | الحجم: 1.1 KiB |
ثنائية
src/assets/icons/iconheader/portfolio.png
Normal file
|
بعد العرض: | الارتفاع: | الحجم: 1.1 KiB |
ثنائية
src/assets/icons/iconheader/total.png
Normal file
|
بعد العرض: | الارتفاع: | الحجم: 1.3 KiB |
ثنائية
src/assets/icons/map.png
Normal file
|
بعد العرض: | الارتفاع: | الحجم: 604 B |
ثنائية
src/assets/icons/no.png
Normal file
|
بعد العرض: | الارتفاع: | الحجم: 636 B |
@@ -1,137 +1,137 @@
|
||||
|
||||
|
||||
const CampaignTracker = () => {
|
||||
const progressSections = [
|
||||
{
|
||||
title: "Social",
|
||||
current: 42,
|
||||
total: 80,
|
||||
color: "bg-blue-500",
|
||||
bgColor: "bg-blue-100",
|
||||
icons: [
|
||||
{ name: "Pinterest", color: "bg-red-600", text: "P", textColor: "text-white" },
|
||||
{ name: "Amazon", color: "bg-black", text: "a", textColor: "text-white" },
|
||||
{ name: "PayPal", color: "bg-blue-600", text: "P", textColor: "text-white" },
|
||||
{ name: "Yelp", color: "bg-red-600", text: "★", textColor: "text-white" },
|
||||
{ name: "Uber", color: "bg-black", text: "uber", textColor: "text-white", fontSize: "text-xs" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Citation",
|
||||
current: 30,
|
||||
total: 80,
|
||||
color: "bg-blue-500",
|
||||
bgColor: "bg-blue-100",
|
||||
icons: [
|
||||
{ name: "Service", color: "bg-blue-400", text: "◐", textColor: "text-white" },
|
||||
{ name: "Package", color: "bg-yellow-400", text: "📦", textColor: "text-white" },
|
||||
{ name: "Directory", color: "bg-blue-600", text: "◐", textColor: "text-white" },
|
||||
{ name: "Yelp", color: "bg-red-600", text: "★", textColor: "text-white" },
|
||||
{ name: "Pinterest", color: "bg-red-600", text: "P", textColor: "text-white" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Web 2.0",
|
||||
current: 15,
|
||||
total: 70,
|
||||
color: "bg-green-500",
|
||||
bgColor: "bg-green-100",
|
||||
icons: [
|
||||
{ name: "Directory", color: "bg-blue-600", text: "◐", textColor: "text-white" },
|
||||
{ name: "Fox", color: "bg-red-600", text: "FOX", textColor: "text-white", fontSize: "text-xs" },
|
||||
{ name: "Package", color: "bg-yellow-400", text: "📦", textColor: "text-white" },
|
||||
{ name: "Service", color: "bg-green-600", text: "≡", textColor: "text-white" },
|
||||
{ name: "Platform", color: "bg-red-600", text: "⬜", textColor: "text-white" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Multimedia",
|
||||
current: 5,
|
||||
total: 15,
|
||||
color: "bg-yellow-500",
|
||||
bgColor: "bg-yellow-100",
|
||||
icons: [
|
||||
{ name: "Package", color: "bg-yellow-400", text: "📦", textColor: "text-white" },
|
||||
{ name: "Service", color: "bg-green-600", text: "≡", textColor: "text-white" },
|
||||
{ name: "Directory", color: "bg-blue-600", text: "◐", textColor: "text-white" },
|
||||
{ name: "Uber", color: "bg-black", text: "uber", textColor: "text-white", fontSize: "text-xs" },
|
||||
{ name: "Amazon", color: "bg-black", text: "a", textColor: "text-white" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Crowd",
|
||||
current: 5,
|
||||
total: 15,
|
||||
color: "bg-red-500",
|
||||
bgColor: "bg-red-100",
|
||||
icons: [
|
||||
{ name: "Directory", color: "bg-blue-600", text: "◐", textColor: "text-white" },
|
||||
{ name: "PayPal", color: "bg-blue-600", text: "P", textColor: "text-white" },
|
||||
{ name: "Package", color: "bg-yellow-400", text: "📦", textColor: "text-white" },
|
||||
{ name: "Service", color: "bg-green-600", text: "≡", textColor: "text-white" },
|
||||
{ name: "Platform", color: "bg-red-600", text: "⬜", textColor: "text-white" },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto p-3 bg-white">
|
||||
|
||||
|
||||
{/* Status Cards */}
|
||||
<div className="grid grid-cols-3 gap-4 mb-8">
|
||||
<div className="bg-gray-50 rounded-lg p-1 border">
|
||||
<div className="text-sm text-gray-600 mb-1">Indexed:</div>
|
||||
<div className="text-sm font-bold text-gray-900">77%</div>
|
||||
</div>
|
||||
<div className="bg-gray-50 rounded-lg p-1 border">
|
||||
<div className="text-sm text-gray-600 mb-1">Interlinked:</div>
|
||||
<div className="text-sm font-bold text-gray-900">5%</div>
|
||||
</div>
|
||||
<div className="bg-gray-50 rounded-lg p-1 border">
|
||||
<div className="text-sm text-gray-600 mb-1">Publishing:</div>
|
||||
<div className="text-sm font-bold text-gray-900">Locked</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Progress Sections */}
|
||||
<div className="space-y-6">
|
||||
{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">
|
||||
<h3 className="text-sm font-semibold text-gray-900">
|
||||
{section.title}: {section.current}/{section.total}
|
||||
</h3>
|
||||
<div className="flex gap-2">
|
||||
{section.icons.map((icon, iconIndex) => (
|
||||
<div
|
||||
key={iconIndex}
|
||||
className={`w-6 h-6 rounded-lg ${icon.color} ${icon.textColor} flex items-center justify-center font-bold ${icon.fontSize || "text-sm"}`}
|
||||
title={icon.name}
|
||||
>
|
||||
{icon.text}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Progress Bar */}
|
||||
<div className={`w-full h-3 ${section.bgColor} rounded-full overflow-hidden`}>
|
||||
<div
|
||||
className={`h-full ${section.color} rounded-full transition-all duration-500 ease-out`}
|
||||
style={{ width: `${progressPercentage}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CampaignTracker
|
||||
|
||||
|
||||
const CampaignTracker = ({ handleMenuClick }: { handleMenuClick: () => void }) => {
|
||||
const progressSections = [
|
||||
{
|
||||
title: "Social",
|
||||
current: 42,
|
||||
total: 80,
|
||||
color: "bg-blue-500",
|
||||
bgColor: "bg-blue-100",
|
||||
icons: [
|
||||
{ name: "Pinterest", color: "bg-red-600", text: "P", textColor: "text-white" },
|
||||
{ name: "Amazon", color: "bg-black", text: "a", textColor: "text-white" },
|
||||
{ name: "PayPal", color: "bg-blue-600", text: "P", textColor: "text-white" },
|
||||
{ name: "Yelp", color: "bg-red-600", text: "★", textColor: "text-white" },
|
||||
{ name: "Uber", color: "bg-black", text: "uber", textColor: "text-white", fontSize: "text-xs" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Citation",
|
||||
current: 30,
|
||||
total: 80,
|
||||
color: "bg-blue-500",
|
||||
bgColor: "bg-blue-100",
|
||||
icons: [
|
||||
{ name: "Service", color: "bg-blue-400", text: "◐", textColor: "text-white" },
|
||||
{ name: "Package", color: "bg-yellow-400", text: "📦", textColor: "text-white" },
|
||||
{ name: "Directory", color: "bg-blue-600", text: "◐", textColor: "text-white" },
|
||||
{ name: "Yelp", color: "bg-red-600", text: "★", textColor: "text-white" },
|
||||
{ name: "Pinterest", color: "bg-red-600", text: "P", textColor: "text-white" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Web 2.0",
|
||||
current: 15,
|
||||
total: 70,
|
||||
color: "bg-green-500",
|
||||
bgColor: "bg-green-100",
|
||||
icons: [
|
||||
{ name: "Directory", color: "bg-blue-600", text: "◐", textColor: "text-white" },
|
||||
{ name: "Fox", color: "bg-red-600", text: "FOX", textColor: "text-white", fontSize: "text-xs" },
|
||||
{ name: "Package", color: "bg-yellow-400", text: "📦", textColor: "text-white" },
|
||||
{ name: "Service", color: "bg-green-600", text: "≡", textColor: "text-white" },
|
||||
{ name: "Platform", color: "bg-red-600", text: "⬜", textColor: "text-white" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Multimedia",
|
||||
current: 5,
|
||||
total: 15,
|
||||
color: "bg-yellow-500",
|
||||
bgColor: "bg-yellow-100",
|
||||
icons: [
|
||||
{ name: "Package", color: "bg-yellow-400", text: "📦", textColor: "text-white" },
|
||||
{ name: "Service", color: "bg-green-600", text: "≡", textColor: "text-white" },
|
||||
{ name: "Directory", color: "bg-blue-600", text: "◐", textColor: "text-white" },
|
||||
{ name: "Uber", color: "bg-black", text: "uber", textColor: "text-white", fontSize: "text-xs" },
|
||||
{ name: "Amazon", color: "bg-black", text: "a", textColor: "text-white" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Crowd",
|
||||
current: 5,
|
||||
total: 15,
|
||||
color: "bg-red-500",
|
||||
bgColor: "bg-red-100",
|
||||
icons: [
|
||||
{ name: "Directory", color: "bg-blue-600", text: "◐", textColor: "text-white" },
|
||||
{ name: "PayPal", color: "bg-blue-600", text: "P", textColor: "text-white" },
|
||||
{ name: "Package", color: "bg-yellow-400", text: "📦", textColor: "text-white" },
|
||||
{ name: "Service", color: "bg-green-600", text: "≡", textColor: "text-white" },
|
||||
{ name: "Platform", color: "bg-red-600", text: "⬜", textColor: "text-white" },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto p-3 bg-white">
|
||||
|
||||
|
||||
{/* Status Cards */}
|
||||
<div className="grid grid-cols-3 gap-4 mb-8">
|
||||
<div className="px-2 rounded-lg p-1 border">
|
||||
<div className=" text-gray-700 mb-1 ">Indexed:</div>
|
||||
<div className="text-sm font-bold text-gray-900">77%</div>
|
||||
</div>
|
||||
<div className="px-2 rounded-lg p-1 border">
|
||||
<div className="text- text-gray-700 mb-1 ">Interlinked:</div>
|
||||
<div className="text-sm font-bold text-gray-900">5%</div>
|
||||
</div>
|
||||
<div className="px-2 rounded-lg p-1 border">
|
||||
<div className="text- text-gray-700 mb-1 ">Publishing:</div>
|
||||
<div className="text-sm font-bold text-gray-900">Locked</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Progress Sections */}
|
||||
<div className="space-y-6">
|
||||
{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">
|
||||
<h3 className="text-sm font-semibold text-gray-900">
|
||||
{section.title}: {section.current}/{section.total}
|
||||
</h3>
|
||||
<div className="flex gap-2">
|
||||
{section.icons.map((icon, iconIndex) => (
|
||||
<div
|
||||
key={iconIndex}
|
||||
className={`w-6 h-6 rounded-lg ${icon.color} ${icon.textColor} flex items-center justify-center font-bold ${icon.fontSize || "text-sm"}`}
|
||||
title={icon.name}
|
||||
>
|
||||
{icon.text}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Progress Bar */}
|
||||
<div className={`w-full h-3 ${section.bgColor} rounded-full overflow-hidden cursor-pointer`} onClick={handleMenuClick} >
|
||||
<div
|
||||
className={`h-full ${section.color} rounded-full transition-all duration-500 ease-out`}
|
||||
style={{ width: `${progressPercentage}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CampaignTracker
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// DashboardGrid.tsx
|
||||
|
||||
import { useState } from "react";
|
||||
import React, { useState } from "react";
|
||||
import MultiRingCircularProgress from "./MultiRingCircularProgress";
|
||||
import CampaignTracker from "./CampaignTracker";
|
||||
import {
|
||||
@@ -14,79 +14,188 @@ import {
|
||||
import MetricCard from "./shared/MetricCard";
|
||||
import UrlDropdown from "./shared/UrlDropdown";
|
||||
import HeaderPage from "./shared/HeaderPage";
|
||||
import Google from "../assets/icons/Google.png";
|
||||
import Gemini from "../assets/icons/gemini.png";
|
||||
import Perplexity from "../assets/icons/preplextiy.png";
|
||||
import Vector from "../assets/icons/Vector.png";
|
||||
import Vector2 from "../assets/icons/Vector2.png";
|
||||
import GPT from "../assets/icons/gpt.png";
|
||||
import Map from "../assets/icons/map.png";
|
||||
|
||||
import TotalBacklinks from "../assets/icons/iconheader/total.png";
|
||||
import ActiveKeywords from "../assets/icons/iconheader/active.png";
|
||||
import PortfolioPerformance from "../assets/icons/iconheader/portfolio.png";
|
||||
import InfoBadge from "../assets/icons/iconheader/Info-badge.png";
|
||||
import Amazon from "../assets/icons/amazon.png";
|
||||
|
||||
interface TagContent {
|
||||
icon: string;
|
||||
text?: string;
|
||||
value?: string;
|
||||
extra?: string;
|
||||
}
|
||||
|
||||
interface Tag {
|
||||
content: TagContent[];
|
||||
color?: string;
|
||||
}
|
||||
|
||||
interface HoverState {
|
||||
cardId: string | null;
|
||||
ringIndex: number | null;
|
||||
}
|
||||
|
||||
const CARDS = [
|
||||
{
|
||||
title: "Richard Bower CPA",
|
||||
title: "CPA Hollywood",
|
||||
percent: 99,
|
||||
rings: [
|
||||
{ color: "#4285F4", value: 99 }, // blue
|
||||
{ color: "#34A853", value: 80 }, // green
|
||||
{ color: "#FBBC05", value: 60 }, // yellow
|
||||
{ color: "#EA4335", value: 40 } // red
|
||||
{ color: "#4c60e5", value: 99 }, // blue
|
||||
{ color: "#4285f4", value: 50 }, // blue
|
||||
{ color: "#34a853", value: 80 }, // green
|
||||
{ color: "#fbbc05", value: 90 }, // yellow
|
||||
{ color: "#ea4335", value: 40 } // red
|
||||
],
|
||||
tags: [
|
||||
{ text: "G 14", extra: "+1", color: "#4285F4" },
|
||||
{ text: "Ai" },
|
||||
{ text: "LLM" },
|
||||
{
|
||||
content: [
|
||||
{ icon: "Google", value: "14", extra: "+1" }
|
||||
],
|
||||
color: "#4285F4"
|
||||
},
|
||||
{
|
||||
content: [
|
||||
{ icon: "Map", value: "7", extra: "+2" }
|
||||
],
|
||||
color: "#4285F4"
|
||||
},
|
||||
{
|
||||
content: [
|
||||
{ icon: "Ai" },
|
||||
{ icon: "Perplexity" },
|
||||
{ icon: "GPT" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "CPA Los Angeles",
|
||||
percent: 24,
|
||||
rings: [
|
||||
{ color: "#4285F4", value: 24 },
|
||||
{ color: "#34A853", value: 18 },
|
||||
{ color: "#FBBC05", value: 14 },
|
||||
{ color: "#EA4335", value: 8 }
|
||||
{ color: "#4c60e5", value: 25 }, // blue
|
||||
{ color: "#4285f4", value: 21 }, // blue
|
||||
{ color: "#34a853", value: 40 }, // green
|
||||
{ color: "#fbbc05", value: 30 }, // yellow
|
||||
{ color: "#ea4335", value: 20 } // red
|
||||
],
|
||||
tags: [
|
||||
{ text: "G 14", extra: "-2", color: "#4285F4" }
|
||||
{
|
||||
content: [
|
||||
{ icon: "Google", value: "14", extra: "-2" }
|
||||
],
|
||||
color: "#4285F4"
|
||||
},
|
||||
{
|
||||
content: [
|
||||
{ icon: "Ai" },
|
||||
{ icon: "GPT" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "CPA Los Angeles4",
|
||||
percent: 45,
|
||||
rings: [
|
||||
{ color: "#4285F4", value: 45 },
|
||||
{ color: "#34A853", value: 42 },
|
||||
{ color: "#FBBC05", value: 32 },
|
||||
{ color: "#EA4335", value: 24 }
|
||||
{ color: "#4c60e5", value: 45 }, // blue
|
||||
{ color: "#4285f4", value: 42 }, // blue
|
||||
{ color: "#34a853", value: 32 }, // green
|
||||
{ color: "#fbbc05", value: 28 }, // yellow
|
||||
{ color: "#ea4335", value: 24 } // red
|
||||
],
|
||||
tags: [
|
||||
{ text: "G 14", extra: "-2", color: "#4285F4" },
|
||||
{ text: "+7" },
|
||||
{ text: "Ai" }
|
||||
{
|
||||
content: [
|
||||
{ icon: "Google", value: "14", extra: "-2" }
|
||||
],
|
||||
color: "#4285F4"
|
||||
},
|
||||
{
|
||||
content: [
|
||||
{ icon: "Map", value: "5", extra: "+1" }
|
||||
],
|
||||
color: "#4285F4"
|
||||
},
|
||||
{
|
||||
content: [
|
||||
{ icon: "Ai" },
|
||||
{ icon: "Perplexity" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Best CPA Los Angeles5",
|
||||
percent: 89,
|
||||
rings: [
|
||||
{ color: "#4285F4", value: 89 },
|
||||
{ color: "#34A853", value: 80 },
|
||||
{ color: "#FBBC05", value: 72 },
|
||||
{ color: "#EA4335", value: 57 }
|
||||
{ color: "#4c60e5", value: 89 }, // blue
|
||||
{ color: "#4285f4", value: 80 }, // blue
|
||||
{ color: "#34a853", value: 72 }, // green
|
||||
{ color: "#fbbc05", value: 65 }, // yellow
|
||||
{ color: "#ea4335", value: 57 } // red
|
||||
],
|
||||
tags: [
|
||||
{ text: "G 14", extra: "+1", color: "#4285F4" },
|
||||
{ text: "+7" },
|
||||
{ text: "Ai" }
|
||||
{
|
||||
content: [
|
||||
{ icon: "Google", value: "14", extra: "+1" }
|
||||
],
|
||||
color: "#4285F4"
|
||||
},
|
||||
{
|
||||
content: [
|
||||
{ icon: "Map", value: "9", extra: "+3" }
|
||||
],
|
||||
color: "#4285F4"
|
||||
},
|
||||
{
|
||||
content: [
|
||||
{ icon: "Ai" },
|
||||
{ icon: "GPT" },
|
||||
{ icon: "Perplexity" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "CPA Hollywood",
|
||||
percent: 100,
|
||||
title: "CPA Hollywood 2",
|
||||
percent: 98,
|
||||
rings: [
|
||||
{ color: "#4285F4", value: 100 },
|
||||
{ color: "#34A853", value: 100 },
|
||||
{ color: "#FBBC05", value: 100 },
|
||||
{ color: "#EA4335", value: 100 }
|
||||
{ color: "#4c60e5", value: 98 }, // blue
|
||||
{ color: "#4285f4", value: 98 }, // blue
|
||||
{ color: "#34a853", value: 98 }, // green
|
||||
{ color: "#fbbc05", value: 98 }, // yellow
|
||||
{ color: "#ea4335", value: 100 } // red
|
||||
],
|
||||
tags: [
|
||||
{ text: "G 14", extra: "+1", color: "#4285F4" },
|
||||
{ text: "+7" },
|
||||
{ text: "Ai" }
|
||||
{
|
||||
content: [
|
||||
{ icon: "Google", value: "14", extra: "+1" }
|
||||
],
|
||||
color: "#4285F4"
|
||||
},
|
||||
{
|
||||
content: [
|
||||
{ icon: "Map", value: "7", extra: "+2" }
|
||||
],
|
||||
color: "#4285F4"
|
||||
},
|
||||
{
|
||||
content: [
|
||||
{ icon: "Ai" },
|
||||
{ icon: "Perplexity" },
|
||||
{ icon: "GPT" }
|
||||
]
|
||||
}
|
||||
],
|
||||
badge: "Content Unlocked",
|
||||
actionLink: "View Published Content "
|
||||
@@ -95,15 +204,32 @@ const CARDS = [
|
||||
title: "Best CPA Los Angeles21",
|
||||
percent: 89,
|
||||
rings: [
|
||||
{ color: "#cccccc", value: 89 },
|
||||
{ color: "#cccccc", value: 80 },
|
||||
{ color: "#cccccc", value: 72 },
|
||||
{ color: "#cccccc", value: 57 }
|
||||
{ color: "#cccccc", value: 89 }, // gray
|
||||
{ color: "#cccccc", value: 80 }, // gray
|
||||
{ color: "#cccccc", value: 72 }, // gray
|
||||
{ color: "#cccccc", value: 65 }, // gray
|
||||
{ color: "#cccccc", value: 57 } // gray
|
||||
],
|
||||
tags: [
|
||||
{ text: "G 14", extra: "+1", color: "#cccccc" },
|
||||
{ text: "+7", color: "#cccccc" },
|
||||
{ text: "Ai", color: "#cccccc" }
|
||||
{
|
||||
content: [
|
||||
{ icon: "Google", value: "14", extra: "+1" }
|
||||
],
|
||||
color: "#cccccc"
|
||||
},
|
||||
{
|
||||
content: [
|
||||
{ icon: "Map", value: "7", extra: "+2" }
|
||||
],
|
||||
color: "#cccccc"
|
||||
},
|
||||
{
|
||||
content: [
|
||||
{ icon: "Ai" },
|
||||
{ icon: "Perplexity" },
|
||||
{ icon: "GPT" }
|
||||
]
|
||||
}
|
||||
],
|
||||
faded: true,
|
||||
paused: true
|
||||
@@ -116,33 +242,140 @@ const pillClass = (val: string) =>
|
||||
: 'bg-gray-100 text-gray-700 font-bold';
|
||||
|
||||
export default function DashboardGrid() {
|
||||
const [selectedCampaigns, setSelectedCampaigns] = useState<string[]>([])
|
||||
const [tableView, setTableView] = useState<string | null>(null)
|
||||
const [activeCard, setActiveCard] = useState<string | null>(null)
|
||||
const [selectedCampaigns, setSelectedCampaigns] = useState<string[]>([]);
|
||||
const [tableView, setTableView] = useState<string | null>(null);
|
||||
const [activeCard, setActiveCard] = useState<string | null>(null);
|
||||
const [hoverState, setHoverState] = useState<HoverState>({ cardId: null, ringIndex: null });
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
|
||||
const handleCardClick = (campaignName: string, paused: boolean) => {
|
||||
if (paused) return;
|
||||
setSelectedCampaigns([campaignName]);
|
||||
setActiveCard(campaignName);
|
||||
if (activeCard) {
|
||||
|
||||
if (!selectedCampaigns.includes(campaignName)) {
|
||||
setSelectedCampaigns(prev => [...prev, campaignName])
|
||||
}
|
||||
setActiveCard(campaignName)
|
||||
}
|
||||
};
|
||||
|
||||
const handleBackClick = () => {
|
||||
const handleBackClick = (e: React.MouseEvent) => {
|
||||
e.stopPropagation(); // Prevent event bubbling to card click
|
||||
if (tableView) {
|
||||
setTableView(null)
|
||||
setTableView(null);
|
||||
} else if (activeCard) {
|
||||
// Remove only the active card from selected campaigns
|
||||
setSelectedCampaigns(prev => prev.filter(card => card !== activeCard))
|
||||
setActiveCard(null)
|
||||
setSelectedCampaigns(prev => prev.filter(card => card !== activeCard));
|
||||
setActiveCard(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleMenuClick = (campaignName: string) => {
|
||||
setTableView(campaignName)
|
||||
}
|
||||
setTableView(campaignName);
|
||||
};
|
||||
|
||||
const handleRingHover = (cardId: string, ringIndex: number | null) => {
|
||||
setHoverState({ cardId, ringIndex });
|
||||
};
|
||||
|
||||
const TagComponent = ({ content, color = "#4285F4", faded = false }: Tag & { faded?: boolean }) => {
|
||||
const getIconContent = (item: TagContent) => {
|
||||
switch (item.icon) {
|
||||
case "Google":
|
||||
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.extra && (
|
||||
<span className={`ml-1 text-md ${item.extra.startsWith('+') ? 'text-green-600' : 'text-red-600'} font-bold`}>
|
||||
{item.extra}
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
case "Map":
|
||||
return (
|
||||
<>
|
||||
<img src={Map} alt="Map" 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 "Ai":
|
||||
return <img src={Gemini} alt="AI" className="w-5 h-5" />;
|
||||
case "GPT":
|
||||
return <img src={GPT} alt="GPT" className="w-5 h-5" />;
|
||||
case "Perplexity":
|
||||
return <img src={Perplexity} alt="Perplexity" className="w-5 h-5" />;
|
||||
default:
|
||||
return item.text || null;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<span
|
||||
className={`rounded-full border-2 border-gray-200 bg-white px-3 py-1 flex items-center gap-2 text-sm font-medium transition-all duration-300 hover:scale-105
|
||||
${faded ? "text-gray-200 border-gray-100" : "text-gray-400 border-gray-100 hover:border-blue-100"
|
||||
}`}
|
||||
style={color ? { color } : {}} // borderColor: color
|
||||
>
|
||||
{content.map((item, index) => (
|
||||
<div key={index} className="flex items-center gap-1 py-1">
|
||||
{getIconContent(item)}
|
||||
</div>
|
||||
))}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const CardHeader = ({ title, value, valueChange, icon, tooltipText }: { title: string, value: string, valueChange?: string, icon: string, tooltipText?: string }) => {
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2 border-r-2 border-gray-200 pr-4 last:border-r-0 w-[32%]"
|
||||
onMouseEnter={() => {
|
||||
if (tooltipText) {
|
||||
setIsHovered(true)
|
||||
}
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
if (tooltipText) {
|
||||
setIsHovered(false)
|
||||
}
|
||||
}}>
|
||||
<img src={icon} alt="Active Keywords" className="w-14 h-14" />
|
||||
|
||||
<div className="flex flex-col px-4">
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-2xl text-gray-500 font-bold">{value}</span>
|
||||
|
||||
{tooltipText && <div className="relative">
|
||||
{/* <Info size={16} className="text-gray-800 cursor-pointer" /> */}
|
||||
|
||||
<img src={InfoBadge} alt="Info" className="w-4 h-4 text-gray-800 cursor-pointer" />
|
||||
{isHovered && (
|
||||
<div className="absolute top-full left-1/2 -translate-x-1/2 mt-2 px-4 py-2 w-72 bg-gray-800 text-white text-sm rounded-lg shadow-lg z-50 transition-opacity duration-300 pointer-events-none opacity-100">
|
||||
{/* Tooltip triangle */}
|
||||
<div className="absolute bottom-full left-1/2 -translate-x-1/2 w-0 h-0 border-8 border-transparent border-b-gray-800"></div>
|
||||
{tooltipText}
|
||||
</div>
|
||||
)}
|
||||
</div>}
|
||||
</div>
|
||||
<div className="text-lg text-gray-500 ">{title}</div>
|
||||
<div className="text-lg text-gray-500 flex items-center gap-2">
|
||||
<span className="text-lg text-gray-500 font-bold">{valueChange}</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -150,28 +383,11 @@ export default function DashboardGrid() {
|
||||
|
||||
{/* Main content area with new metric cards */}
|
||||
<main className="pb-4 px-2 border-collapse border-2 mb-6 rounded-lg">
|
||||
<div className="flex flex-wrap gap-4 lg:flex-row flex-col">
|
||||
<MetricCard
|
||||
icon={FileText}
|
||||
value="5"
|
||||
label="Active Keywords"
|
||||
// tooltipText="Total Keywords"
|
||||
valueChange="10"
|
||||
/>
|
||||
<MetricCard
|
||||
icon={Link}
|
||||
value="112"
|
||||
label="Total Backlinks"
|
||||
// tooltipText="Total Backlinks"
|
||||
valueChange="10"
|
||||
/>
|
||||
<MetricCard
|
||||
icon={TrendingUp}
|
||||
value="34%"
|
||||
valueChange="+6%"
|
||||
label="Portfolio Performance"
|
||||
tooltipText="The % of keywords with top rankings, including page 1, map pack, and Ai."
|
||||
/>
|
||||
<div className="flex flex-wrap gap-4 lg:flex-row flex-col justify-between items-center py-4 px-4">
|
||||
<CardHeader title="Active Keywords" value="5" icon={ActiveKeywords} />
|
||||
<CardHeader title="Total Backlinks" value="112/1012" icon={TotalBacklinks} />
|
||||
<CardHeader title="Portfolio Performance" value="34%" icon={PortfolioPerformance} tooltipText="Portfolio Performance is the percentage of keywords that are performing well." />
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -179,141 +395,138 @@ export default function DashboardGrid() {
|
||||
{CARDS.map((card, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className={`bg-white rounded-2xl shadow-lg border transition-all duration-700 ease-in-out transform
|
||||
hover:shadow-2xl hover:scale-105 cursor-pointer
|
||||
${card.faded ? "opacity-60" : ""}
|
||||
${activeCard === card.title ? "cursor-default" : "cursor-pointer"}
|
||||
${selectedCampaigns.includes(card.title)
|
||||
? 'shadow-2xl scale-[1.02] '
|
||||
: 'hover:scale-105 hover:shadow-xl'
|
||||
}`}
|
||||
onClick={() => handleCardClick(card.title, card.paused)}
|
||||
style={{
|
||||
transform: selectedCampaigns.includes(card.title)
|
||||
? 'translateY(-5px) scale(1.02)'
|
||||
: 'translateY(0px) scale(1)'
|
||||
}}
|
||||
className="[perspective:1000px] h-[560px]"
|
||||
onClick={() => handleCardClick(card.title, card.paused || false)}
|
||||
>
|
||||
{selectedCampaigns.includes(card.title) && activeCard === card.title ? (
|
||||
<div className="py-3 flex flex-col">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between border-b border-gray-200 pb-2">
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
onClick={handleBackClick}
|
||||
<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'
|
||||
}`}
|
||||
style={{
|
||||
transform: selectedCampaigns.includes(card.title)
|
||||
? 'translateY(-5px) scale(1.02)'
|
||||
: 'translateY(0px) scale(1)'
|
||||
}}>
|
||||
<div className="p-5 flex flex-col items-center h-full">
|
||||
{card.badge && (
|
||||
<div className="absolute top-4 right-4 bg-green-500 text-white px-3 py-1 rounded-full text-xs font-bold transform rotate-12 shadow-lg">
|
||||
{card.badge}
|
||||
</div>
|
||||
)}
|
||||
<MultiRingCircularProgress
|
||||
card={card}
|
||||
hoveredIndex={hoverState.cardId === card.title ? hoverState.ringIndex : null}
|
||||
onHover={(index) => handleRingHover(card.title, index)}
|
||||
cardId={card.title}
|
||||
/>
|
||||
<div className={`mt-1 font-bold text-lg text-center ${card.faded ? "text-gray-400" : "text-gray-900"}`}>
|
||||
{card.title}
|
||||
</div>
|
||||
{/* Tags */}
|
||||
<div className="mt-3 flex flex-row gap-2 flex-wrap justify-center">
|
||||
{card.tags.map((tag, i) => (
|
||||
<TagComponent
|
||||
key={i}
|
||||
{...tag}
|
||||
faded={card.faded}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{card.actionLink && (
|
||||
<div className="mt-3 text-blue-600 text-sm cursor-pointer hover:underline transition-all duration-300 hover:text-blue-800">
|
||||
{card.actionLink}
|
||||
</div>
|
||||
)}
|
||||
{card.paused && (
|
||||
<div className="mt-3 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>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Back of card */}
|
||||
<div className="absolute w-full h-full [backface-visibility:hidden] [transform:rotateY(180deg)] bg-white rounded-2xl shadow-lg border transition-all duration-300 ease-in-out"
|
||||
style={{
|
||||
transform: selectedCampaigns.includes(card.title)
|
||||
? 'translateY(-5px) scale(1.02) rotateY(180deg)'
|
||||
: 'translateY(0px) scale(1) rotateY(180deg)'
|
||||
}}>
|
||||
<div className="py-3 flex flex-col h-full">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between border-b border-gray-200 pb-2 px-3">
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
onClick={handleBackClick}
|
||||
className="px-2 py-1 hover:bg-gray-100 rounded-lg transition-all duration-300 hover:scale-110 border-2 border-gray-200"
|
||||
>
|
||||
<ArrowLeft className="w-5 h-5 text-gray-600" />
|
||||
</button>
|
||||
<h1 className="text-xl font-bold text-gray-900">{card.title}</h1>
|
||||
</div>
|
||||
{/* <button
|
||||
onClick={() => handleMenuClick(card.title)}
|
||||
className="p-2 hover:bg-gray-100 rounded-lg transition-all duration-300 hover:scale-110"
|
||||
title="View table"
|
||||
>
|
||||
<ArrowLeft className="w-5 h-5 text-gray-600" />
|
||||
</button>
|
||||
<h1 className="text-xl font-bold text-gray-900">{card.title}</h1>
|
||||
<Menu className="w-5 h-5 text-gray-600" />
|
||||
</button> */}
|
||||
</div>
|
||||
<button
|
||||
onClick={() => handleMenuClick(card.title)}
|
||||
className="p-2 hover:bg-gray-100 rounded-lg transition-all duration-300 hover:scale-110"
|
||||
title="View table"
|
||||
|
||||
{/* Content with smooth transitions */}
|
||||
<div className={`transition-all duration-700 ease-in-out ${tableView === card.title
|
||||
? 'opacity-0 translate-y-4 pointer-events-none'
|
||||
: 'opacity-100 translate-y-0'
|
||||
}`}
|
||||
style={{ display: tableView !== card.title ? 'block' : 'none' }}>
|
||||
<CampaignTracker handleMenuClick={() => handleMenuClick(card.title)} />
|
||||
</div>
|
||||
|
||||
{/* Table View with smooth transitions */}
|
||||
<div className={`transition-all duration-700 ease-in-out ${tableView === card.title
|
||||
? 'opacity-100 translate-y-0'
|
||||
: 'opacity-0 translate-y-4 pointer-events-none'
|
||||
}`}
|
||||
style={{ display: tableView === card.title ? 'block' : 'none' }}
|
||||
>
|
||||
<Menu className="w-5 h-5 text-gray-600" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Content with smooth transitions */}
|
||||
<div className={`transition-all duration-700 ease-in-out ${tableView === card.title
|
||||
? 'opacity-0 translate-y-4 pointer-events-none'
|
||||
: 'opacity-100 translate-y-0'
|
||||
}`}
|
||||
style={{ display: tableView !== card.title ? 'block' : 'none' }}>
|
||||
<CampaignTracker />
|
||||
</div>
|
||||
|
||||
{/* Table View with smooth transitions */}
|
||||
<div className={`transition-all duration-700 ease-in-out ${tableView === card.title
|
||||
? 'opacity-100 translate-y-0'
|
||||
: 'opacity-0 translate-y-4 pointer-events-none'
|
||||
}`}
|
||||
style={{ display: tableView === card.title ? 'block' : 'none' }}
|
||||
>
|
||||
<div className="custom-scrollbar-table">
|
||||
<table className="w-full rounded-lg ">
|
||||
<tbody>
|
||||
{[
|
||||
{ icon: '🅰️', title: 'Questions to ask your CPA httpAmazon' },
|
||||
{ icon: '🅰️', title: 'Questions to ask your CPA Published on Amazon' },
|
||||
{ icon: '🎵', title: 'Questions to ask your CPA Published on Amazon AWS' },
|
||||
{ icon: '🅰️', title: 'Questions to ask your CPA httpAmazon' },
|
||||
{ icon: '🅰️', title: 'Questions to ask your CPA Published on Amazon' },
|
||||
{ icon: '🎵', title: 'Questions to ask your CPA Published on Amazon AWS' },
|
||||
{ icon: '🅰️', title: 'Questions to ask your CPA httpAmazon' },
|
||||
{ icon: '🅰️', title: 'Questions to ask your CPA Published on Amazon' },
|
||||
{ icon: '🎵', title: 'Questions to ask your CPA Published on Amazon AWS' },
|
||||
{ icon: '🅿️', title: 'Pinterest DR 97 DA 95 Traffic 3,989,220' },
|
||||
{ icon: '📄', title: 'Questions to ask your CPA Substack DR 97 DA 95' }
|
||||
].map((row, rIdx) => (
|
||||
<tr key={rIdx} className='border-b last:border-0 hover:bg-[#f1f7fe] transition-colors'>
|
||||
<td className='py-3 pl-2 text-center w-10'>{row.icon}</td>
|
||||
<td className='py-3 px-1 min-w-[300px] text-[#4285F4] hover:text-[#1e40af] cursor-pointer font-medium' title={row.title}>
|
||||
<div className='truncate max-w-[300px] text-sm underline'>{row.title}</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
|
||||
<div className="custom-scrollbar-table">
|
||||
<table className="w-full rounded-lg ">
|
||||
<tbody>
|
||||
{[
|
||||
{ icon: Amazon, title: 'Questions to ask your CPA httpAmazon' },
|
||||
{ icon: Amazon, title: 'Questions to ask your CPA Published on Amazon' },
|
||||
{ icon: Amazon, title: 'Questions to ask your CPA Published on Amazon AWS' },
|
||||
{ icon: Amazon, title: 'Questions to ask your CPA httpAmazon' },
|
||||
{ icon: Amazon, title: 'Questions to ask your CPA Published on Amazon' },
|
||||
{ icon: Amazon, title: 'Questions to ask your CPA Published on Amazon AWS' },
|
||||
{ icon: Amazon, title: 'Questions to ask your CPA httpAmazon' },
|
||||
{ icon: Amazon, title: 'Questions to ask your CPA Published on Amazon' },
|
||||
{ icon: Amazon, title: 'Questions to ask your CPA Published on Amazon AWS' },
|
||||
{ icon: Amazon, title: 'Pinterest DR 97 DA 95 Traffic 3,989,220' },
|
||||
{ icon: Amazon, title: 'Questions to ask your CPA Substack DR 97 DA 95' }
|
||||
].map((row, rIdx) => (
|
||||
<tr key={rIdx} className='border-b last:border-0 hover:bg-[#f1f7fe] transition-colors px-2 flex items-center'>
|
||||
<td className='py-3 pl-2 text-center '>
|
||||
<img src={row.icon} alt='' className="w-8 h-8 rounded-lg" />
|
||||
</td>
|
||||
<td className='py-3 px-1 min-w-[300px] text-[#4285F4] hover:text-[#1e40af] cursor-pointer font-medium' title={row.title}>
|
||||
<div className='truncate max-w-[340px] text-sm underline'>{row.title}</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div className="text-blue-600 text-sm cursor-pointer hover:underline transition-all duration-300 hover:text-blue-800 text-center pt-3" style={{ boxShadow: '#dadada 0px -6px 10px' }}>View All</div>
|
||||
</div>
|
||||
<div className="text-blue-600 text-sm cursor-pointer hover:underline transition-all duration-300 hover:text-blue-800 text-center pt-3" style={{ boxShadow: '#dadada 0px -6px 10px' }}>View All</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
) : (
|
||||
// Card preview state
|
||||
<div className="p-5 flex flex-col items-center">
|
||||
{card.badge && (
|
||||
<div className="absolute top-4 right-4 bg-green-500 text-white px-3 py-1 rounded-full text-xs font-bold transform rotate-12 shadow-lg">
|
||||
{card.badge}
|
||||
</div>
|
||||
)}
|
||||
<MultiRingCircularProgress card={card} />
|
||||
<div className={`mt-4 font-bold text-lg text-center ${card.faded ? "text-gray-400" : "text-gray-900"}`}>
|
||||
{card.title}
|
||||
</div>
|
||||
<div className="mt-3 flex flex-row gap-2 flex-wrap justify-center">
|
||||
{card.tags.map((tag, i) => (
|
||||
<span
|
||||
key={i}
|
||||
className={`rounded-full border-2 bg-white px-3 py-1 flex items-center text-sm font-medium transition-all duration-300 hover:scale-105 ${card.faded
|
||||
? "text-gray-400 border-gray-300"
|
||||
: "text-gray-700 border-gray-200 hover:border-blue-300"
|
||||
}`}
|
||||
style={tag.color ? { color: tag.color, borderColor: tag.color } : {}}
|
||||
>
|
||||
{tag.text}
|
||||
{tag.extra && (
|
||||
<span className={`ml-1 text-xs ${card.faded ? "text-gray-400" : "text-green-600 font-bold"
|
||||
}`}>
|
||||
{tag.extra}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
{card.actionLink && (
|
||||
<div className="mt-3 text-blue-600 text-sm cursor-pointer hover:underline transition-all duration-300 hover:text-blue-800">
|
||||
{card.actionLink}
|
||||
</div>
|
||||
)}
|
||||
{card.paused && (
|
||||
<div className="mt-3 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>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
@@ -323,4 +536,4 @@ export default function DashboardGrid() {
|
||||
// 106
|
||||
// 112
|
||||
// 118-121
|
||||
// 149
|
||||
// 149
|
||||
|
||||
@@ -1,377 +1,377 @@
|
||||
import './shared/scrollableComponent.css';
|
||||
|
||||
|
||||
interface TaskItem {
|
||||
id: string
|
||||
platform: string
|
||||
icon: string
|
||||
title: string
|
||||
metrics?: string
|
||||
isLink?: boolean
|
||||
}
|
||||
|
||||
interface Category {
|
||||
name: string
|
||||
count: number
|
||||
color: string
|
||||
items: TaskItem[]
|
||||
}
|
||||
|
||||
interface Column {
|
||||
title: string
|
||||
count: number
|
||||
date?: string
|
||||
categories: Category[]
|
||||
}
|
||||
|
||||
const IntegratedStrategy = () => {
|
||||
const columns: Column[] = [
|
||||
{
|
||||
title: "Built",
|
||||
count: 104,
|
||||
categories: [
|
||||
{
|
||||
name: "Social",
|
||||
count: 4,
|
||||
color: "bg-blue-100",
|
||||
items: [
|
||||
{
|
||||
id: "1",
|
||||
platform: "Pinterest",
|
||||
icon: "🅿️",
|
||||
title: "Pinterest DR 97 DA 95 Traffic 3,989,220",
|
||||
isLink: true,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
platform: "Substack",
|
||||
icon: "🟠",
|
||||
title: "Substack DR 97 DA 95",
|
||||
isLink: true,
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
platform: "PayPal",
|
||||
icon: "🅿️",
|
||||
title: "Paypal DR 97 DA 95 Traffic",
|
||||
isLink: true,
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
platform: "PayPal",
|
||||
icon: "🅿️",
|
||||
title: "Paypal DR 97 DA 95 Traffic",
|
||||
isLink: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Citation",
|
||||
count: 2,
|
||||
color: "bg-blue-100",
|
||||
items: [
|
||||
{
|
||||
id: "5",
|
||||
platform: "Pinterest",
|
||||
icon: "🅿️",
|
||||
title: "Pinterest DR 97 DA 95 Traffic 3,989,220",
|
||||
isLink: true,
|
||||
},
|
||||
{
|
||||
id: "6",
|
||||
platform: "Substack",
|
||||
icon: "🟠",
|
||||
title: "Substack DR 97 DA 95 Traffic",
|
||||
isLink: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Web 2.0",
|
||||
count: 3,
|
||||
color: "bg-green-100",
|
||||
items: [
|
||||
{
|
||||
id: "7",
|
||||
platform: "Apple Music",
|
||||
icon: "🎵",
|
||||
title: "Apple Music DR 97 DA 95 Traffic",
|
||||
isLink: true,
|
||||
},
|
||||
{
|
||||
id: "8",
|
||||
platform: "Pinterest",
|
||||
icon: "🅿️",
|
||||
title: "Pinterest DR 97 DA 95 Traffic 3,989,220",
|
||||
isLink: true,
|
||||
},
|
||||
{
|
||||
id: "9",
|
||||
platform: "Pinterest",
|
||||
icon: "⚫",
|
||||
title: "Pinterest DR 97 DA 95 Traffic 3,989,220",
|
||||
isLink: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Multimedia",
|
||||
count: 1,
|
||||
color: "bg-yellow-100",
|
||||
items: [
|
||||
{
|
||||
id: "10",
|
||||
platform: "Pinterest",
|
||||
icon: "⚫",
|
||||
title: "Pinterest DR 97 DA 95 Traffic 3,989,220",
|
||||
isLink: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "In Progress",
|
||||
count: 14,
|
||||
date: "23 Oct 2025",
|
||||
categories: [
|
||||
{
|
||||
name: "Social",
|
||||
count: 3,
|
||||
color: "bg-blue-100",
|
||||
items: [
|
||||
{
|
||||
id: "11",
|
||||
platform: "Pinterest",
|
||||
icon: "🅿️",
|
||||
title: "Pinterest DR 97 DA 95 Traffic 3,989,220",
|
||||
},
|
||||
{
|
||||
id: "12",
|
||||
platform: "Substack",
|
||||
icon: "🟠",
|
||||
title: "Substack DR 97 DA 95",
|
||||
},
|
||||
{
|
||||
id: "13",
|
||||
platform: "PayPal",
|
||||
icon: "🅿️",
|
||||
title: "Paypal DR 97 DA 95 Traffic",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Citation",
|
||||
count: 5,
|
||||
color: "bg-blue-100",
|
||||
items: [
|
||||
{
|
||||
id: "14",
|
||||
platform: "Pinterest",
|
||||
icon: "🅿️",
|
||||
title: "Pinterest DR 97 DA 95 Traffic 3,989,220",
|
||||
},
|
||||
{
|
||||
id: "15",
|
||||
platform: "Pinterest",
|
||||
icon: "🅿️",
|
||||
title: "Pinterest DR 97 DA 95 Traffic 3,989,220",
|
||||
},
|
||||
{
|
||||
id: "16",
|
||||
platform: "Substack",
|
||||
icon: "🟠",
|
||||
title: "Substack DR 97 DA 95 Traffic",
|
||||
},
|
||||
{
|
||||
id: "17",
|
||||
platform: "PayPal",
|
||||
icon: "🅿️",
|
||||
title: "Paypal DR 97 DA 95 Traffic 3,989,220",
|
||||
},
|
||||
{
|
||||
id: "18",
|
||||
platform: "Apple Music",
|
||||
icon: "🎵",
|
||||
title: "Apple Music DR 97 DA 95 Traffic",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Web 2.0",
|
||||
count: 5,
|
||||
color: "bg-green-100",
|
||||
items: [
|
||||
{
|
||||
id: "19",
|
||||
platform: "Pinterest",
|
||||
icon: "🅿️",
|
||||
title: "Pinterest DR 97 DA 95 Traffic 3,989,220",
|
||||
},
|
||||
{
|
||||
id: "20",
|
||||
platform: "Questions",
|
||||
icon: "⚫",
|
||||
title: "Questions to ask your CPA Published o...",
|
||||
},
|
||||
{
|
||||
id: "21",
|
||||
platform: "PayPal",
|
||||
icon: "🅿️",
|
||||
title: "Paypal DR 97 DA 95 Traffic",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Coming up",
|
||||
count: 10,
|
||||
date: "23 Nov 2025",
|
||||
categories: [
|
||||
{
|
||||
name: "Social",
|
||||
count: 5,
|
||||
color: "bg-blue-100",
|
||||
items: [
|
||||
{
|
||||
id: "22",
|
||||
platform: "Pinterest",
|
||||
icon: "🅿️",
|
||||
title: "Pinterest DR 97 DA 95 Traffic 3,989,220",
|
||||
},
|
||||
{
|
||||
id: "23",
|
||||
platform: "Substack",
|
||||
icon: "🟠",
|
||||
title: "Substack DR 97 DA 95",
|
||||
},
|
||||
{
|
||||
id: "24",
|
||||
platform: "PayPal",
|
||||
icon: "🅿️",
|
||||
title: "Paypal DR 97 DA 95 Traffic",
|
||||
},
|
||||
{
|
||||
id: "25",
|
||||
platform: "Pinterest",
|
||||
icon: "🅿️",
|
||||
title: "Pinterest DR 97 DA 95 Traffic 3,989,220",
|
||||
},
|
||||
{
|
||||
id: "26",
|
||||
platform: "PayPal",
|
||||
icon: "🅿️",
|
||||
title: "Paypal DR 97 DA 95 Traffic",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Citation",
|
||||
count: 5,
|
||||
color: "bg-blue-100",
|
||||
items: [
|
||||
{
|
||||
id: "27",
|
||||
platform: "Pinterest",
|
||||
icon: "🅿️",
|
||||
title: "Pinterest DR 97 DA 95 Traffic 3,989,220",
|
||||
},
|
||||
{
|
||||
id: "28",
|
||||
platform: "Pinterest",
|
||||
icon: "🅿️",
|
||||
title: "Pinterest DR 97 DA 95 Traffic 3,989,220",
|
||||
},
|
||||
{
|
||||
id: "29",
|
||||
platform: "Substack",
|
||||
icon: "🟠",
|
||||
title: "Substack DR 97 DA 95 Traffic",
|
||||
},
|
||||
{
|
||||
id: "30",
|
||||
platform: "PayPal",
|
||||
icon: "🅿️",
|
||||
title: "Paypal DR 97 DA 95 Traffic",
|
||||
},
|
||||
{
|
||||
id: "31",
|
||||
platform: "Apple Music",
|
||||
icon: "🎵",
|
||||
title: "Apple Music DR 97 DA 95 Traffic",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Web 2.0",
|
||||
count: 2,
|
||||
color: "bg-green-100",
|
||||
items: [
|
||||
{
|
||||
id: "32",
|
||||
platform: "Pinterest",
|
||||
icon: "🅿️",
|
||||
title: "Pinterest DR 97 DA 95 Traffic 3,989,220",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const CategorySection = ({ category }: { category: Category }) => (
|
||||
<div className="mb-4">
|
||||
<div className={`${category.color} px-3 py-2 border-l-4 border-blue-500`}>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-3 h-3 bg-blue-500 rounded-full"></div>
|
||||
<span className="font-medium text-gray-700">{category.name}</span>
|
||||
<span className="text-gray-600 text-sm">{category.count}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white border border-t-0 rounded-b-md">
|
||||
{category.items.map((item) => (
|
||||
<div key={item.id} className="px-3 py-2 border-b border-gray-100 last:border-b-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-lg mt-0.5">{item.icon}</span>
|
||||
<span
|
||||
className={`text-sm text-blue-600 hover:text-blue-800 ${item.isLink ? "underline cursor-pointer" : "text-gray-700"}`}
|
||||
>
|
||||
{item.title}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="p-2 bg-gray-50">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
{columns.map((column, index) => (
|
||||
<div key={index} className="bg-white rounded-lg shadow-sm border">
|
||||
<div className="border-b border-gray-200 p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<h2 className="text-lg font-semibold text-gray-900">{column.title}</h2>
|
||||
<span className="bg-gray-100 text-gray-600 px-2 py-1 rounded-full text-sm font-medium">
|
||||
{column.count}
|
||||
</span>
|
||||
</div>
|
||||
{column.date && <span className="text-sm text-gray-500">{column.date}</span>}
|
||||
</div>
|
||||
</div>
|
||||
<div className=' custom-scrollbar'>
|
||||
{column.categories.map((category, categoryIndex) => (
|
||||
<CategorySection key={categoryIndex} category={category} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default IntegratedStrategy
|
||||
import './shared/scrollableComponent.css';
|
||||
|
||||
|
||||
interface TaskItem {
|
||||
id: string
|
||||
platform: string
|
||||
icon: string
|
||||
title: string
|
||||
metrics?: string
|
||||
isLink?: boolean
|
||||
}
|
||||
|
||||
interface Category {
|
||||
name: string
|
||||
count: number
|
||||
color: string
|
||||
items: TaskItem[]
|
||||
}
|
||||
|
||||
interface Column {
|
||||
title: string
|
||||
count: number
|
||||
date?: string
|
||||
categories: Category[]
|
||||
}
|
||||
|
||||
const IntegratedStrategy = () => {
|
||||
const columns: Column[] = [
|
||||
{
|
||||
title: "Built",
|
||||
count: 104,
|
||||
categories: [
|
||||
{
|
||||
name: "Social",
|
||||
count: 4,
|
||||
color: "bg-blue-100",
|
||||
items: [
|
||||
{
|
||||
id: "1",
|
||||
platform: "Pinterest",
|
||||
icon: "🅿️",
|
||||
title: "Pinterest DR 97 DA 95 Traffic 3,989,220",
|
||||
isLink: true,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
platform: "Substack",
|
||||
icon: "🟠",
|
||||
title: "Substack DR 97 DA 95",
|
||||
isLink: true,
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
platform: "PayPal",
|
||||
icon: "🅿️",
|
||||
title: "Paypal DR 97 DA 95 Traffic",
|
||||
isLink: true,
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
platform: "PayPal",
|
||||
icon: "🅿️",
|
||||
title: "Paypal DR 97 DA 95 Traffic",
|
||||
isLink: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Citation",
|
||||
count: 2,
|
||||
color: "bg-blue-100",
|
||||
items: [
|
||||
{
|
||||
id: "5",
|
||||
platform: "Pinterest",
|
||||
icon: "🅿️",
|
||||
title: "Pinterest DR 97 DA 95 Traffic 3,989,220",
|
||||
isLink: true,
|
||||
},
|
||||
{
|
||||
id: "6",
|
||||
platform: "Substack",
|
||||
icon: "🟠",
|
||||
title: "Substack DR 97 DA 95 Traffic",
|
||||
isLink: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Web 2.0",
|
||||
count: 3,
|
||||
color: "bg-green-100",
|
||||
items: [
|
||||
{
|
||||
id: "7",
|
||||
platform: "Apple Music",
|
||||
icon: "🎵",
|
||||
title: "Apple Music DR 97 DA 95 Traffic",
|
||||
isLink: true,
|
||||
},
|
||||
{
|
||||
id: "8",
|
||||
platform: "Pinterest",
|
||||
icon: "🅿️",
|
||||
title: "Pinterest DR 97 DA 95 Traffic 3,989,220",
|
||||
isLink: true,
|
||||
},
|
||||
{
|
||||
id: "9",
|
||||
platform: "Pinterest",
|
||||
icon: "⚫",
|
||||
title: "Pinterest DR 97 DA 95 Traffic 3,989,220",
|
||||
isLink: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Multimedia",
|
||||
count: 1,
|
||||
color: "bg-yellow-100",
|
||||
items: [
|
||||
{
|
||||
id: "10",
|
||||
platform: "Pinterest",
|
||||
icon: "⚫",
|
||||
title: "Pinterest DR 97 DA 95 Traffic 3,989,220",
|
||||
isLink: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "In Progress",
|
||||
count: 14,
|
||||
date: "23 Oct 2025",
|
||||
categories: [
|
||||
{
|
||||
name: "Social",
|
||||
count: 3,
|
||||
color: "bg-blue-100",
|
||||
items: [
|
||||
{
|
||||
id: "11",
|
||||
platform: "Pinterest",
|
||||
icon: "🅿️",
|
||||
title: "Pinterest DR 97 DA 95 Traffic 3,989,220",
|
||||
},
|
||||
{
|
||||
id: "12",
|
||||
platform: "Substack",
|
||||
icon: "🟠",
|
||||
title: "Substack DR 97 DA 95",
|
||||
},
|
||||
{
|
||||
id: "13",
|
||||
platform: "PayPal",
|
||||
icon: "🅿️",
|
||||
title: "Paypal DR 97 DA 95 Traffic",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Citation",
|
||||
count: 5,
|
||||
color: "bg-blue-100",
|
||||
items: [
|
||||
{
|
||||
id: "14",
|
||||
platform: "Pinterest",
|
||||
icon: "🅿️",
|
||||
title: "Pinterest DR 97 DA 95 Traffic 3,989,220",
|
||||
},
|
||||
{
|
||||
id: "15",
|
||||
platform: "Pinterest",
|
||||
icon: "🅿️",
|
||||
title: "Pinterest DR 97 DA 95 Traffic 3,989,220",
|
||||
},
|
||||
{
|
||||
id: "16",
|
||||
platform: "Substack",
|
||||
icon: "🟠",
|
||||
title: "Substack DR 97 DA 95 Traffic",
|
||||
},
|
||||
{
|
||||
id: "17",
|
||||
platform: "PayPal",
|
||||
icon: "🅿️",
|
||||
title: "Paypal DR 97 DA 95 Traffic 3,989,220",
|
||||
},
|
||||
{
|
||||
id: "18",
|
||||
platform: "Apple Music",
|
||||
icon: "🎵",
|
||||
title: "Apple Music DR 97 DA 95 Traffic",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Web 2.0",
|
||||
count: 5,
|
||||
color: "bg-green-100",
|
||||
items: [
|
||||
{
|
||||
id: "19",
|
||||
platform: "Pinterest",
|
||||
icon: "🅿️",
|
||||
title: "Pinterest DR 97 DA 95 Traffic 3,989,220",
|
||||
},
|
||||
{
|
||||
id: "20",
|
||||
platform: "Questions",
|
||||
icon: "⚫",
|
||||
title: "Questions to ask your CPA Published o...",
|
||||
},
|
||||
{
|
||||
id: "21",
|
||||
platform: "PayPal",
|
||||
icon: "🅿️",
|
||||
title: "Paypal DR 97 DA 95 Traffic",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Coming up",
|
||||
count: 10,
|
||||
date: "23 Nov 2025",
|
||||
categories: [
|
||||
{
|
||||
name: "Social",
|
||||
count: 5,
|
||||
color: "bg-blue-100",
|
||||
items: [
|
||||
{
|
||||
id: "22",
|
||||
platform: "Pinterest",
|
||||
icon: "🅿️",
|
||||
title: "Pinterest DR 97 DA 95 Traffic 3,989,220",
|
||||
},
|
||||
{
|
||||
id: "23",
|
||||
platform: "Substack",
|
||||
icon: "🟠",
|
||||
title: "Substack DR 97 DA 95",
|
||||
},
|
||||
{
|
||||
id: "24",
|
||||
platform: "PayPal",
|
||||
icon: "🅿️",
|
||||
title: "Paypal DR 97 DA 95 Traffic",
|
||||
},
|
||||
{
|
||||
id: "25",
|
||||
platform: "Pinterest",
|
||||
icon: "🅿️",
|
||||
title: "Pinterest DR 97 DA 95 Traffic 3,989,220",
|
||||
},
|
||||
{
|
||||
id: "26",
|
||||
platform: "PayPal",
|
||||
icon: "🅿️",
|
||||
title: "Paypal DR 97 DA 95 Traffic",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Citation",
|
||||
count: 5,
|
||||
color: "bg-blue-100",
|
||||
items: [
|
||||
{
|
||||
id: "27",
|
||||
platform: "Pinterest",
|
||||
icon: "🅿️",
|
||||
title: "Pinterest DR 97 DA 95 Traffic 3,989,220",
|
||||
},
|
||||
{
|
||||
id: "28",
|
||||
platform: "Pinterest",
|
||||
icon: "🅿️",
|
||||
title: "Pinterest DR 97 DA 95 Traffic 3,989,220",
|
||||
},
|
||||
{
|
||||
id: "29",
|
||||
platform: "Substack",
|
||||
icon: "🟠",
|
||||
title: "Substack DR 97 DA 95 Traffic",
|
||||
},
|
||||
{
|
||||
id: "30",
|
||||
platform: "PayPal",
|
||||
icon: "🅿️",
|
||||
title: "Paypal DR 97 DA 95 Traffic",
|
||||
},
|
||||
{
|
||||
id: "31",
|
||||
platform: "Apple Music",
|
||||
icon: "🎵",
|
||||
title: "Apple Music DR 97 DA 95 Traffic",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Web 2.0",
|
||||
count: 2,
|
||||
color: "bg-green-100",
|
||||
items: [
|
||||
{
|
||||
id: "32",
|
||||
platform: "Pinterest",
|
||||
icon: "🅿️",
|
||||
title: "Pinterest DR 97 DA 95 Traffic 3,989,220",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const CategorySection = ({ category }: { category: Category }) => (
|
||||
<div className="mb-4">
|
||||
<div className={`${category.color} px-3 py-2 border-l-4 border-blue-500`}>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-5 bg-blue-500 rounded"></div>
|
||||
<span className="font-medium text-lg text-[#2B2D3B]">{category.name}</span>
|
||||
<span className="text-[#2B2D3B] text-lg font-bold bg-white py-[1px] px-[10px] rounded-full">{category.count}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white border border-t-0 rounded-b-md">
|
||||
{category.items.map((item) => (
|
||||
<div key={item.id} className="px-3 py-2 border-b border-gray-100 last:border-b-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xl mt-0.5">{item.icon}</span>
|
||||
<span
|
||||
className={`text-md font-semibold text-blue-600 hover:text-blue-800 ${item.isLink ? "underline cursor-pointer" : "text-gray-700"}`}
|
||||
>
|
||||
{item.title}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="p-2 bg-gray-50">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
{columns.map((column, index) => (
|
||||
<div key={index} className="bg-white rounded-lg shadow-sm border">
|
||||
<div className="border-b border-gray-200 bg-[#f6f6fa] p-4 shadow-lg ">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<h2 className="text-lg font-bold text-[#2B2D3B]">{column.title}</h2>
|
||||
<span className="text-[#2B2D3B] bg-[#e9eaf1] px-2 py-1 rounded-full text-md font-semibold">
|
||||
{column.count}
|
||||
</span>
|
||||
</div>
|
||||
{column.date && <span className="text-sm text-gray-500">{column.date}</span>}
|
||||
</div>
|
||||
</div>
|
||||
<div className=' custom-scrollbar'>
|
||||
{column.categories.map((category, categoryIndex) => (
|
||||
<CategorySection key={categoryIndex} category={category} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default IntegratedStrategy
|
||||
|
||||
@@ -13,20 +13,33 @@ interface Card {
|
||||
|
||||
interface MultiRingCircularProgressProps {
|
||||
card: Card;
|
||||
hoveredIndex: number | null;
|
||||
onHover: (index: number | null) => void;
|
||||
cardId: string;
|
||||
}
|
||||
|
||||
export default function MultiRingCircularProgress({ card }: MultiRingCircularProgressProps) {
|
||||
export default function MultiRingCircularProgress({ card, hoveredIndex, onHover, cardId }: MultiRingCircularProgressProps) {
|
||||
const { percent, rings } = card;
|
||||
|
||||
|
||||
// Create ring data with proper outer and inner radius calculations
|
||||
const ringData = rings.map((ring, index) => ({
|
||||
...ring,
|
||||
outerRadius: 120 - (index * 18), // Decrease by 18px for each ring
|
||||
innerRadius: 105 - (index * 18), // Decrease by 18px for each ring
|
||||
outerRadius: 125 - (index * 20), // Decrease by 20px for each ring
|
||||
innerRadius: 110 - (index * 20), // Decrease by 20px for each ring
|
||||
}));
|
||||
|
||||
// When a ring is hovered, other rings become gray
|
||||
const getHoverColor = (ring: Ring, index: number) => {
|
||||
if (hoveredIndex === null) return ring.color;
|
||||
if (hoveredIndex === index) return ring.color;
|
||||
return "#d3d3d3"; // gray for non-hovered rings
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-center pointer-events-none hover:scale-115 transition-all duration-300">
|
||||
<div
|
||||
className="flex items-center justify-center hover:scale-115 transition-all duration-300 cursor-pointer"
|
||||
onMouseLeave={() => onHover(null)}
|
||||
>
|
||||
<PieChart width={400} height={400}>
|
||||
{ringData.map((ring, index) => (
|
||||
<Pie
|
||||
@@ -41,9 +54,15 @@ export default function MultiRingCircularProgress({ card }: MultiRingCircularPro
|
||||
innerRadius={ring.innerRadius}
|
||||
outerRadius={ring.outerRadius}
|
||||
paddingAngle={0}
|
||||
isAnimationActive={false}
|
||||
isAnimationActive={true}
|
||||
scale={1.5}
|
||||
onMouseEnter={() => onHover(index)}
|
||||
onMouseLeave={() => onHover(null)}
|
||||
fill={getHoverColor(ring, index)}
|
||||
>
|
||||
<Cell fill={ring.color} />
|
||||
<Cell
|
||||
fill={getHoverColor(ring, index)}
|
||||
/>
|
||||
<Cell fill="#f2f3f5" />
|
||||
</Pie>
|
||||
))}
|
||||
|
||||
@@ -1,47 +1,61 @@
|
||||
import React, { useState } from 'react'
|
||||
|
||||
const BacklinkTypeDropdown = () => {
|
||||
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||
const [checkedRings, setCheckedRings] = useState<Record<string, boolean>>({
|
||||
Social: true, Citation: true, 'Web 2.0': true, Multimedia: false, Crowd: false
|
||||
});
|
||||
|
||||
const toggleRing = (ring: string) => setCheckedRings(r => ({ ...r, [ring]: !r[ring] }));
|
||||
|
||||
return (
|
||||
<div className='relative'>
|
||||
<button
|
||||
className='border rounded-lg px-3 py-1 bg-white text-gray-700 font-medium flex items-center text-xs'
|
||||
onClick={() => setDropdownOpen(v => !v)}
|
||||
>
|
||||
Backlink Type <svg className='ml-2 w-4 h-4' fill='none' stroke='currentColor' viewBox='0 0 24 24'>
|
||||
<path strokeLinecap='round' strokeLinejoin='round' strokeWidth={2} d='M19 9l-7 7-7-7' />
|
||||
</svg>
|
||||
</button>
|
||||
{dropdownOpen && (
|
||||
<div className='absolute z-10 bg-white shadow-lg border rounded-lg top-[110%] right-0 px-4 py-3 min-w-[190px]'>
|
||||
<div className='font-semibold text-xs text-gray-700 mb-2'>Show rings:</div>
|
||||
{Object.keys(checkedRings).map((ring, idx) => (
|
||||
<label
|
||||
key={idx}
|
||||
className='flex items-center gap-2 py-1 text-[14px] cursor-pointer select-none'
|
||||
style={{ fontWeight: 500 }}
|
||||
>
|
||||
<input
|
||||
type='checkbox'
|
||||
checked={checkedRings[ring]}
|
||||
className='form-checkbox accent-blue-600 w-4 h-4'
|
||||
onChange={() => toggleRing(ring)}
|
||||
/>
|
||||
{ring}
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
import { ChevronDown } from 'lucide-react';
|
||||
import React, { useState, useEffect, useRef } from 'react'
|
||||
|
||||
const BacklinkTypeDropdown = ({ options, title, subTitle }: { options: Record<string, boolean>, title: string, subTitle?: string }) => {
|
||||
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||
const [checkedRings, setCheckedRings] = useState<Record<string, boolean>>(options);
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const toggleRing = (ring: string) => setCheckedRings(r => ({ ...r, [ring]: !r[ring] }));
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
|
||||
setDropdownOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (dropdownOpen) {
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
}, [dropdownOpen]);
|
||||
|
||||
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'
|
||||
onClick={() => setDropdownOpen(v => !v)}
|
||||
>
|
||||
{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='font-semibold text-md text-gray-700 mb-2'>{subTitle}:</div>
|
||||
{Object.keys(checkedRings).map((ring, idx) => (
|
||||
<label
|
||||
key={idx}
|
||||
className='flex items-center gap-2 py-1 text-[16px] cursor-pointer select-none'
|
||||
style={{ fontWeight: 500 }}
|
||||
>
|
||||
<input
|
||||
type='checkbox'
|
||||
checked={checkedRings[ring]}
|
||||
className='form-checkbox accent-blue-600 w-4 h-4 rounded-full'
|
||||
onChange={() => toggleRing(ring)}
|
||||
/>
|
||||
{ring}
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
export default BacklinkTypeDropdown;
|
||||
20
src/components/ProtectedRoute.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Navigate } from 'react-router-dom';
|
||||
|
||||
interface ProtectedRouteProps {
|
||||
children: JSX.Element;
|
||||
}
|
||||
|
||||
const ProtectedRoute = ({ children }: ProtectedRouteProps) => {
|
||||
// Check if user is logged in (from localStorage or context)
|
||||
const user = localStorage.getItem('sp_user');
|
||||
|
||||
if (!user) {
|
||||
// If not logged in, redirect to login page
|
||||
return <Navigate to="/login" replace />;
|
||||
}
|
||||
|
||||
// Else render the protected component
|
||||
return children;
|
||||
};
|
||||
|
||||
export default ProtectedRoute;
|
||||
@@ -1,93 +1,122 @@
|
||||
import Preplextiy from "../../assets/icons/Preplextiy.png"
|
||||
import ChatGPT from "../../assets/icons/gpt.png"
|
||||
import MetricCard from "../shared/MetricCard"
|
||||
|
||||
|
||||
function ProgressMetrics() {
|
||||
return (
|
||||
<div className="bg-white rounded-lg border border-gray-200 p-3 mb-6">
|
||||
<h3 className="text-lg font-bold text-gray-600 mb-2">CPA Los Angeles</h3>
|
||||
|
||||
<div className="flex items-center justify-between border-2 border-solid border-gray-100 rounded-lg p-4">
|
||||
{/* Left section with progress indicators */}
|
||||
<div className="flex items-center w-1/4 ">
|
||||
{/* 60% Progress */}
|
||||
<div className="w-[100%]">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-4xl font-bold text-gray-900 mb-1">60%</div>
|
||||
<div className="text-md font-bold text-gray-900 mb-1">120/250 <span className="text-gray-500">links</span></div>
|
||||
</div>
|
||||
<div className="text-sm text-gray-500 mb-2">until content publishing + LLM seeding unlocked</div>
|
||||
{/* Progress bar */}
|
||||
<div className="w-70 h-2 bg-gray-200 rounded-full overflow-hidden">
|
||||
<div
|
||||
className="h-full bg-gradient-to-r from-blue-500 via-green-500 via-yellow-500 to-orange-500 rounded-full"
|
||||
style={{ width: "60%" }}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div className="flex items-center w-1/3 2xl:gap-32 gap-3">
|
||||
{/* 75% Keyword Visibility */}
|
||||
<div className="w-[30%]">
|
||||
<div className="text-4xl font-bold text-gray-900 mb-1">75%</div>
|
||||
<div className="text-sm text-gray-500">Keyword Visibility</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3 w-[100%]">
|
||||
<div className="flex items-center gap-8">
|
||||
<MetricCard
|
||||
img={ChatGPT}
|
||||
value="10" label="ChatGPT" tooltipText="ChatGPT is a chatbot that can answer questions and help with tasks." valueChange="+1" size="sm" />
|
||||
{/* <img src={ChatGPT} alt="ChatGPT" className="w-8 h-8" /> */}
|
||||
|
||||
<MetricCard
|
||||
img={Preplextiy}
|
||||
value="10" label="Preplextiy" tooltipText="Preplextiy is a chatbot that can answer questions and help with tasks." valueChange="+1" size="sm" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right section with Chat GPT and metrics */}
|
||||
<div className="flex items-center w-1/3">
|
||||
|
||||
{/* Performance metrics with chart */}
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-42 h-24 relative">
|
||||
<svg viewBox="0 0 96 48" className="w-full h-full">
|
||||
<defs>
|
||||
<linearGradient id="chartGradient" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" stopColor="#10B981" stopOpacity="0.3" />
|
||||
<stop offset="100%" stopColor="#10B981" stopOpacity="0.1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path d="M0,40 Q24,35 48,25 Q72,20 96,15" fill="none" stroke="#10B981" strokeWidth="2" />
|
||||
<path d="M0,40 Q24,35 48,25 Q72,20 96,15 L96,48 L0,48 Z" fill="url(#chartGradient)" />
|
||||
<circle cx="96" cy="15" r="2" fill="#10B981" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="flex items-center justify-end gap-1 mb-1">
|
||||
<span className="text-gray-400">G</span>
|
||||
<span className="text-lg font-bold text-gray-900">7</span>
|
||||
<span className="text-lg text-green-600 font-medium">+10</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-end gap-1 mb-1">
|
||||
<span className="text-orange-500">📍</span>
|
||||
<span className="text-lg font-bold text-gray-900">5</span>
|
||||
<span className="text-lg text-green-600 font-medium">+3</span>
|
||||
</div>
|
||||
<div className="text-xs text-gray-500">Current Rank</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* <ShowDetails /> */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
import MetricCard from "../shared/MetricCard"
|
||||
import Progress from "../../assets/icons/InProgress.png"
|
||||
import No from "../../assets/icons/no.png"
|
||||
import { CircleCheck } from 'lucide-react'
|
||||
import { Stepper } from "../shared/Stepper"
|
||||
import Preplextiy from "../../assets/icons/preplextiy.png"
|
||||
import ChatGPT from "../../assets/icons/gpt.png"
|
||||
import Gemini from "../../assets/icons/gemini.png"
|
||||
import Gemini1 from "../../assets/icons/gemini1.png"
|
||||
import Gemini2 from "../../assets/icons/gemini-border.png"
|
||||
import Google from "../../assets/icons/Google.png"
|
||||
|
||||
|
||||
function ProgressMetrics() {
|
||||
|
||||
return (
|
||||
<div className="bg-white rounded-lg border border-gray-200 p-3 mb-6">
|
||||
|
||||
<div className="flex items-center justify-between pb-3 px-2">
|
||||
<h3 className="text-lg font-bold text-gray-600">CPA Los Angeles</h3>
|
||||
{/* Example 2: With Completed Status */}
|
||||
<div>
|
||||
<Stepper
|
||||
steps={[
|
||||
{ number: 1, label: "Building Backlinks", status: "completed" },
|
||||
{ number: 2, label: "Indexing", status: "active" },
|
||||
{ number: 3, label: "LLM Seeding", status: "inactive" },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between border-2 border-solid border-gray-100 rounded-lg p-4 gap-8">
|
||||
{/* Left section with progress indicators */}
|
||||
<div className="flex items-center w-1/3 border-r-2 border-solid border-gray-300 pr-10">
|
||||
{/* 60% Progress */}
|
||||
<div className="w-[100%]">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-4xl font-bold text-gray-900 mb-1">60%</div>
|
||||
<div className="text-3xl font-bold text-gray-900 mb-1">120/250 <span className="text-gray-500">links</span></div>
|
||||
</div>
|
||||
<div className="text-sm text-gray-500 mb-2">until content publishing + LLM seeding unlocked</div>
|
||||
{/* Progress bar */}
|
||||
<div className="w-70 h-3 bg-gray-200 rounded-full overflow-hidden">
|
||||
<div
|
||||
className="h-full bg-gradient-to-r from-blue-500 via-green-500 via-yellow-500 to-orange-500 rounded-full"
|
||||
style={{ width: "60%" }}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div className="flex items-center w-1/3 border-r-2 border-solid border-gray-300 2xl:gap-32 gap-3">
|
||||
{/* 75% Keyword Visibility */}
|
||||
<div className="w-[30%]">
|
||||
<div className="text-4xl font-bold text-gray-900 mb-1">75%</div>
|
||||
<div className="text-sm text-gray-500">Keyword Visibility</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3 w-[100%]">
|
||||
<div className="flex items-center gap-3">
|
||||
<MetricCard
|
||||
img={ChatGPT} tooltipText="ChatGPT" valueChange="+1" size="sm" />
|
||||
|
||||
<MetricCard
|
||||
img={Preplextiy}
|
||||
tooltipText="Preplextiy" valueChange="+1" size="sm" />
|
||||
|
||||
<MetricCard
|
||||
img={ChatGPT} tooltipText="ChatGPT" valueChange="+1" size="sm" />
|
||||
|
||||
<MetricCard
|
||||
img={Gemini} tooltipText="Gemini" valueChange="+1" size="sm" extraStyle="border-2 border-solid border-gray-300 rounded-lg " />
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right section with Chat GPT and metrics */}
|
||||
<div className="flex items-center w-1/3">
|
||||
|
||||
{/* Performance metrics with chart */}
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-42 h-24 relative">
|
||||
<svg viewBox="0 0 96 48" className="w-full h-full">
|
||||
<defs>
|
||||
<linearGradient id="chartGradient" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" stopColor="#10B981" stopOpacity="0.3" />
|
||||
<stop offset="100%" stopColor="#10B981" stopOpacity="0.1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path d="M0,40 Q24,35 48,25 Q72,20 96,15" fill="none" stroke="#10B981" strokeWidth="2" />
|
||||
<path d="M0,40 Q24,35 48,25 Q72,20 96,15 L96,48 L0,48 Z" fill="url(#chartGradient)" />
|
||||
<circle cx="96" cy="15" r="2" fill="#10B981" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="flex items-center justify-start gap-1 mb-1">
|
||||
<img src={Google} className="w-5 h-5 mr-2" alt="" />
|
||||
<span className="text-lg font-bold text-gray-900">7</span>
|
||||
<span className="text-lg text-green-600 font-medium">+10</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-start gap-1 mb-1">
|
||||
<img src={Google} className="w-5 h-5 mr-2" alt="" />
|
||||
<span className="text-lg font-bold text-gray-900">5</span>
|
||||
<span className="text-lg text-green-600 font-medium">+3</span>
|
||||
</div>
|
||||
<div className="text-md text-gray-500">Current Rank</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* <ShowDetails /> */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ProgressMetrics
|
||||
@@ -1,70 +1,70 @@
|
||||
import Vector from "../../assets/icons/Vector.png"
|
||||
import Vector2 from "../../assets/icons/Vector2.png"
|
||||
|
||||
// Roadmap sidebar component
|
||||
const RoadmapSidebar = ({
|
||||
selectedKeyword,
|
||||
onKeywordSelect,
|
||||
mode
|
||||
}: {
|
||||
selectedKeyword: string
|
||||
onKeywordSelect: (keyword: string) => void
|
||||
mode: string
|
||||
}) => {
|
||||
const keywords = [
|
||||
"CPA Los Angeles",
|
||||
"Richard Bower CPA",
|
||||
"CPA Los Angeles 2",
|
||||
"CPA Hollywood",
|
||||
"Best CPA Los Angeles",
|
||||
"CPA Los Angeles 4",
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="w-80 h-[100%] bg-white border-2 border-gray-200 rounded-lg">
|
||||
{mode ?
|
||||
null
|
||||
:
|
||||
<div className="mb-2 border-b-2 border-gray-200 p-6">
|
||||
<h2 className="text-xl font-bold text-gray-900 mb-2">Roadmap</h2>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-3xl font-bold text-green-600">1001</span>
|
||||
<div>
|
||||
<div className="font-semibold text-gray-900">Total Links</div>
|
||||
<div className="text-sm text-gray-500">Across all keywords</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
<div className="space-y-2 pt-0">
|
||||
{keywords.map((keyword, index) => (
|
||||
<button
|
||||
key={index}
|
||||
onClick={() => onKeywordSelect(keyword)}
|
||||
className={`w-full text-left p-3 rounded-lg flex items-center gap-3 transition-colors ${selectedKeyword === keyword ? "bg-blue-50 border-2 border-dashed border-blue-300" : "hover:bg-gray-50"}`}
|
||||
>
|
||||
{
|
||||
selectedKeyword === keyword
|
||||
?
|
||||
<>
|
||||
<img src={Vector} alt={keyword} className="w-4 h-4" />
|
||||
<span className="text-md text-blue-600 font-bold">{keyword}</span>
|
||||
</>
|
||||
:
|
||||
<>
|
||||
<img src={Vector2} alt={keyword} className="w-4 h-4" />
|
||||
<span className="text-md text-gray-700 font-bold">{keyword}</span>
|
||||
</>
|
||||
|
||||
}
|
||||
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
import Vector from "../../assets/icons/Vector.png"
|
||||
import Vector2 from "../../assets/icons/Vector2.png"
|
||||
|
||||
// Roadmap sidebar component
|
||||
const RoadmapSidebar = ({
|
||||
selectedKeyword,
|
||||
onKeywordSelect,
|
||||
mode
|
||||
}: {
|
||||
selectedKeyword: string
|
||||
onKeywordSelect: (keyword: string) => void
|
||||
mode: string
|
||||
}) => {
|
||||
const keywords = [
|
||||
"CPA Los Angeles",
|
||||
"Richard Bower CPA",
|
||||
"CPA Los Angeles 2",
|
||||
"CPA Hollywood",
|
||||
"Best CPA Los Angeles",
|
||||
"CPA Los Angeles 4",
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="lg:w-80 w-[96%] mb-3 lg:mb-0 h-[100%] bg-white border-2 border-gray-200 rounded-lg">
|
||||
{mode ?
|
||||
null
|
||||
:
|
||||
<div className="mb-2 border-b-2 border-gray-200 p-6">
|
||||
<h2 className="text-xl font-bold text-gray-900 mb-2">Roadmap</h2>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-3xl font-bold text-green-600">1001</span>
|
||||
<div>
|
||||
<div className="font-semibold text-gray-900">Total Links</div>
|
||||
<div className="text-sm text-gray-500">Across all keywords</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
<div className="space-y-2 pt-0">
|
||||
{keywords.map((keyword, index) => (
|
||||
<button
|
||||
key={index}
|
||||
onClick={() => onKeywordSelect(keyword)}
|
||||
className={`w-full text-left p-3 rounded-lg flex items-center gap-3 transition-colors ${selectedKeyword === keyword ? "bg-blue-50 border-2 border-dashed border-blue-300" : "hover:bg-gray-50"}`}
|
||||
>
|
||||
{
|
||||
selectedKeyword === keyword
|
||||
?
|
||||
<>
|
||||
<img src={Vector} alt={keyword} className="w-4 h-4" />
|
||||
<span className="text-md text-blue-600 font-bold">{keyword}</span>
|
||||
</>
|
||||
:
|
||||
<>
|
||||
<img src={Vector2} alt={keyword} className="w-4 h-4" />
|
||||
<span className="text-md text-gray-700 font-bold">{keyword}</span>
|
||||
</>
|
||||
|
||||
}
|
||||
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default RoadmapSidebar
|
||||
@@ -1,262 +1,262 @@
|
||||
|
||||
import { useState } from "react"
|
||||
|
||||
interface Task {
|
||||
id: string
|
||||
title: string
|
||||
platform: "wordpress" | "substack" | "paypal" | "pinterest" | "apple-music"
|
||||
metrics?: string
|
||||
}
|
||||
|
||||
interface Column {
|
||||
id: string
|
||||
title: string
|
||||
count: number
|
||||
date: string
|
||||
tasks: Task[]
|
||||
}
|
||||
|
||||
const platformIcons = {
|
||||
wordpress: (
|
||||
<div className="w-5 h-5 bg-blue-600 rounded-sm flex items-center justify-center">
|
||||
<span className="text-white text-xs font-bold">W</span>
|
||||
</div>
|
||||
),
|
||||
substack: (
|
||||
<div className="w-5 h-5 bg-orange-500 rounded-sm flex items-center justify-center">
|
||||
<span className="text-white text-xs font-bold">S</span>
|
||||
</div>
|
||||
),
|
||||
paypal: (
|
||||
<div className="w-5 h-5 bg-blue-500 rounded-sm flex items-center justify-center">
|
||||
<span className="text-white text-xs font-bold">P</span>
|
||||
</div>
|
||||
),
|
||||
pinterest: (
|
||||
<div className="w-5 h-5 bg-red-500 rounded-sm flex items-center justify-center">
|
||||
<span className="text-white text-xs font-bold">P</span>
|
||||
</div>
|
||||
),
|
||||
"apple-music": (
|
||||
<div className="w-5 h-5 bg-black rounded-sm flex items-center justify-center">
|
||||
<span className="text-white text-xs font-bold">A</span>
|
||||
</div>
|
||||
),
|
||||
}
|
||||
|
||||
const initialData: Column[] = [
|
||||
{
|
||||
id: "published",
|
||||
title: "Published",
|
||||
count: 10,
|
||||
date: "Since 23 Oct 2025",
|
||||
tasks: [
|
||||
{
|
||||
id: "1",
|
||||
title: "Questions to ask your CPA Published o...",
|
||||
platform: "wordpress",
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
title: "Substack DR 97 DA 95",
|
||||
platform: "substack",
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
title: "Paypal DR 97 DA 95 Traffic",
|
||||
platform: "paypal",
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
title: "Pinterest DR 97 DA 95 Traffic 3,989,220",
|
||||
platform: "pinterest",
|
||||
},
|
||||
{
|
||||
id: "5",
|
||||
title: "Pinterest DR 97 DA 95 Traffic 3,989,220",
|
||||
platform: "pinterest",
|
||||
},
|
||||
{
|
||||
id: "6",
|
||||
title: "Substack DR 97 DA 95 Traffic",
|
||||
platform: "substack",
|
||||
},
|
||||
{
|
||||
id: "7",
|
||||
title: "Paypal DR 97 DA 95 Traffic 3,989,220",
|
||||
platform: "paypal",
|
||||
},
|
||||
{
|
||||
id: "8",
|
||||
title: "Apple Music DR 97 DA 95 Traffic",
|
||||
platform: "apple-music",
|
||||
},
|
||||
{
|
||||
id: "9",
|
||||
title: "Pinterest DR 97 DA 95 Traffic 3,989,220",
|
||||
platform: "pinterest",
|
||||
},
|
||||
{
|
||||
id: "10",
|
||||
title: "Questions to ask your CPA Published o...",
|
||||
platform: "wordpress",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "in-progress",
|
||||
title: "In Progress",
|
||||
count: 10,
|
||||
date: "23 Oct 2025",
|
||||
tasks: [
|
||||
{
|
||||
id: "11",
|
||||
title: "Questions to ask your CPA Published o...",
|
||||
platform: "wordpress",
|
||||
},
|
||||
{
|
||||
id: "12",
|
||||
title: "Substack DR 97 DA 95",
|
||||
platform: "substack",
|
||||
},
|
||||
{
|
||||
id: "13",
|
||||
title: "Paypal DR 97 DA 95 Traffic",
|
||||
platform: "paypal",
|
||||
},
|
||||
{
|
||||
id: "14",
|
||||
title: "Pinterest DR 97 DA 95 Traffic 3,989,220",
|
||||
platform: "pinterest",
|
||||
},
|
||||
{
|
||||
id: "15",
|
||||
title: "Pinterest DR 97 DA 95 Traffic 3,989,220",
|
||||
platform: "pinterest",
|
||||
},
|
||||
{
|
||||
id: "16",
|
||||
title: "Substack DR 97 DA 95 Traffic",
|
||||
platform: "substack",
|
||||
},
|
||||
{
|
||||
id: "17",
|
||||
title: "Paypal DR 97 DA 95 Traffic 3,989,220",
|
||||
platform: "paypal",
|
||||
},
|
||||
{
|
||||
id: "18",
|
||||
title: "Apple Music DR 97 DA 95 Traffic",
|
||||
platform: "apple-music",
|
||||
},
|
||||
{
|
||||
id: "19",
|
||||
title: "Pinterest DR 97 DA 95 Traffic 3,989,220",
|
||||
platform: "pinterest",
|
||||
},
|
||||
{
|
||||
id: "20",
|
||||
title: "Questions to ask your CPA Published o...",
|
||||
platform: "wordpress",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "next-month",
|
||||
title: "Next month",
|
||||
count: 10,
|
||||
date: "23 Nov 2025",
|
||||
tasks: [
|
||||
{
|
||||
id: "21",
|
||||
title: "Pinterest DR 97 DA 95 Traffic 3,989,220",
|
||||
platform: "pinterest",
|
||||
},
|
||||
{
|
||||
id: "22",
|
||||
title: "Substack DR 97 DA 95",
|
||||
platform: "substack",
|
||||
},
|
||||
{
|
||||
id: "23",
|
||||
title: "Pinterest DR 97 DA 95 Traffic 3,989,220",
|
||||
platform: "pinterest",
|
||||
},
|
||||
{
|
||||
id: "24",
|
||||
title: "Paypal DR 97 DA 95 Traffic",
|
||||
platform: "paypal",
|
||||
},
|
||||
{
|
||||
id: "25",
|
||||
title: "Pinterest DR 97 DA 95 Traffic 3,989,220",
|
||||
platform: "pinterest",
|
||||
},
|
||||
{
|
||||
id: "26",
|
||||
title: "Substack DR 97 DA 95 Traffic",
|
||||
platform: "substack",
|
||||
},
|
||||
{
|
||||
id: "27",
|
||||
title: "Paypal DR 97 DA 95 Traffic 3,989,220",
|
||||
platform: "paypal",
|
||||
},
|
||||
{
|
||||
id: "28",
|
||||
title: "Apple Music DR 97 DA 95 Traffic",
|
||||
platform: "apple-music",
|
||||
},
|
||||
{
|
||||
id: "29",
|
||||
title: "Pinterest DR 97 DA 95 Traffic 3,989,220",
|
||||
platform: "pinterest",
|
||||
},
|
||||
{
|
||||
id: "30",
|
||||
title: "Questions to ask your CPA Published o...",
|
||||
platform: "wordpress",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
function ShowDetails() {
|
||||
const [columns, setColumns] = useState<Column[]>(initialData)
|
||||
|
||||
return (
|
||||
<div className="p-2">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
{columns.map((column) => (
|
||||
<div key={column.id} className="bg-white rounded-lg shadow-sm border border-gray-200 ">
|
||||
{/* Column Header */}
|
||||
<div className="p-4 border-b border-gray-200 bg-[#F6F6FA] rounded-lg flex justify-between items-center ">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="font-semibold text-gray-900 text-lg">{column.title}</h3>
|
||||
<span className="bg-gray-100 text-gray-600 text-sm px-2 py-1 rounded-full">{column.count}</span>
|
||||
</div>
|
||||
<p className="text-sm text-gray-500">{column.date}</p>
|
||||
</div>
|
||||
|
||||
{/* Tasks List */}
|
||||
<div className="p-3 space-y-3 max-h-96 custom-scrollbar">
|
||||
{column.tasks.map((task) => (
|
||||
<div
|
||||
key={task.id}
|
||||
className="flex items-start gap-3 p-1 rounded-lg hover:bg-gray-100 transition-colors cursor-pointer border-b-2 border-[#f1eeee]"
|
||||
>
|
||||
{platformIcons[task.platform]}
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm line-clamp-2 leading-relaxed truncate max-w-[350px] whitespace-pre-wrap text-blue-600 hover:text-blue-800 underline">{task.title}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
import { useState } from "react"
|
||||
|
||||
interface Task {
|
||||
id: string
|
||||
title: string
|
||||
platform: "wordpress" | "substack" | "paypal" | "pinterest" | "apple-music"
|
||||
metrics?: string
|
||||
}
|
||||
|
||||
interface Column {
|
||||
id: string
|
||||
title: string
|
||||
count: number
|
||||
date: string
|
||||
tasks: Task[]
|
||||
}
|
||||
|
||||
const platformIcons = {
|
||||
wordpress: (
|
||||
<div className="w-5 h-5 bg-blue-600 rounded-sm flex items-center justify-center">
|
||||
<span className="text-white text-xs font-bold">W</span>
|
||||
</div>
|
||||
),
|
||||
substack: (
|
||||
<div className="w-5 h-5 bg-orange-500 rounded-sm flex items-center justify-center">
|
||||
<span className="text-white text-xs font-bold">S</span>
|
||||
</div>
|
||||
),
|
||||
paypal: (
|
||||
<div className="w-5 h-5 bg-blue-500 rounded-sm flex items-center justify-center">
|
||||
<span className="text-white text-xs font-bold">P</span>
|
||||
</div>
|
||||
),
|
||||
pinterest: (
|
||||
<div className="w-5 h-5 bg-red-500 rounded-sm flex items-center justify-center">
|
||||
<span className="text-white text-xs font-bold">P</span>
|
||||
</div>
|
||||
),
|
||||
"apple-music": (
|
||||
<div className="w-5 h-5 bg-black rounded-sm flex items-center justify-center">
|
||||
<span className="text-white text-xs font-bold">A</span>
|
||||
</div>
|
||||
),
|
||||
}
|
||||
|
||||
const initialData: Column[] = [
|
||||
{
|
||||
id: "published",
|
||||
title: "Published",
|
||||
count: 10,
|
||||
date: "Since 23 Oct 2025",
|
||||
tasks: [
|
||||
{
|
||||
id: "1",
|
||||
title: "Questions to ask your CPA Published o...",
|
||||
platform: "wordpress",
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
title: "Substack DR 97 DA 95",
|
||||
platform: "substack",
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
title: "Paypal DR 97 DA 95 Traffic",
|
||||
platform: "paypal",
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
title: "Pinterest DR 97 DA 95 Traffic 3,989,220",
|
||||
platform: "pinterest",
|
||||
},
|
||||
{
|
||||
id: "5",
|
||||
title: "Pinterest DR 97 DA 95 Traffic 3,989,220",
|
||||
platform: "pinterest",
|
||||
},
|
||||
{
|
||||
id: "6",
|
||||
title: "Substack DR 97 DA 95 Traffic",
|
||||
platform: "substack",
|
||||
},
|
||||
{
|
||||
id: "7",
|
||||
title: "Paypal DR 97 DA 95 Traffic 3,989,220",
|
||||
platform: "paypal",
|
||||
},
|
||||
{
|
||||
id: "8",
|
||||
title: "Apple Music DR 97 DA 95 Traffic",
|
||||
platform: "apple-music",
|
||||
},
|
||||
{
|
||||
id: "9",
|
||||
title: "Pinterest DR 97 DA 95 Traffic 3,989,220",
|
||||
platform: "pinterest",
|
||||
},
|
||||
{
|
||||
id: "10",
|
||||
title: "Questions to ask your CPA Published o...",
|
||||
platform: "wordpress",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "in-progress",
|
||||
title: "In Progress",
|
||||
count: 10,
|
||||
date: "23 Oct 2025",
|
||||
tasks: [
|
||||
{
|
||||
id: "11",
|
||||
title: "Questions to ask your CPA Published o...",
|
||||
platform: "wordpress",
|
||||
},
|
||||
{
|
||||
id: "12",
|
||||
title: "Substack DR 97 DA 95",
|
||||
platform: "substack",
|
||||
},
|
||||
{
|
||||
id: "13",
|
||||
title: "Paypal DR 97 DA 95 Traffic",
|
||||
platform: "paypal",
|
||||
},
|
||||
{
|
||||
id: "14",
|
||||
title: "Pinterest DR 97 DA 95 Traffic 3,989,220",
|
||||
platform: "pinterest",
|
||||
},
|
||||
{
|
||||
id: "15",
|
||||
title: "Pinterest DR 97 DA 95 Traffic 3,989,220",
|
||||
platform: "pinterest",
|
||||
},
|
||||
{
|
||||
id: "16",
|
||||
title: "Substack DR 97 DA 95 Traffic",
|
||||
platform: "substack",
|
||||
},
|
||||
{
|
||||
id: "17",
|
||||
title: "Paypal DR 97 DA 95 Traffic 3,989,220",
|
||||
platform: "paypal",
|
||||
},
|
||||
{
|
||||
id: "18",
|
||||
title: "Apple Music DR 97 DA 95 Traffic",
|
||||
platform: "apple-music",
|
||||
},
|
||||
{
|
||||
id: "19",
|
||||
title: "Pinterest DR 97 DA 95 Traffic 3,989,220",
|
||||
platform: "pinterest",
|
||||
},
|
||||
{
|
||||
id: "20",
|
||||
title: "Questions to ask your CPA Published o...",
|
||||
platform: "wordpress",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "next-month",
|
||||
title: "Next month",
|
||||
count: 10,
|
||||
date: "23 Nov 2025",
|
||||
tasks: [
|
||||
{
|
||||
id: "21",
|
||||
title: "Pinterest DR 97 DA 95 Traffic 3,989,220",
|
||||
platform: "pinterest",
|
||||
},
|
||||
{
|
||||
id: "22",
|
||||
title: "Substack DR 97 DA 95",
|
||||
platform: "substack",
|
||||
},
|
||||
{
|
||||
id: "23",
|
||||
title: "Pinterest DR 97 DA 95 Traffic 3,989,220",
|
||||
platform: "pinterest",
|
||||
},
|
||||
{
|
||||
id: "24",
|
||||
title: "Paypal DR 97 DA 95 Traffic",
|
||||
platform: "paypal",
|
||||
},
|
||||
{
|
||||
id: "25",
|
||||
title: "Pinterest DR 97 DA 95 Traffic 3,989,220",
|
||||
platform: "pinterest",
|
||||
},
|
||||
{
|
||||
id: "26",
|
||||
title: "Substack DR 97 DA 95 Traffic",
|
||||
platform: "substack",
|
||||
},
|
||||
{
|
||||
id: "27",
|
||||
title: "Paypal DR 97 DA 95 Traffic 3,989,220",
|
||||
platform: "paypal",
|
||||
},
|
||||
{
|
||||
id: "28",
|
||||
title: "Apple Music DR 97 DA 95 Traffic",
|
||||
platform: "apple-music",
|
||||
},
|
||||
{
|
||||
id: "29",
|
||||
title: "Pinterest DR 97 DA 95 Traffic 3,989,220",
|
||||
platform: "pinterest",
|
||||
},
|
||||
{
|
||||
id: "30",
|
||||
title: "Questions to ask your CPA Published o...",
|
||||
platform: "wordpress",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
function ShowDetails() {
|
||||
const [columns, setColumns] = useState<Column[]>(initialData)
|
||||
|
||||
return (
|
||||
<div className="p-2">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
{columns.map((column) => (
|
||||
<div key={column.id} className="bg-white rounded-lg shadow-sm border border-gray-200 ">
|
||||
{/* Column Header */}
|
||||
<div className="p-4 border-b border-gray-200 bg-[#F6F6FA] rounded-lg flex justify-between items-center ">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="font-semibold text-gray-900 text-lg">{column.title}</h3>
|
||||
<span className="bg-gray-100 text-gray-600 text-sm px-2 py-1 rounded-full">{column.count}</span>
|
||||
</div>
|
||||
<p className="text-sm text-gray-500">{column.date}</p>
|
||||
</div>
|
||||
|
||||
{/* Tasks List */}
|
||||
<div className="p-3 space-y-3 max-h-96 custom-scrollbar">
|
||||
{column.tasks.map((task) => (
|
||||
<div
|
||||
key={task.id}
|
||||
className="flex items-start gap-3 p-1 rounded-lg hover:bg-gray-100 transition-colors cursor-pointer border-b-2 border-[#f1eeee]"
|
||||
>
|
||||
{platformIcons[task.platform]}
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm line-clamp-2 leading-relaxed truncate max-w-[350px] whitespace-pre-wrap text-blue-600 hover:text-blue-800 underline">{task.title}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ShowDetails
|
||||
307
src/components/flip-card.tsx
Normal file
@@ -0,0 +1,307 @@
|
||||
"use client"
|
||||
|
||||
import type React from "react"
|
||||
import { useState } from "react"
|
||||
import { ArrowLeft, Menu } from "lucide-react"
|
||||
import CampaignTracker from "./CampaignTracker";
|
||||
|
||||
// Your existing components - make sure these are imported
|
||||
// import MultiRingCircularProgress from './multi-ring-circular-progress';
|
||||
// import CampaignTracker from './campaign-tracker';
|
||||
|
||||
const CARDS = [
|
||||
{
|
||||
title: "Richard Bower CPA",
|
||||
percent: 99,
|
||||
rings: [
|
||||
{ color: "#4285F4", value: 99 }, // blue
|
||||
{ color: "#34A853", value: 80 }, // green
|
||||
{ color: "#FBBC05", value: 60 }, // yellow
|
||||
{ color: "#EA4335", value: 40 } // red
|
||||
],
|
||||
tags: [
|
||||
{ text: "G 14", extra: "+1", color: "#4285F4" },
|
||||
{ text: "Ai" },
|
||||
{ text: "LLM" },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "CPA Los Angeles",
|
||||
percent: 24,
|
||||
rings: [
|
||||
{ color: "#4285F4", value: 24 },
|
||||
{ color: "#34A853", value: 18 },
|
||||
{ color: "#FBBC05", value: 14 },
|
||||
{ color: "#EA4335", value: 8 }
|
||||
],
|
||||
tags: [
|
||||
{ text: "G 14", extra: "-2", color: "#4285F4" }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "CPA Los Angeles4",
|
||||
percent: 45,
|
||||
rings: [
|
||||
{ color: "#4285F4", value: 45 },
|
||||
{ color: "#34A853", value: 42 },
|
||||
{ color: "#FBBC05", value: 32 },
|
||||
{ color: "#EA4335", value: 24 }
|
||||
],
|
||||
tags: [
|
||||
{ text: "G 14", extra: "-2", color: "#4285F4" },
|
||||
{ text: "+7" },
|
||||
{ text: "Ai" }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Best CPA Los Angeles5",
|
||||
percent: 89,
|
||||
rings: [
|
||||
{ color: "#4285F4", value: 89 },
|
||||
{ color: "#34A853", value: 80 },
|
||||
{ color: "#FBBC05", value: 72 },
|
||||
{ color: "#EA4335", value: 57 }
|
||||
],
|
||||
tags: [
|
||||
{ text: "G 14", extra: "+1", color: "#4285F4" },
|
||||
{ text: "+7" },
|
||||
{ text: "Ai" }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "CPA Hollywood",
|
||||
percent: 100,
|
||||
rings: [
|
||||
{ color: "#4285F4", value: 100 },
|
||||
{ color: "#34A853", value: 100 },
|
||||
{ color: "#FBBC05", value: 100 },
|
||||
{ color: "#EA4335", value: 100 }
|
||||
],
|
||||
tags: [
|
||||
{ text: "G 14", extra: "+1", color: "#4285F4" },
|
||||
{ text: "+7" },
|
||||
{ text: "Ai" }
|
||||
],
|
||||
badge: "Content Unlocked",
|
||||
actionLink: "View Published Content "
|
||||
},
|
||||
{
|
||||
title: "Best CPA Los Angeles21",
|
||||
percent: 89,
|
||||
rings: [
|
||||
{ color: "#cccccc", value: 89 },
|
||||
{ color: "#cccccc", value: 80 },
|
||||
{ color: "#cccccc", value: 72 },
|
||||
{ color: "#cccccc", value: 57 }
|
||||
],
|
||||
tags: [
|
||||
{ text: "G 14", extra: "+1", color: "#cccccc" },
|
||||
{ text: "+7", color: "#cccccc" },
|
||||
{ text: "Ai", color: "#cccccc" }
|
||||
],
|
||||
faded: true,
|
||||
paused: true
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
export default function FlipCardComplete() {
|
||||
const [selectedCampaigns, setSelectedCampaigns] = useState<string[]>([])
|
||||
const [activeCard, setActiveCard] = useState<string | null>(null)
|
||||
const [tableView, setTableView] = useState<string | null>(null)
|
||||
const [flippedCards, setFlippedCards] = useState<string[]>([])
|
||||
|
||||
const handleCardClick = (title: string, paused: boolean) => {
|
||||
if (paused) return
|
||||
|
||||
if (!flippedCards.includes(title)) {
|
||||
setFlippedCards([...flippedCards, title])
|
||||
setSelectedCampaigns([...selectedCampaigns, title])
|
||||
setActiveCard(title)
|
||||
}
|
||||
}
|
||||
|
||||
const handleBackClick = (e: React.MouseEvent, title: string) => {
|
||||
e.stopPropagation()
|
||||
setFlippedCards(flippedCards.filter((t) => t !== title))
|
||||
setSelectedCampaigns(selectedCampaigns.filter((t) => t !== title))
|
||||
setActiveCard(null)
|
||||
setTableView(null)
|
||||
}
|
||||
|
||||
const handleMenuClick = (title: string) => {
|
||||
setTableView(tableView === title ? null : title)
|
||||
}
|
||||
console.log(CARDS)
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 p-6">
|
||||
{CARDS.map((card, idx) => (
|
||||
<div key={idx} className="relative h-[500px]" style={{ perspective: "1000px" }}>
|
||||
<div
|
||||
className={`relative w-full h-full transition-transform duration-700 ease-in-out`}
|
||||
style={{
|
||||
transformStyle: "preserve-3d",
|
||||
transform: flippedCards.includes(card.title) ? "rotateY(180deg)" : "rotateY(0deg)",
|
||||
}}
|
||||
>
|
||||
|
||||
{/* FRONT FACE - Your original card preview */}
|
||||
<div
|
||||
className={`absolute w-full h-full bg-white rounded-2xl shadow-lg border transition-all duration-300 hover:shadow-2xl hover:scale-105 cursor-pointer
|
||||
${card.faded ? "opacity-60" : ""}
|
||||
${card.paused ? "cursor-not-allowed" : "cursor-pointer"}`}
|
||||
style={{
|
||||
backfaceVisibility: "hidden",
|
||||
WebkitBackfaceVisibility: "hidden",
|
||||
}}
|
||||
onClick={() => handleCardClick(card.title, card.paused)}
|
||||
>
|
||||
<div className="p-5 flex flex-col items-center h-full justify-center">
|
||||
{card.badge && (
|
||||
<div className="absolute top-4 right-4 bg-green-500 text-white px-3 py-1 rounded-full text-xs font-bold transform rotate-12 shadow-lg">
|
||||
{card.badge}
|
||||
</div>
|
||||
)}
|
||||
<div className="w-32 h-32 rounded-full bg-gradient-to-br from-blue-400 to-purple-500 flex items-center justify-center text-white font-bold text-2xl">
|
||||
{card.title.charAt(0)}
|
||||
</div>
|
||||
|
||||
<div className={`mt-4 font-bold text-lg text-center ${card.faded ? "text-gray-400" : "text-gray-900"}`}>
|
||||
{card.title}
|
||||
</div>
|
||||
|
||||
<div className="mt-3 flex flex-row gap-2 flex-wrap justify-center">
|
||||
{card.tags.map((tag, i) => (
|
||||
<span
|
||||
key={i}
|
||||
className={`rounded-full border-2 bg-white px-3 py-1 flex items-center text-sm font-medium transition-all duration-300 hover:scale-105 ${
|
||||
card.faded
|
||||
? "text-gray-400 border-gray-300"
|
||||
: "text-gray-700 border-gray-200 hover:border-blue-300"
|
||||
}`}
|
||||
style={tag.color ? { color: tag.color, borderColor: tag.color } : {}}
|
||||
>
|
||||
{tag.text}
|
||||
{tag.extra && (
|
||||
<span className={`ml-1 text-xs ${card.faded ? "text-gray-400" : "text-green-600 font-bold"}`}>
|
||||
{tag.extra}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{card.actionLink && (
|
||||
<div className="mt-3 text-blue-600 text-sm cursor-pointer hover:underline transition-all duration-300 hover:text-blue-800">
|
||||
{card.actionLink}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{card.paused && (
|
||||
<div className="mt-3 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>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* BACK FACE - Your original expanded card view */}
|
||||
<div
|
||||
className="absolute w-full h-full bg-white rounded-2xl shadow-2xl border"
|
||||
style={{
|
||||
backfaceVisibility: "hidden",
|
||||
WebkitBackfaceVisibility: "hidden",
|
||||
transform: "rotateY(180deg)",
|
||||
}}
|
||||
>
|
||||
<div className="py-3 flex flex-col h-full">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between border-b border-gray-200 pb-2 px-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
onClick={(e) => handleBackClick(e, card.title)}
|
||||
className="p-2 hover:bg-gray-100 rounded-lg transition-all duration-300 hover:scale-110"
|
||||
>
|
||||
<ArrowLeft className="w-5 h-5 text-gray-600" />
|
||||
</button>
|
||||
<h1 className="text-xl font-bold text-gray-900">{card.title}</h1>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => handleMenuClick(card.title)}
|
||||
className="p-2 hover:bg-gray-100 rounded-lg transition-all duration-300 hover:scale-110"
|
||||
title="View table"
|
||||
>
|
||||
<Menu className="w-5 h-5 text-gray-600" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Content with smooth transitions */}
|
||||
<div
|
||||
className={`transition-all duration-700 ease-in-out px-4 ${
|
||||
tableView === card.title
|
||||
? "opacity-0 translate-y-4 pointer-events-none"
|
||||
: "opacity-100 translate-y-0"
|
||||
}`}
|
||||
style={{ display: tableView !== card.title ? "block" : "none" }}
|
||||
>
|
||||
<div className="py-4">
|
||||
<CampaignTracker />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Table View with smooth transitions */}
|
||||
<div
|
||||
className={`transition-all duration-700 ease-in-out flex-1 overflow-hidden ${
|
||||
tableView === card.title
|
||||
? "opacity-100 translate-y-0"
|
||||
: "opacity-0 translate-y-4 pointer-events-none"
|
||||
}`}
|
||||
style={{ display: tableView === card.title ? "block" : "none" }}
|
||||
>
|
||||
<div className="overflow-y-auto h-full custom-scrollbar-table">
|
||||
<table className="w-full rounded-lg">
|
||||
<tbody>
|
||||
{[
|
||||
{ icon: "🅰️", title: "Questions to ask your CPA httpAmazon" },
|
||||
{ icon: "🅰️", title: "Questions to ask your CPA Published on Amazon" },
|
||||
{ icon: "🎵", title: "Questions to ask your CPA Published on Amazon AWS" },
|
||||
{ icon: "🅰️", title: "Questions to ask your CPA httpAmazon" },
|
||||
{ icon: "🅰️", title: "Questions to ask your CPA Published on Amazon" },
|
||||
{ icon: "🎵", title: "Questions to ask your CPA Published on Amazon AWS" },
|
||||
{ icon: "🅰️", title: "Questions to ask your CPA httpAmazon" },
|
||||
{ icon: "🅰️", title: "Questions to ask your CPA Published on Amazon" },
|
||||
{ icon: "🎵", title: "Questions to ask your CPA Published on Amazon AWS" },
|
||||
{ icon: "🅿️", title: "Pinterest DR 97 DA 95 Traffic 3,989,220" },
|
||||
{ icon: "📄", title: "Questions to ask your CPA Substack DR 97 DA 95" },
|
||||
].map((row, rIdx) => (
|
||||
<tr key={rIdx} className="border-b last:border-0 hover:bg-[#f1f7fe] transition-colors">
|
||||
<td className="py-3 pl-2 text-center w-10">{row.icon}</td>
|
||||
<td
|
||||
className="py-3 px-1 min-w-[300px] text-[#4285F4] hover:text-[#1e40af] cursor-pointer font-medium"
|
||||
title={row.title}
|
||||
>
|
||||
<div className="truncate max-w-[300px] text-sm underline">{row.title}</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div
|
||||
className="text-blue-600 text-sm cursor-pointer hover:underline transition-all duration-300 hover:text-blue-800 text-center pt-3"
|
||||
style={{ boxShadow: "#dadada 0px -6px 10px" }}
|
||||
>
|
||||
View All
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
import React from 'react'
|
||||
|
||||
const ContainerPage = ({ children }: { children: React.ReactNode }) => {
|
||||
return (
|
||||
<div className='min-h-screen container pt-6 pb-20'>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
import React from 'react'
|
||||
|
||||
const ContainerPage = ({ children }: { children: React.ReactNode }) => {
|
||||
return (
|
||||
<div className='min-h-screen container pt-6 pb-20'>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ContainerPage
|
||||
@@ -3,9 +3,9 @@ import { Link } from "react-router-dom"
|
||||
const Footer = () => {
|
||||
return (
|
||||
<footer className="bg-gray-100 py-6">
|
||||
<div className="max-w-7xl mx-auto px-6">
|
||||
<div className="container px-6">
|
||||
{/* Navigation Links */}
|
||||
<nav className="flex flex-wrap items-center justify-center space-x-8 mb-4">
|
||||
<nav className="flex flex-wrap items-center space-x-8 mb-4">
|
||||
<Link to="#" className="text-gray-600 hover:text-gray-800 font-medium m-0">Home</Link>
|
||||
<Link to="#" className="text-gray-600 hover:text-gray-800 font-medium m-0">About</Link>
|
||||
<Link to="#" className="text-gray-600 hover:text-gray-800 font-medium m-0">Journal</Link>
|
||||
@@ -15,7 +15,7 @@ const Footer = () => {
|
||||
</nav>
|
||||
|
||||
{/* Copyright and Legal */}
|
||||
<div className="flex items-center justify-center text-sm text-gray-600 lg:flex-row flex-col">
|
||||
<div className="flex items-center text-sm text-gray-600 lg:flex-row flex-col">
|
||||
<span>©One Keyword LLC. Premiere Position LLC.</span>
|
||||
<span className="mx-2">|</span>
|
||||
<span>30745 Pacific Coast Hwy Ste 21 Malibu, CA 90265</span>
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import MetricCard from "./MetricCard"
|
||||
import UrlDropdown from "./UrlDropdown"
|
||||
|
||||
const HeaderPage = ({ title }: { title: string }) => {
|
||||
return (
|
||||
<div className="flex space-x-4 my-4 lg:flex-row flex-col items-baseline">
|
||||
<h1 className="text-3xl font-bold text-gray-800">{title}</h1>
|
||||
|
||||
<UrlDropdown />
|
||||
|
||||
<div className="flex space-x-4">
|
||||
<MetricCard value="12" label="DR" valueChange="+1" size="sm" />
|
||||
<MetricCard value="14" label="DA" valueChange="+3" size="sm" />
|
||||
<MetricCard value="10" label="AIQ" valueChange="+2" size="sm" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
import MetricCard from "./MetricCard"
|
||||
import UrlDropdown from "./UrlDropdown"
|
||||
|
||||
const HeaderPage = ({ title }: { title: string }) => {
|
||||
return (
|
||||
<div className="flex space-x-4 my-4 lg:flex-row flex-col items-baseline">
|
||||
<h1 className="text-3xl font-bold text-gray-800">{title}</h1>
|
||||
|
||||
<UrlDropdown />
|
||||
|
||||
<div className="flex space-x-4">
|
||||
<MetricCard value="12" label="DR" valueChange="+1" size="sm" />
|
||||
<MetricCard value="14" label="DA" valueChange="+3" size="sm" />
|
||||
<MetricCard value="10" label="AIQ" valueChange="+2" size="sm" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default HeaderPage
|
||||
@@ -1,78 +1,82 @@
|
||||
import { ArrowUp, Info } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
// Reusable Metric Card Component
|
||||
const MetricCard = ({ icon: Icon, img: Img, value, label, tooltipText, valueChange, size }: { icon: any, img: any, value: any, label: any, tooltipText: any, valueChange: any, size: any }) => {
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
|
||||
if (Img) {
|
||||
return (
|
||||
<div className="relative cursor-pointer"
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}>
|
||||
<img src={Img} alt="Img" className="w-12 h-12" />
|
||||
<div className=" ">
|
||||
{tooltipText && (
|
||||
<div className="relative">
|
||||
{isHovered && (
|
||||
<div className="absolute top-full left-1/2 -translate-x-1/2 mt-2 px-4 py-2 w-72 bg-gray-800 text-white text-sm rounded-lg shadow-lg z-50 transition-opacity duration-300 pointer-events-none opacity-100">
|
||||
{/* Tooltip triangle */}
|
||||
<div className="absolute bottom-full left-1/2 -translate-x-1/2 w-0 h-0 border-8 border-transparent border-b-gray-800"></div>
|
||||
{tooltipText}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className="flex-1 min-w-[50px] bg-white rounded-xl shadow-sm p-2 flex items-center space-x-2 relative"
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
>
|
||||
{Icon && (
|
||||
<div className="p-3 bg-gray-100 rounded-lg">
|
||||
<Icon size={24} className="text-gray-500" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
|
||||
<div className="flex-1">
|
||||
<div className={`${size === 'sm' ? 'text-sm border-2 border-gray-200 px-1 rounded-lg' : 'text-xl'} font-bold text-gray-800 flex items-center space-x-2`}>
|
||||
<span>{value}</span>
|
||||
{valueChange && (
|
||||
<div className="flex items-center text-sm font-semibold text-green-500">
|
||||
{Icon && (
|
||||
<ArrowUp size={14} className="inline-block mr-1" />
|
||||
)}
|
||||
<span>{valueChange}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center space-x-1 mt-1 text-gray-500">
|
||||
<span className="text-sm">{label}</span>
|
||||
{tooltipText && (
|
||||
<div className="relative">
|
||||
<Info size={16} className="text-gray-400 cursor-pointer" />
|
||||
{isHovered && (
|
||||
<div className="absolute top-full left-1/2 -translate-x-1/2 mt-2 px-4 py-2 w-72 bg-gray-800 text-white text-sm rounded-lg shadow-lg z-50 transition-opacity duration-300 pointer-events-none opacity-100">
|
||||
{/* Tooltip triangle */}
|
||||
<div className="absolute bottom-full left-1/2 -translate-x-1/2 w-0 h-0 border-8 border-transparent border-b-gray-800"></div>
|
||||
{tooltipText}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
import { ArrowUp, Info } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
|
||||
// Reusable Metric Card Component
|
||||
const MetricCard = ({ icon: Icon, img: Img, value, label, tooltipText, valueChange, size, extraStyle }: { icon?: any, img?: any, value?: any, label?: any, tooltipText?: any, valueChange?: any, size?: any, extraStyle?: any }) => {
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
|
||||
if (Img) {
|
||||
return (
|
||||
<div className="relative cursor-pointer"
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}>
|
||||
<div className={`${extraStyle}`}>
|
||||
<img src={Img} alt="Img" className="w-10 h-10" />
|
||||
</div>
|
||||
<div className="">
|
||||
{tooltipText && (
|
||||
<div className="relative">
|
||||
{isHovered && (
|
||||
<div className="absolute top-full left-1/2 -translate-x-1/2 mt-2 px-4 py-2 min-w-28 text-center bg-gray-800 text-white text-lg rounded-lg shadow-lg z-50 transition-opacity duration-300 pointer-events-none opacity-100 ">
|
||||
{/* Tooltip triangle */}
|
||||
<div className="absolute bottom-full left-1/2 -translate-x-1/2 w-0 h-0 border-8 border-transparent border-b-gray-800 "></div>
|
||||
{tooltipText}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex-1 min-w-[50px] bg-white rounded-xl shadow-sm p-2 flex items-center space-x-2 relative"
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
>
|
||||
{Icon && (
|
||||
<div className="p-3 bg-gray-100 rounded-lg">
|
||||
<Icon size={24} className="text-gray-500" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex-1">
|
||||
<div className={`${size === 'sm' ? 'text-sm border-2 border-gray-200 px-1 rounded-lg' : 'text-xl'} font-bold text-gray-800 flex items-center space-x-2`}>
|
||||
<span className="text-sm text-gray-400">{!tooltipText && label}</span>
|
||||
|
||||
<span className="text-gray-600 bg-gray-200 px-1 py-1 rounded-md">{value}</span>
|
||||
{valueChange && (
|
||||
<div className="flex items-center text-sm font-semibold text-green-500">
|
||||
{Icon && (
|
||||
<ArrowUp size={14} className="inline-block mr-1" />
|
||||
)}
|
||||
<span>{valueChange}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center space-x-1 mt-1 text-gray-500">
|
||||
<span className="text-sm">{tooltipText && label}</span>
|
||||
{tooltipText && (
|
||||
<div className="relative">
|
||||
<Info size={16} className="text-gray-400 cursor-pointer" />
|
||||
{isHovered && (
|
||||
<div className="absolute top-full left-1/2 -translate-x-1/2 mt-2 px-4 py-2 w-72 bg-gray-800 text-white text-sm rounded-lg shadow-lg z-50 transition-opacity duration-300 pointer-events-none opacity-100">
|
||||
{/* Tooltip triangle */}
|
||||
<div className="absolute bottom-full left-1/2 -translate-x-1/2 w-0 h-0 border-8 border-transparent border-b-gray-800"></div>
|
||||
{tooltipText}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MetricCard
|
||||
@@ -1,11 +1,11 @@
|
||||
import './scrollableComponent.css';
|
||||
|
||||
export default function MyScrollableComponent() {
|
||||
return (
|
||||
<div className="custom-scrollbar">
|
||||
{[...Array(30)].map((_, index) => (
|
||||
<p key={index}>Line {index + 1}</p>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
import './scrollableComponent.css';
|
||||
|
||||
export default function MyScrollableComponent() {
|
||||
return (
|
||||
<div className="custom-scrollbar">
|
||||
{[...Array(30)].map((_, index) => (
|
||||
<p key={index}>Line {index + 1}</p>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
59
src/components/shared/Stepper.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
|
||||
import { Check } from "lucide-react"
|
||||
|
||||
interface Step {
|
||||
number: number
|
||||
label: string
|
||||
status: "active" | "inactive" | "completed"
|
||||
}
|
||||
|
||||
interface StepperProps {
|
||||
steps: Step[]
|
||||
}
|
||||
|
||||
export function Stepper({ steps }: StepperProps) {
|
||||
return (
|
||||
<div className="inline-flex items-center gap-3 rounded-full px-4 py-2">
|
||||
{steps.map((step, index) => (
|
||||
<div key={step.number} className="flex items-center gap-3 ">
|
||||
{/* Step Badge and Label */}
|
||||
<div className="flex items-center gap-2 bg-[#E6ECFB] rounded-full px-3 py-2">
|
||||
{/* Numbered Badge */}
|
||||
<div
|
||||
className={`flex items-center justify-center w-6 h-6 rounded-full text-md font-semibold ${
|
||||
step.status === "completed" || step.status === "active" ? "bg-[#4C60E5] text-white" : "bg-gray-300 text-gray-600"
|
||||
}`}
|
||||
>
|
||||
{step.number}
|
||||
</div>
|
||||
|
||||
{/* Step Label */}
|
||||
<span
|
||||
className={`text-sm font-medium whitespace-nowrap ${
|
||||
step.status === "active" || step.status === "completed" ? "text-gray-900" : "text-gray-500"
|
||||
}`}
|
||||
>
|
||||
{step.label}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Separator - Three Dots or Checkmark */}
|
||||
{index < steps.length - 1 && (
|
||||
<div className="flex items-center gap-1">
|
||||
<div className={`w-1 h-1 rounded-full ${step.status === "completed" ? "bg-[#4C60E5]" : "bg-gray-400"}`} />
|
||||
<div className={`w-1 h-1 rounded-full ${step.status === "completed" ? "bg-[#4C60E5]" : "bg-gray-400"}`} />
|
||||
<div className={`w-1 h-1 rounded-full ${step.status === "completed" ? "bg-[#4C60E5]" : "bg-gray-400"}`} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Checkmark Icon for Last Step */}
|
||||
{index === steps.length - 1 && (
|
||||
<div className="flex items-center justify-center w-6 h-6 rounded-full bg-gray-300">
|
||||
<Check className="w-4 h-4 text-white" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,56 +1,56 @@
|
||||
// components/UrlDropdown.jsx
|
||||
import { useState } from "react";
|
||||
|
||||
const urlOptions = [
|
||||
"http://www.mysite.com",
|
||||
"http://www.myseosucks.com",
|
||||
"http://www.sribbble.com",
|
||||
"http://www.myseosucks.com",
|
||||
"http://www.myseosucks.com",
|
||||
"http://www.myseosucks.com",
|
||||
];
|
||||
|
||||
export default function UrlDropdown() {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [selected, setSelected] = useState(urlOptions[0]);
|
||||
|
||||
const toggleDropdown = () => setIsOpen((prev) => !prev);
|
||||
const handleSelect = (url) => {
|
||||
setSelected(url);
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative text-sm max-w-[400px] min-w-[220px]">
|
||||
{/* Input box */}
|
||||
<div
|
||||
className="flex justify-between items-center border border-gray-300 bg-white px-2 py-1 rounded-xl cursor-pointer"
|
||||
onClick={toggleDropdown}
|
||||
>
|
||||
<span className="truncate text-gray-800">{selected}</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-gray-500 text-xs">▼</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Dropdown list */}
|
||||
{isOpen && (
|
||||
<div className="absolute mt-1 w-full bg-white border border-gray-200 rounded shadow max-h-60 overflow-y-auto z-10">
|
||||
{urlOptions.map((url, index) => (
|
||||
<div
|
||||
key={index}
|
||||
onClick={() => handleSelect(url)}
|
||||
className={`flex justify-between items-center px-3 py-2 hover:bg-gray-100 cursor-pointer ${selected === url ? "font-bold text-black" : "text-gray-700"
|
||||
}`}
|
||||
>
|
||||
<span className="truncate">{url}</span>
|
||||
{selected === url && (
|
||||
<span className="text-green-500 text-sm">✔</span>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// components/UrlDropdown.jsx
|
||||
import { useState } from "react";
|
||||
|
||||
const urlOptions = [
|
||||
"http://www.mysite.com",
|
||||
"http://www.myseosucks.com",
|
||||
"http://www.sribbble.com",
|
||||
"http://www.myseosucks.com",
|
||||
"http://www.myseosucks.com",
|
||||
"http://www.myseosucks.com",
|
||||
];
|
||||
|
||||
export default function UrlDropdown() {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [selected, setSelected] = useState(urlOptions[0]);
|
||||
|
||||
const toggleDropdown = () => setIsOpen((prev) => !prev);
|
||||
const handleSelect = (url) => {
|
||||
setSelected(url);
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative text-sm max-w-[400px] min-w-[220px]">
|
||||
{/* Input box */}
|
||||
<div
|
||||
className="flex justify-between items-center border border-gray-300 bg-white px-2 py-1 rounded-xl cursor-pointer focus:border-2 focus:border-blue-500"
|
||||
onClick={toggleDropdown}
|
||||
>
|
||||
<span className="truncate text-gray-800">{selected}</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-gray-500 text-xs">▼</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Dropdown list */}
|
||||
{isOpen && (
|
||||
<div className="absolute mt-1 w-full bg-white border border-gray-200 rounded shadow max-h-60 overflow-y-auto z-10 ">
|
||||
{urlOptions.map((url, index) => (
|
||||
<div
|
||||
key={index}
|
||||
onClick={() => handleSelect(url)}
|
||||
className={`flex justify-between items-center px-3 py-2 hover:bg-gray-100 cursor-pointer ${selected === url ? "font-bold text-black" : "text-gray-700"
|
||||
}`}
|
||||
>
|
||||
<span className="truncate">{url}</span>
|
||||
{selected === url && (
|
||||
<span className="text-green-500 text-sm">✔</span>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,44 +1,44 @@
|
||||
.custom-scrollbar {
|
||||
max-height: 600px;
|
||||
overflow-y: scroll;
|
||||
border: 1px solid #ccc;
|
||||
/* padding: 10px; */
|
||||
}
|
||||
|
||||
.custom-scrollbar-table {
|
||||
max-height: 460px;
|
||||
overflow-y: scroll;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
/* ==== WebKit-based browsers (Chrome, Edge, Safari) ==== */
|
||||
.custom-scrollbar::-webkit-scrollbar {
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-track {
|
||||
background: #eee;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||
background: #999;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
||||
background: #555;
|
||||
}
|
||||
|
||||
/* Remove the up/down arrows */
|
||||
.custom-scrollbar::-webkit-scrollbar-button {
|
||||
display: none;
|
||||
height: 0;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
/* ==== Firefox ==== */
|
||||
.custom-scrollbar {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #999 #eee;
|
||||
}
|
||||
.custom-scrollbar {
|
||||
max-height: 600px;
|
||||
overflow-y: scroll;
|
||||
border: 1px solid #ccc;
|
||||
/* padding: 10px; */
|
||||
}
|
||||
|
||||
.custom-scrollbar-table {
|
||||
max-height: 460px;
|
||||
overflow-y: scroll;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
/* ==== WebKit-based browsers (Chrome, Edge, Safari) ==== */
|
||||
.custom-scrollbar::-webkit-scrollbar {
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-track {
|
||||
background: #eee;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||
background: #999;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
||||
background: #555;
|
||||
}
|
||||
|
||||
/* Remove the up/down arrows */
|
||||
.custom-scrollbar::-webkit-scrollbar-button {
|
||||
display: none;
|
||||
height: 0;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
/* ==== Firefox ==== */
|
||||
.custom-scrollbar {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #999 #eee;
|
||||
}
|
||||
|
||||
136
src/components/test
Normal file
@@ -0,0 +1,136 @@
|
||||
{CARDS.map((card, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className={`bg-white rounded-2xl shadow-lg border transition-all duration-700 ease-in-out transform
|
||||
hover:shadow-2xl hover:scale-105 cursor-pointer
|
||||
${card.faded ? "opacity-60" : ""}
|
||||
${activeCard === card.title ? "cursor-default" : "cursor-pointer"}
|
||||
${selectedCampaigns.includes(card.title)
|
||||
? 'shadow-2xl scale-[1.02] '
|
||||
: 'hover:scale-105 hover:shadow-xl'
|
||||
} `}
|
||||
onClick={() => handleCardClick(card.title, card.paused)}
|
||||
style={{
|
||||
transform: selectedCampaigns.includes(card.title)
|
||||
? 'translateY(-5px) scale(1.02)'
|
||||
: 'translateY(0px) scale(1)'
|
||||
}}
|
||||
>
|
||||
{selectedCampaigns.includes(card.title) && activeCard === card.title ? (
|
||||
<div className="py-3 flex flex-col ">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between border-b border-gray-200 pb-2">
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
onClick={handleBackClick}
|
||||
className="p-2 hover:bg-gray-100 rounded-lg transition-all duration-300 hover:scale-110"
|
||||
>
|
||||
<ArrowLeft className="w-5 h-5 text-gray-600" />
|
||||
</button>
|
||||
<h1 className="text-xl font-bold text-gray-900">{card.title}</h1>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => handleMenuClick(card.title)}
|
||||
className="p-2 hover:bg-gray-100 rounded-lg transition-all duration-300 hover:scale-110"
|
||||
title="View table"
|
||||
>
|
||||
<Menu className="w-5 h-5 text-gray-600" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Content with smooth transitions */}
|
||||
<div className={`transition-all duration-700 ease-in-out ${tableView === card.title
|
||||
? 'opacity-0 translate-y-4 pointer-events-none'
|
||||
: 'opacity-100 translate-y-0'
|
||||
}`}
|
||||
style={{ display: tableView !== card.title ? 'block' : 'none' }}>
|
||||
<CampaignTracker />
|
||||
</div>
|
||||
|
||||
{/* Table View with smooth transitions */}
|
||||
<div className={`transition-all duration-700 ease-in-out ${tableView === card.title
|
||||
? 'opacity-100 translate-y-0'
|
||||
: 'opacity-0 translate-y-4 pointer-events-none'
|
||||
}`}
|
||||
style={{ display: tableView === card.title ? 'block' : 'none' }}
|
||||
>
|
||||
<div className="custom-scrollbar-table">
|
||||
<table className="w-full rounded-lg ">
|
||||
<tbody>
|
||||
{[
|
||||
{ icon: '🅰️', title: 'Questions to ask your CPA httpAmazon' },
|
||||
{ icon: '🅰️', title: 'Questions to ask your CPA Published on Amazon' },
|
||||
{ icon: '🎵', title: 'Questions to ask your CPA Published on Amazon AWS' },
|
||||
{ icon: '🅰️', title: 'Questions to ask your CPA httpAmazon' },
|
||||
{ icon: '🅰️', title: 'Questions to ask your CPA Published on Amazon' },
|
||||
{ icon: '🎵', title: 'Questions to ask your CPA Published on Amazon AWS' },
|
||||
{ icon: '🅰️', title: 'Questions to ask your CPA httpAmazon' },
|
||||
{ icon: '🅰️', title: 'Questions to ask your CPA Published on Amazon' },
|
||||
{ icon: '🎵', title: 'Questions to ask your CPA Published on Amazon AWS' },
|
||||
{ icon: '🅿️', title: 'Pinterest DR 97 DA 95 Traffic 3,989,220' },
|
||||
{ icon: '📄', title: 'Questions to ask your CPA Substack DR 97 DA 95' }
|
||||
].map((row, rIdx) => (
|
||||
<tr key={rIdx} className='border-b last:border-0 hover:bg-[#f1f7fe] transition-colors'>
|
||||
<td className='py-3 pl-2 text-center w-10'>{row.icon}</td>
|
||||
<td className='py-3 px-1 min-w-[300px] text-[#4285F4] hover:text-[#1e40af] cursor-pointer font-medium' title={row.title}>
|
||||
<div className='truncate max-w-[300px] text-sm underline'>{row.title}</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
|
||||
</div>
|
||||
<div className="text-blue-600 text-sm cursor-pointer hover:underline transition-all duration-300 hover:text-blue-800 text-center pt-3" style={{ boxShadow: '#dadada 0px -6px 10px' }}>View All</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
) : (
|
||||
// Card preview state
|
||||
<div className="p-5 flex flex-col items-center">
|
||||
{card.badge && (
|
||||
<div className="absolute top-4 right-4 bg-green-500 text-white px-3 py-1 rounded-full text-xs font-bold transform rotate-12 shadow-lg">
|
||||
{card.badge}
|
||||
</div>
|
||||
)}
|
||||
<MultiRingCircularProgress card={card} />
|
||||
<div className={`mt-4 font-bold text-lg text-center ${card.faded ? "text-gray-400" : "text-gray-900"}`}>
|
||||
{card.title}
|
||||
</div>
|
||||
<div className="mt-3 flex flex-row gap-2 flex-wrap justify-center">
|
||||
{card.tags.map((tag, i) => (
|
||||
<span
|
||||
key={i}
|
||||
className={`rounded-full border-2 bg-white px-3 py-1 flex items-center text-sm font-medium transition-all duration-300 hover:scale-105 ${card.faded
|
||||
? "text-gray-400 border-gray-300"
|
||||
: "text-gray-700 border-gray-200 hover:border-blue-300"
|
||||
}`}
|
||||
style={tag.color ? { color: tag.color, borderColor: tag.color } : {}}
|
||||
>
|
||||
{tag.text}
|
||||
{tag.extra && (
|
||||
<span className={`ml-1 text-xs ${card.faded ? "text-gray-400" : "text-green-600 font-bold"
|
||||
}`}>
|
||||
{tag.extra}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
{card.actionLink && (
|
||||
<div className="mt-3 text-blue-600 text-sm cursor-pointer hover:underline transition-all duration-300 hover:text-blue-800">
|
||||
{card.actionLink}
|
||||
</div>
|
||||
)}
|
||||
{card.paused && (
|
||||
<div className="mt-3 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>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
))}
|
||||
@@ -1,12 +1,12 @@
|
||||
import DashboardGrid from "../components/DashboardGrid";
|
||||
import ContainerPage from "../components/shared/ContainerPage";
|
||||
|
||||
const Dashboard = () => {
|
||||
return (
|
||||
<ContainerPage>
|
||||
<DashboardGrid />
|
||||
</ContainerPage>
|
||||
)
|
||||
}
|
||||
|
||||
import DashboardGrid from "../components/DashboardGrid";
|
||||
import ContainerPage from "../components/shared/ContainerPage";
|
||||
|
||||
const Dashboard = () => {
|
||||
return (
|
||||
<ContainerPage>
|
||||
<DashboardGrid />
|
||||
</ContainerPage>
|
||||
)
|
||||
}
|
||||
|
||||
export default Dashboard
|
||||
@@ -1,204 +1,205 @@
|
||||
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';
|
||||
|
||||
|
||||
// Sidebar data
|
||||
const sidebarLinks = [
|
||||
'CPA Los Angeles',
|
||||
'Richard Bower CPA',
|
||||
'CPA Los Angeles',
|
||||
'CPA Hollywood',
|
||||
'Best CPA Los Angeles',
|
||||
'CPA Los Angeles',
|
||||
];
|
||||
|
||||
// Category table data
|
||||
const categories = [
|
||||
{
|
||||
name: 'Social',
|
||||
color: 'border-l-4 border-[#4285F4] bg-[#001EE21C]',
|
||||
rows: [
|
||||
{ icon: '🅰️', title: 'Questions to ask your CPA httpAmazon', DR: 90, DA: 97, traffic: '3,902,293', indexed: 'Done', interlinked: 'Done', date: '9/16/25' },
|
||||
{ icon: '🅰️', title: 'Questions to ask your CPA Published on Amazon', DR: 90, DA: 97, traffic: '3,902,293', indexed: 'Done', interlinked: 'Done', date: '9/16/25' },
|
||||
{ icon: '🎵', title: 'Questions to ask your CPA Published on Amazon AWS', DR: 90, DA: 97, traffic: '3,902,293', indexed: 'In Progress', interlinked: 'N/A', date: '9/16/25' },
|
||||
{ icon: '🅿️', title: 'Pinterest DR 97 DA 95 Traffic 3,989,220', DR: 90, DA: 97, traffic: '3,902,293', indexed: 'Done', interlinked: 'Done', date: '9/16/25' },
|
||||
{ icon: '📄', title: 'Questions to ask your CPA Substack DR 97 DA 95', DR: 90, DA: 97, traffic: '3,902,293', indexed: 'In Progress', interlinked: 'In Progress', date: '9/16/25' }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Citation',
|
||||
color: 'border-l-4 border-[#0ea5e9] bg-[#3C87FF2B]',
|
||||
rows: [
|
||||
{ icon: '📄', title: 'Substack DR 97 DA 95', DR: 90, DA: 97, traffic: '3,902,293', indexed: 'Done', interlinked: 'N/A', date: '9/16/25' },
|
||||
{ icon: '🅰️', title: 'Questions to ask your CPA Published on Amazon AWS', DR: 90, DA: 97, traffic: '3,902,293', indexed: 'Done', interlinked: 'Done', date: '9/16/25' },
|
||||
{ icon: '🅿️', title: 'Paypal DR 97 DA 95 Traffic', DR: 90, DA: 97, traffic: '3,902,293', indexed: 'Done', interlinked: 'Done', date: '9/16/25' }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Web 2.0',
|
||||
color: 'border-l-4 border-[#22c55e] bg-[#f0fdf4]',
|
||||
rows: [
|
||||
{ icon: '🅿️', title: 'Paypal DR 97 DA 95 Traffic', DR: 90, DA: 97, traffic: '3,902,293', indexed: 'In Progress', interlinked: 'Done', date: '9/16/25' },
|
||||
{ icon: '🎵', title: 'Apple Music DR 97 DA 95 Traffic', DR: 90, DA: 97, traffic: '3,902,293', indexed: 'Done', interlinked: 'Done', date: '9/16/25' },
|
||||
{ icon: '📄', title: 'Substack DR 97 DA 95', DR: 90, DA: 97, traffic: '3,902,293', indexed: 'Done', interlinked: 'Done', date: '9/16/25' },
|
||||
{ icon: '🅰️', title: 'Questions to ask your CPA Published on Amazon AWS', DR: 90, DA: 97, traffic: '3,902,293', indexed: 'Done', interlinked: 'Done', date: '9/16/25' },
|
||||
{ icon: '🅿️', title: 'Pin DR 97 DA 95 Traffic', DR: 90, DA: 97, traffic: '3,902,293', indexed: 'Done', interlinked: 'Done', date: '9/16/25' }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const pillClass = (val: string) =>
|
||||
val === 'Done' ? 'bg-green-100 text-green-700 font-bold'
|
||||
: val === 'In Progress' ? 'bg-blue-100 text-blue-700 whitespace-nowrap font-bold'
|
||||
: 'bg-gray-100 text-gray-700 font-bold';
|
||||
|
||||
function Portfolio() {
|
||||
const [selectedKeyword, setSelectedKeyword] = useState("CPA Los Angeles")
|
||||
|
||||
|
||||
return (
|
||||
<ContainerPage>
|
||||
<HeaderPage title="Live Library" />
|
||||
<div className='flex gap-8 mx-auto'>
|
||||
{/* Sidebar */}
|
||||
{/* <aside className='w-72 border rounded-2xl shadow-sm bg-white px-5 py-6 h-fit'>
|
||||
<div className='font-extrabold text-xl mb-5 text-[#4285F4] flex items-center'>
|
||||
<span className='w-3 h-3 rounded-full bg-[#4285F4] mr-2'></span>
|
||||
Live Library
|
||||
</div>
|
||||
<ul className='space-y-2'>
|
||||
{sidebarLinks.map((link, idx) => (
|
||||
<li key={idx} className={`px-3 py-2 text-md flex items-center gap-2 rounded-xl cursor-pointer hover:bg-[#e3edfc] transition-colors ${idx === 0 ? 'bg-[#e3edfc] text-[#4285F4] font-bold' : 'text-gray-700'}`}>
|
||||
|
||||
{
|
||||
idx === 0
|
||||
?
|
||||
<img src={Vector} alt={link} className="w-4 h-4" />
|
||||
:
|
||||
<img src={Vector2} alt={link} className="w-4 h-4" />
|
||||
|
||||
}
|
||||
{link}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</aside> */}
|
||||
|
||||
<RoadmapSidebar mode={"portfolio"} selectedKeyword={selectedKeyword} onKeywordSelect={setSelectedKeyword} />
|
||||
|
||||
|
||||
{/* Table Section */}
|
||||
<section className='flex-1 bg-white border rounded-2xl shadow-sm px-6 py-4'>
|
||||
{/* Title Bar */}
|
||||
<div className='flex items-center justify-between mb-2'>
|
||||
<div className='flex flex-col items-start gap-3'>
|
||||
<span className='text-2xl font-extrabold text-[#4285F4]'>CPA Los Angeles</span>
|
||||
<span className='bg-[#f1f5f9] text-gray-700 px-3 py-1 rounded-md text-xs border font-medium'>Links: <b>Indexed 32%</b> • Interlinked <b>4%</b></span>
|
||||
|
||||
<span className='flex items-center gap-2 text-gray-800'>Content Publishing:
|
||||
<span className='bg-[#fee2e2] text-[#dc2626] px-2 py-0 rounded-md text-xs font-bold border'>Locked</span>
|
||||
</span>
|
||||
</div>
|
||||
<div className='flex items-center gap-3'>
|
||||
<BacklinkTypeDropdown />
|
||||
<select className='border px-3 py-1 rounded-lg text-xs bg-[#e3edfc] font-bold text-[#4285F4] hover:bg-[#4285F4] hover:text-white transition-colors'>
|
||||
<option>DA</option>
|
||||
<option>DR</option>
|
||||
<option>Traffic</option>
|
||||
</select>
|
||||
<select className='border px-3 py-1 rounded-lg text-xs bg-[#e3edfc] font-bold text-[#4285F4] hover:bg-[#4285F4] hover:text-white transition-colors'>
|
||||
<option>Traffic</option>
|
||||
<option>DA</option>
|
||||
<option>DR</option>
|
||||
</select>
|
||||
<button className='bg-[#e3edfc] text-[#4285F4] border px-4 py-1 rounded-lg text-xs font-bold hover:bg-[#4285F4] hover:text-white transition-colors'>Export</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Global Filter/Sort Header Row */}
|
||||
<div className='flex items-center justify-end gap-4 mb-2 px-4 py-3 '>
|
||||
<div className='flex items-center gap-8 text-sm font-medium text-gray-600'>
|
||||
<span className='flex items-center cursor-pointer hover:text-[#4285F4] transition-colors'>
|
||||
DR <svg className='ml-1 w-4 h-4' fill='currentColor' viewBox='0 0 20 20'>
|
||||
<path fillRule='evenodd' d='M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z' clipRule='evenodd' />
|
||||
</svg>
|
||||
</span>
|
||||
<span className='flex items-center cursor-pointer hover:text-[#4285F4] transition-colors'>
|
||||
DA <svg className='ml-1 w-4 h-4' fill='currentColor' viewBox='0 0 20 20'>
|
||||
<path fillRule='evenodd' d='M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z' clipRule='evenodd' />
|
||||
</svg>
|
||||
</span>
|
||||
<span className='flex items-center cursor-pointer hover:text-[#4285F4] transition-colors'>
|
||||
Traffic <svg className='ml-1 w-4 h-4' fill='currentColor' viewBox='0 0 20 20'>
|
||||
<path fillRule='evenodd' d='M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z' clipRule='evenodd' />
|
||||
</svg>
|
||||
</span>
|
||||
<span className='flex items-center cursor-pointer hover:text-[#4285F4] transition-colors'>
|
||||
Indexed <svg className='ml-1 w-4 h-4' fill='currentColor' viewBox='0 0 20 20'>
|
||||
<path fillRule='evenodd' d='M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z' clipRule='evenodd' />
|
||||
</svg>
|
||||
</span>
|
||||
<span className='flex items-center cursor-pointer hover:text-[#4285F4] transition-colors'>
|
||||
Interlinked <svg className='ml-1 w-4 h-4' fill='currentColor' viewBox='0 0 20 20'>
|
||||
<path fillRule='evenodd' d='M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z' clipRule='evenodd' />
|
||||
</svg>
|
||||
</span>
|
||||
<span className='flex items-center cursor-pointer hover:text-[#4285F4] transition-colors'>
|
||||
Date <svg className='ml-1 w-4 h-4' fill='currentColor' viewBox='0 0 20 20'>
|
||||
<path fillRule='evenodd' d='M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z' clipRule='evenodd' />
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Tables */}
|
||||
{categories.map((cat, idx) => (
|
||||
<div key={idx} className='mb-8 last:mb-0'>
|
||||
<div className={`px-4 py-3 rounded-lg font-bold text-sm ${cat.color} mb-3`}>
|
||||
<div className='flex items-center gap-3'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<span className='text-lg'>{cat.name === 'Social' ? '💬' : cat.name === 'Citation' ? '📊' : '🌐'}</span>
|
||||
<span>{cat.name}</span>
|
||||
</div>
|
||||
<span className='text-xs font-semibold text-gray-500 bg-white px-2 py-1 rounded-full'>{cat.rows.length}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className='bg-white rounded-xl border overflow-hidden shadow-sm'>
|
||||
<table className='w-full'>
|
||||
<tbody>
|
||||
{cat.rows.map((row, rIdx) => (
|
||||
<tr key={rIdx} className='border-b last:border-0 hover:bg-[#f1f7fe] transition-colors'>
|
||||
<td className='py-3 px-4 text-center w-12'>{row.icon}</td>
|
||||
<td className='py-3 px-4 w-full text-[#4285F4] hover:text-[#1e40af] cursor-pointer font-medium' title={row.title}>
|
||||
<div className='truncate max-w-[350px] whitespace-pre-wrap'>{row.title}</div>
|
||||
</td>
|
||||
<td className='py-3 px-4 text-center w-16 font-semibold'>{row.DR}</td>
|
||||
<td className='py-3 px-4 text-center w-16 font-semibold'>{row.DA}</td>
|
||||
<td className='py-3 px-4 text-center w-24 font-semibold'>{row.traffic}</td>
|
||||
<td className='py-3 px-4 text-center w-20'>
|
||||
<span className={`px-2 py-1 rounded-full text-xs ${pillClass(row.indexed)}`}>{row.indexed}</span>
|
||||
</td>
|
||||
<td className='py-3 px-4 text-center w-24'>
|
||||
<span className={`px-2 py-1 rounded-full text-xs ${pillClass(row.interlinked)}`}>{row.interlinked}</span>
|
||||
</td>
|
||||
<td className='py-3 px-4 text-center w-20 font-medium text-gray-600'>{row.date}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</section>
|
||||
</div>
|
||||
</ContainerPage>
|
||||
);
|
||||
}
|
||||
|
||||
export default Portfolio;
|
||||
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 { 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.png"
|
||||
import No from "../assets/icons/no.png"
|
||||
import Share2 from "../assets/icons/Share2.png"
|
||||
import MetricCard from '../components/shared/MetricCard';
|
||||
|
||||
const done = <div className='flex items-center gap-2 w-[100px]'> <CircleCheck className='w-6 h-6 text-[#fff] bg-green-500 rounded-full' /> <span className='text-green-500 font-bold text-md'>Done</span></div>
|
||||
|
||||
const progress = <div className='flex items-center gap-2 w-[100px]'>
|
||||
<img src={Progress} alt="" className='w-6 h-6' />
|
||||
<span className='text-blue-500 font-bold text-md'>In Progress</span></div>
|
||||
// Category table data
|
||||
|
||||
const nA = <div className='flex items-center gap-2'>
|
||||
<img src={No} alt="" className='w-6 h-6' />
|
||||
<span className='text-gray-500 font-bold text-md'>N/A</span></div>
|
||||
|
||||
const categories = [
|
||||
{
|
||||
name: 'Social',
|
||||
color: 'border-l-4 border-[#4285F4] bg-[#001EE21C]',
|
||||
rows: [
|
||||
{
|
||||
icon: Amazing, title: 'Questions to ask your CPA httpAmazon', DR: 90, DA: 97, traffic: '3,902,293',
|
||||
indexed: done,
|
||||
interlinked: done, date: '9/16/25'
|
||||
},
|
||||
{ icon: Amazing, title: 'Questions to ask your CPA Published on Amazon', DR: 90, DA: 97, traffic: '3,902,293', indexed: done, interlinked: done, date: '9/16/25' },
|
||||
{ icon: Amazing, title: 'Questions to ask your CPA Published on Amazon AWS', DR: 90, DA: 97, traffic: '3,902,293', indexed: progress, interlinked: nA, date: '9/16/25' },
|
||||
{ icon: Amazing, title: 'Pinterest DR 97 DA 95 Traffic 3,989,220', DR: 90, DA: 97, traffic: '3,902,293', indexed: done, interlinked: done, date: '9/16/25' },
|
||||
{ icon: Amazing, title: 'Questions to ask your CPA Substack DR 97 DA 95', DR: 90, DA: 97, traffic: '3,902,293', indexed: progress, interlinked: progress, date: '9/16/25' }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Citation',
|
||||
color: 'border-l-4 border-[#0ea5e9] bg-[#3C87FF2B]',
|
||||
rows: [
|
||||
{ icon: Amazing, title: 'Substack DR 97 DA 95', DR: 90, DA: 97, traffic: '3,902,293', indexed: done, interlinked: nA, date: '9/16/25' },
|
||||
{ icon: Amazing, title: 'Questions to ask your CPA Published on Amazon AWS', DR: 90, DA: 97, traffic: '3,902,293', indexed: done, interlinked: done, date: '9/16/25' },
|
||||
{ icon: Amazing, title: 'Paypal DR 97 DA 95 Traffic', DR: 90, DA: 97, traffic: '3,902,293', indexed: done, interlinked: done, date: '9/16/25' }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Web 2.0',
|
||||
color: 'border-l-4 border-[#22c55e] bg-[#f0fdf4]',
|
||||
rows: [
|
||||
{ icon: Amazing, title: 'Paypal DR 97 DA 95 Traffic', DR: 90, DA: 97, traffic: '3,902,293', indexed: progress, interlinked: done, date: '9/16/25' },
|
||||
{ icon: Amazing, title: 'Apple Music DR 97 DA 95 Traffic', DR: 90, DA: 97, traffic: '3,902,293', indexed: done, interlinked: nA, date: '9/16/25' },
|
||||
{ icon: Amazing, title: 'Substack DR 97 DA 95', DR: 90, DA: 97, traffic: '3,902,293', indexed: done, interlinked: done, date: '9/16/25' },
|
||||
{ icon: Amazing, title: 'Questions to ask your CPA Published on Amazon AWS', DR: 90, DA: 97, traffic: '3,902,293', indexed: progress, interlinked: done, date: '9/16/25' },
|
||||
{ icon: Amazing, title: 'Pin DR 97 DA 95 Traffic', DR: 90, DA: 97, traffic: '3,902,293', indexed: done, interlinked: progress, date: '9/16/25' }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const pillClass = (val: string) =>
|
||||
val === 'Done' ? 'bg-green-100 text-green-700 font-bold'
|
||||
: val === 'In Progress' ? 'bg-blue-100 text-blue-700 whitespace-nowrap font-bold'
|
||||
: 'bg-gray -100 text-gray-700 font-bold';
|
||||
|
||||
function Portfolio() {
|
||||
const [selectedKeyword, setSelectedKeyword] = useState("CPA Los Angeles")
|
||||
|
||||
|
||||
return (
|
||||
<ContainerPage>
|
||||
<HeaderPage title="Live Library" />
|
||||
<div className='flex gap-8 mx-auto'>
|
||||
|
||||
<RoadmapSidebar mode={"portfolio"} selectedKeyword={selectedKeyword} onKeywordSelect={setSelectedKeyword} />
|
||||
|
||||
{/* Table Section */}
|
||||
<section className='flex-1 bg-white border rounded-2xl shadow-sm px-6 py-4'>
|
||||
{/* Title Bar */}
|
||||
<div className='flex items-center justify-between mb-2'>
|
||||
<div className='flex flex-col items-start gap-2'>
|
||||
<span className='text-2xl font-extrabold text-[#4285F4]'>CPA Los Angeles</span>
|
||||
<span className='text-gray-500 rounded-md text-md font-medium'><span className='font-bold pr-3 text-black'>Links:</span>
|
||||
|
||||
Indexed <b className='font-bold bg-[#e2eff7] text-[#004A74] px-2 py-1 rounded-md mx-1 mr-5'>32%</b>
|
||||
|
||||
Interlinked <b className='font-bold bg-[#e2eff7] text-[#004A74] px-2 py-1 rounded-md mx-1'>4%</b></span>
|
||||
|
||||
<span className='flex items-center gap-2 text-gray-800 text-md'>Content Publishing:
|
||||
<span className='bg-[#f9eaef] text-[#8B0E3A] px-2 py-0 rounded-md font-bold border'>Locked</span>
|
||||
</span>
|
||||
</div>
|
||||
<div className='flex items-center gap-3'>
|
||||
<BacklinkTypeDropdown options={
|
||||
{
|
||||
Social: true, Citation: true, 'Web 2.0': true, Multimedia: false, Crowd: false
|
||||
}} title="Backlink Type" subTitle="Show rings:" />
|
||||
|
||||
<BacklinkTypeDropdown options={{ DA: true, DR: true, Traffic: true }} title="DA" subTitle="Show rings:" />
|
||||
|
||||
<BacklinkTypeDropdown options={{ DA: true, DR: true, Traffic: true }} title="DA" subTitle="Show rings:" />
|
||||
|
||||
<BacklinkTypeDropdown options={{ DA: true, DR: true, Traffic: true }} title="Traffic" subTitle="Show rings:" />
|
||||
|
||||
<button className='bg-[#fff] text-gray-500 border px-4 py-1 rounded-lg text-md font-bold transition-colors flex items-center gap-2'>
|
||||
<img src={Share2} alt="" />
|
||||
<span>Export</span>
|
||||
{/* <MetricCard label="Export" tooltipText="tooltipText" /> */}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Global Filter/Sort Header Row */}
|
||||
<div className='flex items-center justify-end gap-4 mb-2 px-4 py-3 '>
|
||||
<div className='flex items-center gap-8 text-sm font-medium text-gray-600'>
|
||||
<span className='flex items-center cursor-pointer hover:text-[#4285F4] transition-colors '>
|
||||
DR <SortIcon />
|
||||
</span>
|
||||
<span className='flex items-center cursor-pointer hover:text-[#4285F4] transition-colors'>
|
||||
DA <SortIcon />
|
||||
</span>
|
||||
<span className='flex items-center cursor-pointer hover:text-[#4285F4] transition-colors'>
|
||||
Traffic <SortIcon />
|
||||
</span>
|
||||
<span className='flex items-center cursor-pointer hover:text-[#4285F4] transition-colors'>
|
||||
Indexed <SortIcon />
|
||||
</span>
|
||||
<span className='flex items-center cursor-pointer hover:text-[#4285F4] transition-colors'>
|
||||
Interlinked <SortIcon />
|
||||
</span>
|
||||
<span className='flex items-center cursor-pointer hover:text-[#4285F4] transition-colors'>
|
||||
Date <SortIcon />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Tables */}
|
||||
{categories.map((cat, idx) => (
|
||||
<div key={idx} className='mb-8 last:mb-0'>
|
||||
<div className={`px-4 py-3 rounded-lg font-bold text-sm ${cat.color} mb-3`}>
|
||||
<div className='flex items-center gap-3'>
|
||||
<div className='flex items-center gap-2'>
|
||||
{/* <span className='text-lg'>{cat.name === 'Social' ? '💬' : cat.name === 'Citation' ? '📊' : '🌐'}</span> */}
|
||||
<span>{cat.name}</span>
|
||||
</div>
|
||||
<span className='text-md font-bold bg-white p-1 px-2 rounded-full'>{cat.rows.length}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className='bg-white rounded-xl border overflow-hidden shadow-sm'>
|
||||
<table className='w-full'>
|
||||
<tbody>
|
||||
{cat.rows.map((row, rIdx) => (
|
||||
<tr key={rIdx} className='border-b last:border-0 hover:bg-[#f1f7fe] transition-colors'>
|
||||
<td className='w-12 px-4 py-3'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<img src={row.icon} alt="" className='w-10 h-10 rounded-full' />
|
||||
<span className='text-xs font-bold w-[40px]'> </span>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td className='w-full text-[#4285F4] hover:text-[#1e40af] font-medium' title={row.title}>
|
||||
<div className='truncate max-w-[400px] whitespace-pre-wrap underline py-1 cursor-pointer'>{row.title}</div>
|
||||
</td>
|
||||
<td className='py-3 px-4 text-center w-20 font-semibold text-gray-700'>{row.DR}</td>
|
||||
<td className='py-3 px-4 text-center w-20 font-semibold text-gray-700'>{row.DA}</td>
|
||||
<td className='py-3 px-4 text-center w-24 font-semibold text-gray-700'>{row.traffic}</td>
|
||||
<td className='py-3 px-4 text-center '>
|
||||
<div className={`rounded-full text-xs ${pillClass(row.indexed.toString())}`}>{row.indexed}</div>
|
||||
{/* whitespace-nowrap */}
|
||||
</td>
|
||||
<td className='py-3 px-2 text-center w-28'>
|
||||
<span className={`rounded-full text-xs ${pillClass(row.interlinked.toString())}`}>{row.interlinked}</span>
|
||||
</td>
|
||||
<td className='py-3 px-4 text-center w-24 font-medium text-gray-700'>{row.date}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</section>
|
||||
</div>
|
||||
</ContainerPage>
|
||||
);
|
||||
}
|
||||
|
||||
export default Portfolio;
|
||||
|
||||
const SortIcon = () => {
|
||||
return (
|
||||
<svg width="14" height="15" viewBox="0 0 14 15" fill="none" xmlns="http://www.w3.org/2000/svg" className='ml-2'>
|
||||
<path d="M10.6944 9.46965C10.6944 9.58746 10.6464 9.70528 10.5744 9.79953L7.52593 13.004C7.38191 13.1689 7.11788 13.1925 6.94985 13.0511C6.92585 13.0276 6.92585 13.0276 6.90184 13.004L3.85342 9.77596C3.78141 9.70528 3.7334 9.58746 3.7334 9.46965C3.7334 9.23403 3.92543 9.02197 4.16546 8.99841H10.2623C10.5024 8.99841 10.6944 9.21047 10.6944 9.46965ZM10.6944 5.89049C10.6944 6.12611 10.5024 6.33817 10.2623 6.36173H4.16546C3.92543 6.36173 3.7334 6.14967 3.7334 5.89049C3.7334 5.77268 3.78141 5.65486 3.85342 5.56062L6.90184 2.35614C7.04587 2.1912 7.3099 2.16764 7.47793 2.30902C7.50193 2.33258 7.50193 2.33258 7.52593 2.35614L10.5744 5.58418C10.6464 5.65486 10.6944 5.77268 10.6944 5.89049Z" fill="#999BAD" />
|
||||
</svg>
|
||||
|
||||
)
|
||||
}
|
||||
@@ -1,217 +1,217 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
const Settings = () => {
|
||||
const [activeSection, setActiveSection] = useState("profile");
|
||||
const [activeSubSection, setActiveSubSection] = useState("payment-history");
|
||||
|
||||
const sidebarItems = [
|
||||
{
|
||||
id: "profile",
|
||||
label: "Profile",
|
||||
icon: (
|
||||
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clipRule="evenodd" />
|
||||
</svg>
|
||||
),
|
||||
hasSubItems: false
|
||||
},
|
||||
{
|
||||
id: "campaigns",
|
||||
label: "Campaigns",
|
||||
icon: (
|
||||
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z" clipRule="evenodd" />
|
||||
</svg>
|
||||
),
|
||||
hasSubItems: false
|
||||
},
|
||||
{
|
||||
id: "billing",
|
||||
label: "Billing",
|
||||
icon: (
|
||||
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M4 4a2 2 0 00-2 2v1h16V6a2 2 0 00-2-2H4z" />
|
||||
<path fillRule="evenodd" d="M18 9H2v5a2 2 0 002 2h12a2 2 0 002-2V9zM4 13a1 1 0 011-1h1a1 1 0 110 2H5a1 1 0 01-1-1zm5-1a1 1 0 100 2h1a1 1 0 100-2H9z" clipRule="evenodd" />
|
||||
</svg>
|
||||
),
|
||||
hasSubItems: true,
|
||||
subItems: [
|
||||
"Billing info",
|
||||
"Update",
|
||||
"Monthly Engagement",
|
||||
"Billing Date",
|
||||
"Payment History",
|
||||
"Cancel Membership"
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "help-desk",
|
||||
label: "Help Desk",
|
||||
icon: (
|
||||
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-3a1 1 0 00-.867.5 1 1 0 11-1.731-1A3 3 0 0113 8a3.001 3.001 0 01-2 2.83V11a1 1 0 11-2 0v-1a1 1 0 011-1 1 1 0 100-2zm0 8a1 1 0 100-2 1 1 0 000 2z" clipRule="evenodd" />
|
||||
</svg>
|
||||
),
|
||||
hasSubItems: false
|
||||
},
|
||||
{
|
||||
id: "feature-request",
|
||||
label: "Feature Request",
|
||||
icon: (
|
||||
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M18 13V5a2 2 0 00-2-2H4a2 2 0 00-2 2v8a2 2 0 002 2h3l3 3 3-3h3a2 2 0 002-2zM5 7a1 1 0 011-1h8a1 1 0 110 2H6a1 1 0 01-1-1zm1 3a1 1 0 100 2h3a1 1 0 100-2H6z" clipRule="evenodd" />
|
||||
</svg>
|
||||
),
|
||||
hasSubItems: false
|
||||
}
|
||||
];
|
||||
|
||||
const handleSectionClick = (sectionId: string) => {
|
||||
setActiveSection(sectionId);
|
||||
if (sectionId === "billing") {
|
||||
setActiveSubSection("payment-history");
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubSectionClick = (subSection: string) => {
|
||||
setActiveSubSection(subSection);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto px-6 py-8">
|
||||
<div className="flex gap-8">
|
||||
{/* Sidebar */}
|
||||
<div className="w-64 flex-shrink-0">
|
||||
<h1 className="text-3xl font-bold text-gray-900 mb-8">Settings</h1>
|
||||
|
||||
<nav className="space-y-2">
|
||||
{sidebarItems.map((item) => (
|
||||
<div key={item.id}>
|
||||
<button
|
||||
onClick={() => handleSectionClick(item.id)}
|
||||
className={`w-full flex items-center space-x-3 px-4 py-3 rounded-lg text-left transition-colors ${
|
||||
activeSection === item.id
|
||||
? "bg-blue-50 text-blue-600"
|
||||
: "text-gray-700 hover:bg-gray-50"
|
||||
}`}
|
||||
>
|
||||
{item.icon}
|
||||
<span className="font-medium">{item.label}</span>
|
||||
{item.hasSubItems && (
|
||||
<svg className="w-4 h-4 ml-auto" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clipRule="evenodd" />
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
|
||||
{item.hasSubItems && activeSection === item.id && (
|
||||
<div className="ml-8 mt-2 space-y-1">
|
||||
{item.subItems?.map((subItem) => (
|
||||
<button
|
||||
key={subItem}
|
||||
onClick={() => handleSubSectionClick(subItem.toLowerCase().replace(/\s+/g, "-"))}
|
||||
className={`w-full flex items-center px-4 py-2 rounded-lg text-left transition-colors ${
|
||||
activeSubSection === subItem.toLowerCase().replace(/\s+/g, "-")
|
||||
? "bg-blue-600 text-white"
|
||||
: "text-gray-600 hover:bg-gray-50"
|
||||
}`}
|
||||
>
|
||||
{activeSubSection === subItem.toLowerCase().replace(/\s+/g, "-") && (
|
||||
<div className="w-1 h-6 bg-blue-600 rounded-full mr-3"></div>
|
||||
)}
|
||||
<span className="text-sm">{subItem}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="flex-1">
|
||||
<div className="bg-white">
|
||||
<h2 className="text-3xl font-bold text-gray-900 mb-2">Basic Information</h2>
|
||||
<p className="text-gray-600 mb-8">
|
||||
View and update your personal details and account information.
|
||||
</p>
|
||||
|
||||
<form className="space-y-6">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Name
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
defaultValue="Eugiene Jonathan"
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Email
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
defaultValue="Eugiene Jonathan"
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Password
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
defaultValue="Eugiene Jonathan"
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Member Since
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
defaultValue="12 Aug 2025"
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<span>Total Keywords Campaigns</span>
|
||||
<svg className="w-4 h-4 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-3a1 1 0 00-.867.5 1 1 0 11-1.731-1A3 3 0 0113 8a3.001 3.001 0 01-2 2.83V11a1 1 0 11-2 0v-1a1 1 0 011-1 1 1 0 100-2zm0 8a1 1 0 100-2 1 1 0 000 2z" clipRule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
defaultValue="234,029"
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<button
|
||||
type="submit"
|
||||
className="bg-blue-600 text-white px-6 py-3 rounded-lg hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
import React, { useState } from 'react';
|
||||
|
||||
const Settings = () => {
|
||||
const [activeSection, setActiveSection] = useState("profile");
|
||||
const [activeSubSection, setActiveSubSection] = useState("payment-history");
|
||||
|
||||
const sidebarItems = [
|
||||
{
|
||||
id: "profile",
|
||||
label: "Profile",
|
||||
icon: (
|
||||
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clipRule="evenodd" />
|
||||
</svg>
|
||||
),
|
||||
hasSubItems: false
|
||||
},
|
||||
{
|
||||
id: "campaigns",
|
||||
label: "Campaigns",
|
||||
icon: (
|
||||
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z" clipRule="evenodd" />
|
||||
</svg>
|
||||
),
|
||||
hasSubItems: false
|
||||
},
|
||||
{
|
||||
id: "billing",
|
||||
label: "Billing",
|
||||
icon: (
|
||||
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M4 4a2 2 0 00-2 2v1h16V6a2 2 0 00-2-2H4z" />
|
||||
<path fillRule="evenodd" d="M18 9H2v5a2 2 0 002 2h12a2 2 0 002-2V9zM4 13a1 1 0 011-1h1a1 1 0 110 2H5a1 1 0 01-1-1zm5-1a1 1 0 100 2h1a1 1 0 100-2H9z" clipRule="evenodd" />
|
||||
</svg>
|
||||
),
|
||||
hasSubItems: true,
|
||||
subItems: [
|
||||
"Billing info",
|
||||
"Update",
|
||||
"Monthly Engagement",
|
||||
"Billing Date",
|
||||
"Payment History",
|
||||
"Cancel Membership"
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "help-desk",
|
||||
label: "Help Desk",
|
||||
icon: (
|
||||
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-3a1 1 0 00-.867.5 1 1 0 11-1.731-1A3 3 0 0113 8a3.001 3.001 0 01-2 2.83V11a1 1 0 11-2 0v-1a1 1 0 011-1 1 1 0 100-2zm0 8a1 1 0 100-2 1 1 0 000 2z" clipRule="evenodd" />
|
||||
</svg>
|
||||
),
|
||||
hasSubItems: false
|
||||
},
|
||||
{
|
||||
id: "feature-request",
|
||||
label: "Feature Request",
|
||||
icon: (
|
||||
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M18 13V5a2 2 0 00-2-2H4a2 2 0 00-2 2v8a2 2 0 002 2h3l3 3 3-3h3a2 2 0 002-2zM5 7a1 1 0 011-1h8a1 1 0 110 2H6a1 1 0 01-1-1zm1 3a1 1 0 100 2h3a1 1 0 100-2H6z" clipRule="evenodd" />
|
||||
</svg>
|
||||
),
|
||||
hasSubItems: false
|
||||
}
|
||||
];
|
||||
|
||||
const handleSectionClick = (sectionId: string) => {
|
||||
setActiveSection(sectionId);
|
||||
if (sectionId === "billing") {
|
||||
setActiveSubSection("payment-history");
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubSectionClick = (subSection: string) => {
|
||||
setActiveSubSection(subSection);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto px-6 py-8">
|
||||
<div className="flex gap-8">
|
||||
{/* Sidebar */}
|
||||
<div className="w-64 flex-shrink-0">
|
||||
<h1 className="text-3xl font-bold text-gray-900 mb-8">Settings</h1>
|
||||
|
||||
<nav className="space-y-2">
|
||||
{sidebarItems.map((item) => (
|
||||
<div key={item.id}>
|
||||
<button
|
||||
onClick={() => handleSectionClick(item.id)}
|
||||
className={`w-full flex items-center space-x-3 px-4 py-3 rounded-lg text-left transition-colors ${
|
||||
activeSection === item.id
|
||||
? "bg-blue-50 text-blue-600"
|
||||
: "text-gray-700 hover:bg-gray-50"
|
||||
}`}
|
||||
>
|
||||
{item.icon}
|
||||
<span className="font-medium">{item.label}</span>
|
||||
{item.hasSubItems && (
|
||||
<svg className="w-4 h-4 ml-auto" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clipRule="evenodd" />
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
|
||||
{item.hasSubItems && activeSection === item.id && (
|
||||
<div className="ml-8 mt-2 space-y-1">
|
||||
{item.subItems?.map((subItem) => (
|
||||
<button
|
||||
key={subItem}
|
||||
onClick={() => handleSubSectionClick(subItem.toLowerCase().replace(/\s+/g, "-"))}
|
||||
className={`w-full flex items-center px-4 py-2 rounded-lg text-left transition-colors ${
|
||||
activeSubSection === subItem.toLowerCase().replace(/\s+/g, "-")
|
||||
? "bg-blue-600 text-white"
|
||||
: "text-gray-600 hover:bg-gray-50"
|
||||
}`}
|
||||
>
|
||||
{activeSubSection === subItem.toLowerCase().replace(/\s+/g, "-") && (
|
||||
<div className="w-1 h-6 bg-blue-600 rounded-full mr-3"></div>
|
||||
)}
|
||||
<span className="text-sm">{subItem}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="flex-1">
|
||||
<div className="bg-white">
|
||||
<h2 className="text-3xl font-bold text-gray-900 mb-2">Basic Information</h2>
|
||||
<p className="text-gray-600 mb-8">
|
||||
View and update your personal details and account information.
|
||||
</p>
|
||||
|
||||
<form className="space-y-6">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Name
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
defaultValue="Eugiene Jonathan"
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Email
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
defaultValue="Eugiene Jonathan"
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Password
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
defaultValue="Eugiene Jonathan"
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Member Since
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
defaultValue="12 Aug 2025"
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<span>Total Keywords Campaigns</span>
|
||||
<svg className="w-4 h-4 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-3a1 1 0 00-.867.5 1 1 0 11-1.731-1A3 3 0 0113 8a3.001 3.001 0 01-2 2.83V11a1 1 0 11-2 0v-1a1 1 0 011-1 1 1 0 100-2zm0 8a1 1 0 100-2 1 1 0 000 2z" clipRule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
defaultValue="234,029"
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<button
|
||||
type="submit"
|
||||
className="bg-blue-600 text-white px-6 py-3 rounded-lg hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Settings;
|
||||
@@ -1,47 +1,44 @@
|
||||
"use client"
|
||||
import { useState } from "react"
|
||||
// import { Search, MessageSquare } from "lucide-react"
|
||||
import IntegratedStrategy from "../components/IntegratedStrategy"
|
||||
import Vector from "../assets/icons/Vector.png"
|
||||
import Vector2 from "../assets/icons/Vector2.png"
|
||||
import ShowDetails from "../components/Strategy/ShowDetails"
|
||||
import ProgressMetrics from "../components/Strategy/ProgressMetrics"
|
||||
import RoadmapSidebar from "../components/Strategy/RoadmapSidebar"
|
||||
import HeaderPage from "../components/shared/HeaderPage"
|
||||
import ContainerPage from "../components/shared/ContainerPage"
|
||||
|
||||
|
||||
|
||||
|
||||
// Main integrated dashboard component
|
||||
function Strategy() {
|
||||
const [selectedKeyword, setSelectedKeyword] = useState("CPA Los Angeles")
|
||||
|
||||
return (
|
||||
<ContainerPage>
|
||||
<HeaderPage title="Strategy" />
|
||||
<div className="flex bg-white h-[100%]">
|
||||
|
||||
{/* <RoadmapSidebar selectedKeyword={selectedKeyword} onKeywordSelect={setSelectedKeyword} /> */}
|
||||
|
||||
<RoadmapSidebar selectedKeyword={selectedKeyword} onKeywordSelect={setSelectedKeyword} />
|
||||
|
||||
<div className="flex-1 px-4">
|
||||
<ProgressMetrics />
|
||||
|
||||
<div className="bg-white rounded-lg border border-gray-200 p-3 pb-10">
|
||||
|
||||
{
|
||||
selectedKeyword === "CPA Los Angeles" ? <IntegratedStrategy /> : <ShowDetails />
|
||||
}
|
||||
{/* <IntegratedStrategy /> */}
|
||||
{/* <ShowDetails /> */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</ContainerPage>
|
||||
)
|
||||
}
|
||||
|
||||
export default Strategy
|
||||
"use client"
|
||||
import { useState } from "react"
|
||||
// import { Search, MessageSquare } from "lucide-react"
|
||||
import IntegratedStrategy from "../components/IntegratedStrategy"
|
||||
import Vector from "../assets/icons/Vector.png"
|
||||
import Vector2 from "../assets/icons/Vector2.png"
|
||||
import ShowDetails from "../components/Strategy/ShowDetails"
|
||||
import ProgressMetrics from "../components/Strategy/ProgressMetrics"
|
||||
import RoadmapSidebar from "../components/Strategy/RoadmapSidebar"
|
||||
import HeaderPage from "../components/shared/HeaderPage"
|
||||
import ContainerPage from "../components/shared/ContainerPage"
|
||||
|
||||
|
||||
|
||||
|
||||
// Main integrated dashboard component
|
||||
function Strategy() {
|
||||
const [selectedKeyword, setSelectedKeyword] = useState("CPA Los Angeles")
|
||||
|
||||
return (
|
||||
<ContainerPage>
|
||||
<HeaderPage title="Strategy" />
|
||||
<div className="flex lg:flex-row flex-col items-center lg:items-start bg-white h-[100%]">
|
||||
|
||||
{/* <RoadmapSidebar selectedKeyword={selectedKeyword} onKeywordSelect={setSelectedKeyword} /> */}
|
||||
|
||||
<RoadmapSidebar selectedKeyword={selectedKeyword} onKeywordSelect={setSelectedKeyword} />
|
||||
|
||||
<div className="flex-1 px-4">
|
||||
<ProgressMetrics />
|
||||
|
||||
<div className="bg-white rounded-lg border border-gray-200 p-3 pb-10">
|
||||
|
||||
{selectedKeyword === "CPA Los Angeles" ? <IntegratedStrategy /> : <ShowDetails />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</ContainerPage>
|
||||
)
|
||||
}
|
||||
|
||||
export default Strategy
|
||||
|
||||
668
src/pages/login.tsx
Normal file
@@ -0,0 +1,668 @@
|
||||
import { useState } from 'react';
|
||||
// Removed: import { useRouter } from 'next/navigation'; // Next.js specific
|
||||
// Removed: import { motion } from 'framer-motion'; // External library not available
|
||||
|
||||
export default function Login() {
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
// Replaced useRouter with a simple object for basic navigation
|
||||
const router = {
|
||||
push: (path: string) => {
|
||||
window.location.href = path; // Simulating navigation for a generic React environment
|
||||
},
|
||||
};
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
// Removed unused state: const [isHovering, setIsHovering] = useState(false);
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setIsLoading(true);
|
||||
setError('');
|
||||
try {
|
||||
const response = await fetch('https://solidpoint-auth.ghaymah.systems/signin/email-password', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ email, password }),
|
||||
});
|
||||
const data = await response.json();
|
||||
if (response.ok) {
|
||||
// استخراج البيانات المهمة من الاستجابة
|
||||
const accessToken = data.session?.accessToken;
|
||||
const refreshToken = data.session?.refreshToken;
|
||||
const userId = data.session?.user?.id;
|
||||
|
||||
// تخزين بيانات المستخدم في localStorage
|
||||
const userData = {
|
||||
email,
|
||||
accessToken,
|
||||
userId,
|
||||
isLoggedIn: true,
|
||||
lastLogin: new Date().toISOString()
|
||||
};
|
||||
|
||||
localStorage.setItem('sp_user', JSON.stringify(userData));
|
||||
localStorage.setItem('user_id', userId); // تخزين user_id بشكل منفصل للمكونات التي تحتاجه
|
||||
window.dispatchEvent(new Event('userChanged'));
|
||||
|
||||
// تخزين الـ tokens في HttpOnly cookies
|
||||
try {
|
||||
const tokenResponse = await fetch('/api/auth/store-tokens', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({
|
||||
access_token: accessToken,
|
||||
refresh_token: refreshToken,
|
||||
expires_in: 3600 // مدة صلاحية الـ token بالثواني (ساعة واحدة)
|
||||
})
|
||||
});
|
||||
|
||||
if (!tokenResponse.ok) {
|
||||
console.error('Failed to store token in HttpOnly cookie');
|
||||
} else {
|
||||
console.log('Token stored successfully in HttpOnly cookie');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error storing token in HttpOnly cookie:', error);
|
||||
}
|
||||
|
||||
// Show success animation before navigating
|
||||
const successElement = document.createElement('div');
|
||||
successElement.className = 'fixed inset-0 flex items-center justify-center bg-white/80 dark:bg-gray-900/80 backdrop-blur-sm z-50';
|
||||
successElement.innerHTML = `
|
||||
<div class="text-center p-8 bg-white dark:bg-gray-800 rounded-lg shadow-lg">
|
||||
<svg class="w-16 h-16 mx-auto text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||||
</svg>
|
||||
<h2 class="mt-3 text-xl font-bold text-gray-900 dark:text-white">Welcome back!</h2>
|
||||
<p class="mt-2 text-gray-600 dark:text-gray-300">You have successfully logged in.</p>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(successElement);
|
||||
|
||||
// Add animation with CSS
|
||||
const checkmark = successElement.querySelector('svg');
|
||||
if (checkmark) {
|
||||
checkmark.style.transform = 'scale(0)';
|
||||
checkmark.style.transition = 'transform 0.5s ease-out';
|
||||
setTimeout(() => {
|
||||
checkmark.style.transform = 'scale(1)';
|
||||
}, 100);
|
||||
}
|
||||
|
||||
// Navigate after animation completes
|
||||
setTimeout(() => {
|
||||
// Remove the success element before navigating
|
||||
document.body.removeChild(successElement);
|
||||
router.push('/');
|
||||
}, 1800);
|
||||
} else {
|
||||
setError(data?.message || 'Failed to login. Please check your credentials.');
|
||||
}
|
||||
} catch (err: any) {
|
||||
setError('Failed to login. Please try again.');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmitSignUp = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setIsLoading(true);
|
||||
setError('');
|
||||
|
||||
try {
|
||||
const response = await fetch('https://solidpoint-auth.ghaymah.systems/signup/email-password', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ email, password }), // Add more fields like name if needed
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
console.log(data);
|
||||
|
||||
if (response.ok) {
|
||||
const accessToken = data.session?.accessToken;
|
||||
const refreshToken = data.session?.refreshToken;
|
||||
const userId = data.session?.user?.id;
|
||||
|
||||
const userData = {
|
||||
email,
|
||||
accessToken,
|
||||
userId,
|
||||
isLoggedIn: true,
|
||||
lastLogin: new Date().toISOString()
|
||||
};
|
||||
|
||||
localStorage.setItem('sp_user', JSON.stringify(userData));
|
||||
localStorage.setItem('user_id', userId);
|
||||
window.dispatchEvent(new Event('userChanged'));
|
||||
|
||||
// Store tokens in HttpOnly cookies
|
||||
try {
|
||||
const tokenResponse = await fetch('/api/auth/store-tokens', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({
|
||||
access_token: accessToken,
|
||||
refresh_token: refreshToken,
|
||||
expires_in: 3600
|
||||
})
|
||||
});
|
||||
|
||||
if (!tokenResponse.ok) {
|
||||
console.error('Failed to store token in HttpOnly cookie');
|
||||
} else {
|
||||
console.log('Token stored successfully in HttpOnly cookie');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error storing token in HttpOnly cookie:', error);
|
||||
}
|
||||
|
||||
// Success animation
|
||||
const successElement = document.createElement('div');
|
||||
successElement.className = 'fixed inset-0 flex items-center justify-center bg-white/80 dark:bg-gray-900/80 backdrop-blur-sm z-50';
|
||||
successElement.innerHTML = `
|
||||
<div class="text-center p-8 bg-white dark:bg-gray-800 rounded-lg shadow-lg">
|
||||
<svg class="w-16 h-16 mx-auto text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||||
</svg>
|
||||
<h2 class="mt-3 text-xl font-bold text-gray-900 dark:text-white">Account Created!</h2>
|
||||
<p class="mt-2 text-gray-600 dark:text-gray-300">You have successfully signed up.</p>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(successElement);
|
||||
|
||||
const checkmark = successElement.querySelector('svg');
|
||||
if (checkmark) {
|
||||
checkmark.style.transform = 'scale(0)';
|
||||
checkmark.style.transition = 'transform 0.5s ease-out';
|
||||
setTimeout(() => {
|
||||
checkmark.style.transform = 'scale(1)';
|
||||
}, 100);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(successElement);
|
||||
router.push('/');
|
||||
}, 1800);
|
||||
} else {
|
||||
setError(data?.message || 'Signup failed. Please check your info.');
|
||||
}
|
||||
} catch (err: any) {
|
||||
setError('Signup failed. Please try again.');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const [swapAuth, setSwapAuth] = useState(false);
|
||||
|
||||
const swapAuthFunction = () => {
|
||||
setSwapAuth(!swapAuth);
|
||||
setEmail('');
|
||||
setPassword('');
|
||||
setError('');
|
||||
}
|
||||
|
||||
const handleSubmitForgotPassword = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setIsLoading(true);
|
||||
setError('');
|
||||
// setMessage('');
|
||||
|
||||
try {
|
||||
const res = await fetch('https://solidpoint-auth.ghaymah.systems/user/password/reset', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email }),
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
// setMessage('If the email exists, a reset link has been sent.');
|
||||
console.log('If the email exists, a reset link has been sent.');
|
||||
} else {
|
||||
const data = await res.json();
|
||||
setError(data.message || 'Failed to send reset email.');
|
||||
}
|
||||
} catch {
|
||||
setError('Failed to send reset email. Try again later.');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const [forgotPassword, setForgotPassword] = useState(false);
|
||||
|
||||
|
||||
if (forgotPassword) {
|
||||
return (
|
||||
<div className="min-h-screen w-full bg-gradient-to-br from-blue-50 via-indigo-50 to-purple-50 dark:from-gray-800 dark:via-indigo-900 dark:to-purple-900 font-inter relative overflow-hidden">
|
||||
<div className="absolute top-0 left-0 w-full h-full overflow-hidden z-0 opacity-20 dark:opacity-10">
|
||||
<div className="absolute top-10 left-10 w-72 h-72 bg-blue-400 dark:bg-blue-600 rounded-full mix-blend-multiply filter blur-2xl"></div>
|
||||
<div className="absolute top-0 right-10 w-72 h-72 bg-purple-400 dark:bg-purple-600 rounded-full mix-blend-multiply filter blur-2xl"></div>
|
||||
<div className="absolute -bottom-8 left-20 w-72 h-72 bg-indigo-400 dark:bg-indigo-600 rounded-full mix-blend-multiply filter blur-2xl"></div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-[40%_60%] min-h-screen gap-0 md:gap-4 relative z-10">
|
||||
|
||||
<div className="flex flex-col justify-center px-8 sm:px-12 py-12 bg-white/80 dark:bg-gray-800/90 backdrop-blur-md shadow-lg rounded-xl border border-white/20 dark:border-gray-700/30 transition-all duration-200">
|
||||
{/* Logo */}
|
||||
<div className="flex items-center justify-center mb-6">
|
||||
<div className="relative flex items-center justify-center h-12 w-12">
|
||||
<div className="absolute w-11 h-11 rounded-full border-[1.5px] border-blue-500/40"></div>
|
||||
<div className="absolute w-10 h-10 rounded-full border-2 border-blue-600/70"></div>
|
||||
<div className="absolute w-10 h-10 rounded-full bg-gradient-to-r from-blue-500/20 to-indigo-600/20 blur-md"></div>
|
||||
<div className="absolute w-9 h-9 rounded-full bg-gradient-to-tr from-blue-500/30 to-indigo-600/5 blur-[6px]"></div>
|
||||
<div className="absolute w-8 h-8 rounded-full border-[1px] border-blue-600/60 shadow-[0_0_12px_rgba(59,130,246,0.4)] shadow-[0_0_18px_rgba(79,70,229,0.5)]"></div>
|
||||
</div>
|
||||
<span className="ml-3 text-2xl font-bold bg-gradient-to-r from-blue-500 to-indigo-600 bg-clip-text text-transparent">SolidPoint</span>
|
||||
</div>
|
||||
|
||||
<h1 className="text-3xl font-bold mb-4 bg-gradient-to-r from-blue-600 to-indigo-700 bg-clip-text text-transparent text-center">
|
||||
Welcome Back!
|
||||
</h1>
|
||||
<p className="text-sm mb-6 text-gray-600 dark:text-gray-300 text-center">
|
||||
Please enter your credentials to access your account.
|
||||
</p>
|
||||
|
||||
{error && (
|
||||
<div
|
||||
className="p-3 mb-4 text-red-700 bg-red-100 dark:bg-red-900/30 dark:text-red-300 rounded-lg border border-red-200 dark:border-red-800 flex items-center space-x-2 text-sm"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<span>{error}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleSubmitForgotPassword} className="space-y-4 mt-4">
|
||||
{/* <form onSubmit={swapAuth ? handleSubmitSignUp : forgotPassword ? handleSubmitForgotPassword : handleSubmit} className="space-y-4 mt-4"> */}
|
||||
<div className="relative">
|
||||
<label htmlFor="email" className="block text-sm font-medium mb-1">Email Address</label>
|
||||
<div className="relative">
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
|
||||
</svg>
|
||||
</div>
|
||||
<input
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
required
|
||||
className="w-full pl-10 pr-3 py-2.5 rounded-lg border border-gray-300 dark:border-gray-600 bg-white/50 dark:bg-gray-800/50 backdrop-blur-sm focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm transition-all duration-300 hover:border-blue-300 dark:hover:border-blue-500 text-white"
|
||||
placeholder="your.email@example.com"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isLoading}
|
||||
className="w-full py-3 px-4 bg-gradient-to-r from-blue-600 to-indigo-700 hover:from-blue-700 hover:to-indigo-800 text-white font-medium rounded-lg shadow-md transition-all duration-200 disabled:opacity-70 text-sm flex items-center justify-center space-x-2"
|
||||
>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<svg className="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
<span>
|
||||
{
|
||||
swapAuth ?
|
||||
"Signing up..."
|
||||
:
|
||||
"Logging in..."
|
||||
}
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span>Get Password</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M14 5l7 7m0 0l-7 7m7-7H3" />
|
||||
</svg>
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
|
||||
<div className="relative my-6">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<div className="w-full border-t border-gray-300 dark:border-gray-700"></div>
|
||||
</div>
|
||||
<div className="relative flex justify-center text-sm">
|
||||
<span className="px-2 bg-white/80 dark:bg-gray-800/80 backdrop-blur-sm text-gray-500 dark:text-gray-400 rounded-md">
|
||||
remember your password?
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="w-full flex items-center justify-center py-2.5 px-4 border border-gray-300 dark:border-gray-600 rounded-lg shadow-sm bg-white/70 dark:bg-gray-800/70 backdrop-blur-sm hover:bg-gray-50 dark:hover:bg-gray-700 transition-all duration-200"
|
||||
onClick={() => setForgotPassword(false)}
|
||||
>
|
||||
<svg className="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 16l-4-4m0 0l4-4m-4 4h14m-5 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h7a3 3 0 013 3v1" />
|
||||
</svg>
|
||||
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Back to Login
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<RightSide />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
// Replaced motion.div with standard div and adopted SignUp's overall layout
|
||||
<div className="min-h-screen w-full bg-gradient-to-br from-blue-50 via-indigo-50 to-purple-50 dark:from-gray-800 dark:via-indigo-900 dark:to-purple-900 font-inter relative overflow-hidden">
|
||||
{/* Background decorative elements - optimized for performance */}
|
||||
<div className="absolute top-0 left-0 w-full h-full overflow-hidden z-0 opacity-20 dark:opacity-10">
|
||||
<div className="absolute top-10 left-10 w-72 h-72 bg-blue-400 dark:bg-blue-600 rounded-full mix-blend-multiply filter blur-2xl"></div>
|
||||
<div className="absolute top-0 right-10 w-72 h-72 bg-purple-400 dark:bg-purple-600 rounded-full mix-blend-multiply filter blur-2xl"></div>
|
||||
<div className="absolute -bottom-8 left-20 w-72 h-72 bg-indigo-400 dark:bg-indigo-600 rounded-full mix-blend-multiply filter blur-2xl"></div>
|
||||
</div>
|
||||
|
||||
{/* Page split with 35%/65% ratio and responsiveness */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-[40%_60%] min-h-screen gap-0 md:gap-4 relative z-10">
|
||||
{/* Left side - Login Form (aligned with SignUp's form side) */}
|
||||
<div className="flex flex-col justify-center px-8 sm:px-12 py-12 bg-white/80 dark:bg-gray-800/90 backdrop-blur-md shadow-lg rounded-xl border border-white/20 dark:border-gray-700/30 transition-all duration-200">
|
||||
{/* Logo */}
|
||||
<div className="flex items-center justify-center mb-6">
|
||||
<div className="relative flex items-center justify-center h-12 w-12">
|
||||
<div className="absolute w-11 h-11 rounded-full border-[1.5px] border-blue-500/40"></div>
|
||||
<div className="absolute w-10 h-10 rounded-full border-2 border-blue-600/70"></div>
|
||||
<div className="absolute w-10 h-10 rounded-full bg-gradient-to-r from-blue-500/20 to-indigo-600/20 blur-md"></div>
|
||||
<div className="absolute w-9 h-9 rounded-full bg-gradient-to-tr from-blue-500/30 to-indigo-600/5 blur-[6px]"></div>
|
||||
<div className="absolute w-8 h-8 rounded-full border-[1px] border-blue-600/60 shadow-[0_0_12px_rgba(59,130,246,0.4)] shadow-[0_0_18px_rgba(79,70,229,0.5)]"></div>
|
||||
</div>
|
||||
<span className="ml-3 text-2xl font-bold bg-gradient-to-r from-blue-500 to-indigo-600 bg-clip-text text-transparent">SolidPoint</span>
|
||||
</div>
|
||||
|
||||
<h1 className="text-3xl font-bold mb-4 bg-gradient-to-r from-blue-600 to-indigo-700 bg-clip-text text-transparent text-center">
|
||||
Welcome Back!
|
||||
</h1>
|
||||
<p className="text-sm mb-6 text-gray-600 dark:text-gray-300 text-center">
|
||||
Please enter your credentials to access your account.
|
||||
</p>
|
||||
|
||||
{error && (
|
||||
<div
|
||||
className="p-3 mb-4 text-red-700 bg-red-100 dark:bg-red-900/30 dark:text-red-300 rounded-lg border border-red-200 dark:border-red-800 flex items-center space-x-2 text-sm"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<span>{error}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form onSubmit={swapAuth ? handleSubmitSignUp : handleSubmit} className="space-y-4 mt-4">
|
||||
<div className="relative">
|
||||
<label htmlFor="email" className="block text-sm font-medium mb-1">Email Address</label>
|
||||
<div className="relative">
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
|
||||
</svg>
|
||||
</div>
|
||||
<input
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
required
|
||||
className="w-full pl-10 pr-3 py-2.5 rounded-lg border border-gray-300 dark:border-gray-600 bg-white/50 dark:bg-gray-800/50 backdrop-blur-sm focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm transition-all duration-300 hover:border-blue-300 dark:hover:border-blue-500 text-white"
|
||||
placeholder="your.email@example.com"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative">
|
||||
<label htmlFor="password" className="block text-sm font-medium mb-1">Password</label>
|
||||
<div className="relative">
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
|
||||
</svg>
|
||||
</div>
|
||||
<input
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
required
|
||||
className="w-full pl-10 pr-3 py-2.5 rounded-lg border border-gray-300 dark:border-gray-600 bg-white/50 dark:bg-gray-800/50 backdrop-blur-sm focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm transition-all duration-300 hover:border-blue-300 dark:hover:border-blue-500 text-white"
|
||||
placeholder="••••••••"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-400"
|
||||
onClick={() => {
|
||||
const input = document.getElementById('password') as HTMLInputElement;
|
||||
input.type = input.type === 'password' ? 'text' : 'password';
|
||||
}}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
!forgotPassword && (
|
||||
<div style={{ margin: '10px auto 5px', color: 'blue', cursor: 'pointer', width: '100%', textAlign: 'center' }} onClick={() => setForgotPassword(!forgotPassword)}>
|
||||
Forgot password?
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isLoading}
|
||||
className="w-full py-3 px-4 bg-gradient-to-r from-blue-600 to-indigo-700 hover:from-blue-700 hover:to-indigo-800 text-white font-medium rounded-lg shadow-md transition-all duration-200 disabled:opacity-70 text-sm flex items-center justify-center space-x-2"
|
||||
>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<svg className="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
<span>
|
||||
{
|
||||
swapAuth ?
|
||||
"Signing up..."
|
||||
:
|
||||
"Logging in..."
|
||||
}
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span>{swapAuth ? "Sign Up" : "Login"}</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M14 5l7 7m0 0l-7 7m7-7H3" />
|
||||
</svg>
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
|
||||
<div className="relative my-6">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<div className="w-full border-t border-gray-300 dark:border-gray-700"></div>
|
||||
</div>
|
||||
<div className="relative flex justify-center text-sm">
|
||||
<span className="px-2 bg-white/80 dark:bg-gray-800/80 backdrop-blur-sm text-gray-500 dark:text-gray-400 rounded-md">
|
||||
{
|
||||
swapAuth ? "Already have an account" : "Don't have an account"
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="w-full flex items-center justify-center py-2.5 px-4 border border-gray-300 dark:border-gray-600 rounded-lg shadow-sm bg-white/70 dark:bg-gray-800/70 backdrop-blur-sm hover:bg-gray-50 dark:hover:bg-gray-700 transition-all duration-200"
|
||||
onClick={swapAuthFunction}
|
||||
>
|
||||
<svg className="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 16l-4-4m0 0l4-4m-4 4h14m-5 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h7a3 3 0 013 3v1" />
|
||||
</svg>
|
||||
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
{
|
||||
swapAuth ?
|
||||
"Login"
|
||||
:
|
||||
"Create an account"
|
||||
}
|
||||
</span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
<RightSide />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const RightSide = () => {
|
||||
return (
|
||||
<div className="flex flex-col justify-center px-6 sm:px-12 py-8 text-gray-900 dark:text-white bg-white/10 dark:bg-gray-800/20 backdrop-blur-md rounded-xl border border-white/20 dark:border-gray-700/30 shadow-lg transition-all duration-200">
|
||||
<h2 className="text-2xl font-bold mb-6 bg-gradient-to-r from-blue-500 to-indigo-600 bg-clip-text text-transparent text-center">Why Choose SolidPoint?</h2>
|
||||
|
||||
{/* Platform Purpose */}
|
||||
<div className="mb-4 bg-white/20 dark:bg-white/5 p-4 rounded-xl border border-indigo-500/40 dark:border-indigo-500/20 shadow-md hover:shadow-md transition-all duration-200 hover:border-indigo-500/60 dark:hover:border-indigo-500/40 hover:bg-indigo-500/10">
|
||||
<h3 className="text-lg font-bold mb-2 bg-gradient-to-r from-blue-400 to-indigo-500 bg-clip-text text-transparent flex items-center">
|
||||
<div className="shrink-0 bg-gradient-to-br from-blue-500 to-indigo-600 p-1.5 rounded-lg shadow-md mr-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
|
||||
</svg>
|
||||
</div>
|
||||
Platform Purpose
|
||||
</h3>
|
||||
<ul className="space-y-1 text-gray-800 dark:text-gray-300 ml-4 font-medium">
|
||||
<li className="flex items-center">
|
||||
<svg className="w-4 h-4 mr-2 text-indigo-600 dark:text-indigo-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd"></path></svg>
|
||||
SolidPoint is an AI-powered content summarization platform
|
||||
</li>
|
||||
<li className="flex items-center">
|
||||
<svg className="w-4 h-4 mr-2 text-indigo-600 dark:text-indigo-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd"></path></svg>
|
||||
Helps users save time by extracting key insights from various content types
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Key Features */}
|
||||
<div className="mb-4 bg-white/20 dark:bg-white/5 p-4 rounded-xl border border-purple-500/40 dark:border-purple-500/20 shadow-md hover:shadow-md transition-all duration-200 hover:border-purple-500/60 dark:hover:border-purple-500/40 hover:bg-purple-500/10">
|
||||
<h3 className="text-lg font-bold mb-2 bg-gradient-to-r from-purple-400 to-indigo-500 bg-clip-text text-transparent flex items-center">
|
||||
<div className="shrink-0 bg-gradient-to-br from-purple-500 to-indigo-600 p-1.5 rounded-lg shadow-md mr-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z" />
|
||||
</svg>
|
||||
</div>
|
||||
Key Features Highlighted
|
||||
</h3>
|
||||
|
||||
<ul className="space-y-1 text-gray-800 dark:text-gray-300 ml-4">
|
||||
<li className="flex items-center">
|
||||
<svg className="w-4 h-4 mr-2 text-blue-600 dark:text-blue-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd"></path></svg>
|
||||
<span className="font-medium text-blue-700 dark:text-blue-300">Security:</span>
|
||||
<span className="ml-1 text-gray-800 dark:text-gray-300">Data protection and privacy</span>
|
||||
</li>
|
||||
<li className="flex items-center">
|
||||
<svg className="w-4 h-4 mr-2 text-purple-600 dark:text-purple-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd"></path></svg>
|
||||
<span className="font-medium text-purple-700 dark:text-purple-300">Summarization:</span>
|
||||
<span className="ml-1 text-gray-800 dark:text-gray-300">AI-powered content condensation</span>
|
||||
</li>
|
||||
<li className="flex items-center">
|
||||
<svg className="w-4 h-4 mr-2 text-pink-600 dark:text-pink-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd"></path></svg>
|
||||
<span className="font-medium text-pink-700 dark:text-pink-300">Performance:</span>
|
||||
<span className="ml-1 text-gray-800 dark:text-gray-300">Fast and reliable service</span>
|
||||
</li>
|
||||
<li className="flex items-center">
|
||||
<svg className="w-4 h-4 mr-2 text-green-600 dark:text-green-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd"></path></svg>
|
||||
<span className="font-medium text-green-700 dark:text-green-300">Pricing:</span>
|
||||
<span className="ml-1 text-gray-800 dark:text-gray-300">Cost-effective plans for different needs</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Target Use Cases */}
|
||||
<div className="bg-white/10 p-3 rounded-xl border border-teal-500/30 shadow-md hover:shadow-md transition-all duration-200 hover:border-teal-500/40 hover:bg-teal-500/10">
|
||||
<h3 className="text-lg font-bold mb-1 bg-gradient-to-r from-teal-400 to-blue-500 bg-clip-text text-transparent flex items-center">
|
||||
<div className="shrink-0 bg-gradient-to-br from-teal-500 to-blue-600 p-1 rounded-lg shadow-md mr-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
|
||||
</svg>
|
||||
</div>
|
||||
Target Use Cases
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-1 ml-1 transition-all duration-200">
|
||||
<div className="flex items-center p-1.5 rounded-lg hover:bg-white/20 dark:hover:bg-gray-800/30 transition-all duration-200 hover:shadow-sm">
|
||||
<div className="shrink-0 bg-red-500/30 p-1.5 rounded-full mr-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 text-red-600 dark:text-red-500" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M19.615 3.184c-3.604-.246-11.631-.245-15.23 0-3.897.266-4.356 2.62-4.385 8.816.029 6.185.484 8.549 4.385 8.816 3.6.245 11.626.246 15.23 0 3.897-.266 4.356-2.62 4.385-8.816-.029-6.185-.484-8.549-4.385-8.816zm-10.615 12.816v-8l8 3.993-8 4.007z" />
|
||||
</svg>
|
||||
</div>
|
||||
<span className="text-gray-900 dark:text-gray-300 text-sm font-medium">YouTube video summarization</span>
|
||||
</div>
|
||||
<div className="flex items-center p-1.5 rounded-lg hover:bg-white/20 dark:hover:bg-gray-800/30 transition-all duration-200 hover:shadow-sm">
|
||||
<div className="shrink-0 bg-orange-500/30 p-1.5 rounded-full mr-2">
|
||||
<img src="/reddit_logo.jpeg" className="h-4 w-4 rounded-full" alt="Reddit logo" />
|
||||
</div>
|
||||
<span className="text-gray-900 dark:text-gray-300 text-sm font-medium">Reddit post summarization</span>
|
||||
</div>
|
||||
<div className="flex items-center p-1.5 rounded-lg hover:bg-white/20 dark:hover:bg-gray-800/30 transition-all duration-200 hover:shadow-sm">
|
||||
<div className="shrink-0 bg-blue-500/30 p-1.5 rounded-full mr-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 text-blue-600 dark:text-blue-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z" />
|
||||
</svg>
|
||||
</div>
|
||||
<span className="text-gray-900 dark:text-gray-300 text-sm font-medium">arXiv paper summarization</span>
|
||||
</div>
|
||||
<div className="flex items-center p-1.5 rounded-lg hover:bg-white/20 dark:hover:bg-gray-800/30 transition-all duration-200 hover:shadow-sm">
|
||||
<div className="shrink-0 bg-green-500/30 p-1.5 rounded-full mr-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 text-green-600 dark:text-green-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9" />
|
||||
</svg>
|
||||
</div>
|
||||
<span className="text-gray-900 dark:text-gray-300 text-sm font-medium">Website content summarization</span>
|
||||
</div>
|
||||
<div className="flex items-center p-1.5 rounded-lg hover:bg-white/20 dark:hover:bg-gray-800/30 transition-all duration-200 hover:shadow-sm md:col-span-2">
|
||||
<div className="shrink-0 bg-purple-500/30 p-1.5 rounded-full mr-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 text-purple-600 dark:text-purple-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" />
|
||||
</svg>
|
||||
</div>
|
||||
<span className="text-gray-900 dark:text-gray-300 text-sm font-medium">PDF to flashcard conversion</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||