Section 2 Part 3

This commit is contained in:
IGNY8 VPS (Salman)
2026-01-03 08:11:41 +00:00
parent 935c7234b1
commit 4d6ee21408
15 changed files with 1209 additions and 895 deletions

View File

@@ -3,6 +3,7 @@ interface ComponentCardProps {
children: React.ReactNode;
className?: string; // Additional custom classes for styling
desc?: string | React.ReactNode; // Description text
headerContent?: React.ReactNode; // Additional content to display in header (e.g., actions, navigation)
}
const ComponentCard: React.FC<ComponentCardProps> = ({
@@ -10,21 +11,29 @@ const ComponentCard: React.FC<ComponentCardProps> = ({
children,
className = "",
desc = "",
headerContent,
}) => {
return (
<div
className={`rounded-2xl border border-gray-200 bg-white dark:border-gray-800 dark:bg-white/[0.03] overflow-visible ${className}`}
>
{/* Card Header (render only when title or desc provided) */}
{(title || desc) && (
<div className="px-6 py-5 relative z-0">
<h3 className="text-base font-medium text-gray-800 dark:text-white/90">
{title}
</h3>
{desc && (
<p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
{desc}
</p>
{(title || desc || headerContent) && (
<div className="px-6 py-5 relative z-0 flex items-start justify-between gap-4">
<div className="flex-1">
<h3 className="text-base font-medium text-gray-800 dark:text-white/90">
{title}
</h3>
{desc && (
<p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
{desc}
</p>
)}
</div>
{headerContent && (
<div className="flex-shrink-0">
{headerContent}
</div>
)}
</div>
)}

View File

@@ -56,13 +56,21 @@ export default function SingleSiteSelector() {
}
};
const handleSiteSelect = async (siteId: number) => {
const handleSiteSelect = async (newSiteId: number) => {
try {
await apiSetActiveSite(siteId);
const selectedSite = sites.find(s => s.id === siteId);
await apiSetActiveSite(newSiteId);
const selectedSite = sites.find(s => s.id === newSiteId);
if (selectedSite) {
setActiveSite(selectedSite);
toast.success(`Switched to "${selectedSite.name}"`);
// If we're on a site-specific page (/sites/:id/...), navigate to same subpage for new site
const path = window.location.pathname;
const sitePageMatch = path.match(/^\/sites\/(\d+)(\/.*)?$/);
if (sitePageMatch) {
const subPath = sitePageMatch[2] || ''; // e.g., '/settings', '/content', ''
navigate(`/sites/${newSiteId}${subPath}`);
}
}
setSitesOpen(false);
} catch (error: any) {

View File

@@ -0,0 +1,133 @@
/**
* SiteInfoBar - Reusable site info bar for site-specific pages
* Shows site name, URL, badges, and action buttons in a single row
*/
import { useNavigate } from 'react-router-dom';
import Button from '../ui/button/Button';
import {
GridIcon,
FileIcon,
GlobeIcon,
PlusIcon,
} from '../../icons';
interface SiteInfoBarProps {
site: {
id: number;
name: string;
domain?: string;
url?: string;
site_type?: string;
hosting_type?: string;
is_active?: boolean;
} | null;
/** Current page - determines which buttons to show */
currentPage: 'dashboard' | 'settings' | 'content';
/** Optional: total items count for content page */
itemsCount?: number;
/** Optional: show New Post button */
showNewPostButton?: boolean;
}
export default function SiteInfoBar({
site,
currentPage,
itemsCount,
showNewPostButton = false,
}: SiteInfoBarProps) {
const navigate = useNavigate();
if (!site) return null;
const siteUrl = site.domain || site.url;
return (
<div className="mb-6 px-4 py-3 rounded-xl bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 shadow-sm">
<div className="flex items-center justify-between gap-4 flex-wrap">
{/* Left: Badges */}
<div className="flex items-center gap-2">
<span className="inline-flex items-center px-2.5 py-1 rounded-md text-xs font-medium bg-brand-50 text-brand-700 dark:bg-brand-900/30 dark:text-brand-300 border border-brand-200 dark:border-brand-800">
{site.site_type || 'marketing'}
</span>
<span className="inline-flex items-center px-2.5 py-1 rounded-md text-xs font-medium bg-purple-50 text-purple-700 dark:bg-purple-900/30 dark:text-purple-300 border border-purple-200 dark:border-purple-800">
{site.hosting_type || 'igny8_sites'}
</span>
<span className={`inline-flex items-center px-2.5 py-1 rounded-md text-xs font-medium ${
site.is_active !== false
? 'bg-success-50 text-success-700 dark:bg-success-900/30 dark:text-success-300 border border-success-200 dark:border-success-800'
: 'bg-gray-50 text-gray-700 dark:bg-gray-800 dark:text-gray-300 border border-gray-200 dark:border-gray-700'
}`}>
{site.is_active !== false ? '● Active' : '○ Inactive'}
</span>
</div>
{/* Center: Site Name and URL */}
<div className="flex-1 text-center min-w-0">
<h2 className="text-base font-semibold text-gray-900 dark:text-white truncate">{site.name}</h2>
{siteUrl && (
<a
href={siteUrl.startsWith('http') ? siteUrl : `https://${siteUrl}`}
target="_blank"
rel="noopener noreferrer"
className="text-brand-600 hover:text-brand-700 dark:text-brand-400 text-sm transition truncate inline-block max-w-full"
>
{siteUrl}
</a>
)}
{itemsCount !== undefined && (
<span className="text-sm text-gray-500 dark:text-gray-400 ml-2">
({itemsCount} items)
</span>
)}
</div>
{/* Right: Action Buttons */}
<div className="flex items-center gap-2">
{currentPage !== 'dashboard' && (
<Button
onClick={() => navigate(`/sites/${site.id}`)}
variant="outline"
size="sm"
className="border-brand-300 text-brand-700 hover:bg-brand-50 dark:border-brand-600 dark:text-brand-400 dark:hover:bg-brand-900/20"
startIcon={<GlobeIcon className="w-4 h-4" />}
>
Dashboard
</Button>
)}
{currentPage !== 'settings' && (
<Button
onClick={() => navigate(`/sites/${site.id}/settings`)}
variant="outline"
size="sm"
className="border-purple-300 text-purple-700 hover:bg-purple-50 dark:border-purple-600 dark:text-purple-400 dark:hover:bg-purple-900/20"
startIcon={<GridIcon className="w-4 h-4" />}
>
Settings
</Button>
)}
{currentPage !== 'content' && (
<Button
onClick={() => navigate(`/sites/${site.id}/content`)}
variant="outline"
size="sm"
className="border-success-300 text-success-700 hover:bg-success-50 dark:border-success-600 dark:text-success-400 dark:hover:bg-success-900/20"
startIcon={<FileIcon className="w-4 h-4" />}
>
Content
</Button>
)}
{showNewPostButton && (
<Button
onClick={() => navigate(`/sites/${site.id}/posts/new`)}
variant="primary"
size="sm"
startIcon={<PlusIcon className="w-4 h-4" />}
>
New Post
</Button>
)}
</div>
</div>
</div>
);
}