Site-imp1
This commit is contained in:
@@ -80,6 +80,22 @@ const Publishing = lazy(() => import("./pages/Settings/Publishing"));
|
||||
const Sites = lazy(() => import("./pages/Settings/Sites"));
|
||||
const ImportExport = lazy(() => import("./pages/Settings/ImportExport"));
|
||||
|
||||
// Sites - Lazy loaded
|
||||
const SiteList = lazy(() => import("./pages/Sites/List"));
|
||||
const SiteManage = lazy(() => import("./pages/Sites/Manage"));
|
||||
const SiteDashboard = lazy(() => import("./pages/Sites/Dashboard"));
|
||||
const SiteContent = lazy(() => import("./pages/Sites/Content"));
|
||||
const SiteEditor = lazy(() => import("./pages/Sites/Editor"));
|
||||
const PageManager = lazy(() => import("./pages/Sites/PageManager"));
|
||||
const PostEditor = lazy(() => import("./pages/Sites/PostEditor"));
|
||||
const SitePreview = lazy(() => import("./pages/Sites/Preview"));
|
||||
const SiteSettings = lazy(() => import("./pages/Sites/Settings"));
|
||||
|
||||
// Site Builder - Lazy loaded (will be moved from separate container)
|
||||
const SiteBuilderWizard = lazy(() => import("./pages/Sites/Builder/Wizard"));
|
||||
const SiteBuilderPreview = lazy(() => import("./pages/Sites/Builder/Preview"));
|
||||
const SiteBuilderBlueprints = lazy(() => import("./pages/Sites/Builder/Blueprints"));
|
||||
|
||||
// Help - Lazy loaded
|
||||
const Help = lazy(() => import("./pages/Help/Help"));
|
||||
const Docs = lazy(() => import("./pages/Help/Docs"));
|
||||
@@ -436,6 +452,85 @@ export default function App() {
|
||||
</Suspense>
|
||||
} />
|
||||
|
||||
{/* Sites Management */}
|
||||
<Route path="/sites" element={
|
||||
<Suspense fallback={null}>
|
||||
<SiteList />
|
||||
</Suspense>
|
||||
} />
|
||||
<Route path="/sites/manage" element={
|
||||
<Suspense fallback={null}>
|
||||
<SiteManage />
|
||||
</Suspense>
|
||||
} />
|
||||
<Route path="/sites/:id" element={
|
||||
<Suspense fallback={null}>
|
||||
<SiteDashboard />
|
||||
</Suspense>
|
||||
} />
|
||||
<Route path="/sites/:id/pages" element={
|
||||
<Suspense fallback={null}>
|
||||
<PageManager />
|
||||
</Suspense>
|
||||
} />
|
||||
<Route path="/sites/:id/pages/new" element={
|
||||
<Suspense fallback={null}>
|
||||
<PageManager />
|
||||
</Suspense>
|
||||
} />
|
||||
<Route path="/sites/:id/pages/:pageId/edit" element={
|
||||
<Suspense fallback={null}>
|
||||
<PageManager />
|
||||
</Suspense>
|
||||
} />
|
||||
<Route path="/sites/:id/content" element={
|
||||
<Suspense fallback={null}>
|
||||
<SiteContent />
|
||||
</Suspense>
|
||||
} />
|
||||
<Route path="/sites/:id/editor" element={
|
||||
<Suspense fallback={null}>
|
||||
<SiteEditor />
|
||||
</Suspense>
|
||||
} />
|
||||
<Route path="/sites/:id/preview" element={
|
||||
<Suspense fallback={null}>
|
||||
<SitePreview />
|
||||
</Suspense>
|
||||
} />
|
||||
<Route path="/sites/:id/settings" element={
|
||||
<Suspense fallback={null}>
|
||||
<SiteSettings />
|
||||
</Suspense>
|
||||
} />
|
||||
<Route path="/sites/:id/posts/:postId" element={
|
||||
<Suspense fallback={null}>
|
||||
<PostEditor />
|
||||
</Suspense>
|
||||
} />
|
||||
<Route path="/sites/:id/posts/:postId/edit" element={
|
||||
<Suspense fallback={null}>
|
||||
<PostEditor />
|
||||
</Suspense>
|
||||
} />
|
||||
|
||||
{/* Site Builder */}
|
||||
<Route path="/sites/builder" element={
|
||||
<Suspense fallback={null}>
|
||||
<SiteBuilderWizard />
|
||||
</Suspense>
|
||||
} />
|
||||
<Route path="/sites/builder/preview" element={
|
||||
<Suspense fallback={null}>
|
||||
<SiteBuilderPreview />
|
||||
</Suspense>
|
||||
} />
|
||||
<Route path="/sites/blueprints" element={
|
||||
<Suspense fallback={null}>
|
||||
<SiteBuilderBlueprints />
|
||||
</Suspense>
|
||||
} />
|
||||
|
||||
{/* Help */}
|
||||
<Route path="/help" element={
|
||||
<Suspense fallback={null}>
|
||||
|
||||
51
frontend/src/components/sites/SiteTypeBadge.tsx
Normal file
51
frontend/src/components/sites/SiteTypeBadge.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Site Type Badge Component
|
||||
* Displays site type indicator (Site Builder or WordPress)
|
||||
*/
|
||||
import React from 'react';
|
||||
import { Wand2, Globe } from 'lucide-react';
|
||||
import Badge from '../ui/badge/Badge';
|
||||
|
||||
interface SiteTypeBadgeProps {
|
||||
hostingType: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function SiteTypeBadge({ hostingType, className = '' }: SiteTypeBadgeProps) {
|
||||
const getTypeInfo = () => {
|
||||
switch (hostingType) {
|
||||
case 'igny8_sites':
|
||||
return {
|
||||
label: 'Site Builder',
|
||||
color: 'primary' as const,
|
||||
icon: <Wand2 className="w-3 h-3" />,
|
||||
};
|
||||
case 'wordpress':
|
||||
return {
|
||||
label: 'WordPress',
|
||||
color: 'info' as const,
|
||||
icon: <Globe className="w-3 h-3" />,
|
||||
};
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const typeInfo = getTypeInfo();
|
||||
|
||||
if (!typeInfo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Badge
|
||||
variant="soft"
|
||||
color={typeInfo.color}
|
||||
startIcon={typeInfo.icon}
|
||||
className={className}
|
||||
>
|
||||
{typeInfo.label}
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -69,6 +69,16 @@ export const routes: RouteConfig[] = [
|
||||
{ path: '/optimizer/content', label: 'Content', breadcrumb: 'Optimize Content' },
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/sites',
|
||||
label: 'Sites',
|
||||
icon: 'Sites',
|
||||
children: [
|
||||
{ path: '/sites', label: 'All Sites', breadcrumb: 'All Sites' },
|
||||
{ path: '/sites/builder', label: 'Create Site', breadcrumb: 'Site Builder' },
|
||||
{ path: '/sites/blueprints', label: 'Blueprints', breadcrumb: 'Blueprints' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const getBreadcrumbs = (pathname: string): Array<{ label: string; path: string }> => {
|
||||
|
||||
@@ -84,7 +84,6 @@ const AppSidebar: React.FC = () => {
|
||||
icon: <PlugInIcon />,
|
||||
name: "Setup",
|
||||
subItems: [
|
||||
{ name: "Sites", path: "/settings/sites" },
|
||||
{ name: "Keywords Opportunities", path: "/planner/keyword-opportunities" },
|
||||
],
|
||||
},
|
||||
@@ -171,6 +170,17 @@ const AppSidebar: React.FC = () => {
|
||||
});
|
||||
}
|
||||
|
||||
// Add Sites menu (always available)
|
||||
workflowItems.push({
|
||||
icon: <GridIcon />,
|
||||
name: "Sites",
|
||||
subItems: [
|
||||
{ name: "All Sites", path: "/sites" },
|
||||
{ name: "Create Site", path: "/sites/builder" },
|
||||
{ name: "Blueprints", path: "/sites/blueprints" },
|
||||
],
|
||||
});
|
||||
|
||||
return [
|
||||
{
|
||||
label: "OVERVIEW",
|
||||
@@ -194,17 +204,18 @@ const AppSidebar: React.FC = () => {
|
||||
{
|
||||
label: "ACCOUNT & SETTINGS",
|
||||
items: [
|
||||
{
|
||||
icon: <PlugInIcon />,
|
||||
name: "Settings",
|
||||
subItems: [
|
||||
{ name: "General", path: "/settings" },
|
||||
{ name: "Plans", path: "/settings/plans" },
|
||||
{ name: "Integration", path: "/settings/integration" },
|
||||
{ name: "Publishing", path: "/settings/publishing" },
|
||||
{ name: "Import / Export", path: "/settings/import-export" },
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: <PlugInIcon />,
|
||||
name: "Settings",
|
||||
subItems: [
|
||||
{ name: "General", path: "/settings" },
|
||||
{ name: "Plans", path: "/settings/plans" },
|
||||
{ name: "Sites", path: "/settings/sites" },
|
||||
{ name: "Integration", path: "/settings/integration" },
|
||||
{ name: "Publishing", path: "/settings/publishing" },
|
||||
{ name: "Import / Export", path: "/settings/import-export" },
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: <DollarLineIcon />,
|
||||
name: "Billing",
|
||||
|
||||
108
frontend/src/pages/Sites/Builder/Blueprints.tsx
Normal file
108
frontend/src/pages/Sites/Builder/Blueprints.tsx
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* Site Builder Blueprints
|
||||
* Moved from site-builder container to main app
|
||||
* TODO: Migrate full implementation from site-builder/src/pages/dashboard/
|
||||
*/
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import PageMeta from '../../../components/common/PageMeta';
|
||||
import { Card } from '../../../components/ui/card';
|
||||
import Button from '../../../components/ui/button/Button';
|
||||
import { useToast } from '../../../components/ui/toast/ToastContainer';
|
||||
import { fetchAPI } from '../../../services/api';
|
||||
import { FileText, Plus } from 'lucide-react';
|
||||
|
||||
export default function SiteBuilderBlueprints() {
|
||||
const navigate = useNavigate();
|
||||
const toast = useToast();
|
||||
const [blueprints, setBlueprints] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
loadBlueprints();
|
||||
}, []);
|
||||
|
||||
const loadBlueprints = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const data = await fetchAPI('/v1/site-builder/blueprints/');
|
||||
if (data?.results) {
|
||||
setBlueprints(data.results);
|
||||
} else if (Array.isArray(data)) {
|
||||
setBlueprints(data);
|
||||
}
|
||||
} catch (error: any) {
|
||||
toast.error(`Failed to load blueprints: ${error.message}`);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
<PageMeta title="Blueprints - IGNY8" />
|
||||
<div className="mb-6 flex justify-between items-center">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">
|
||||
Blueprints
|
||||
</h1>
|
||||
<p className="text-gray-600 dark:text-gray-400 mt-1">
|
||||
View and manage all site blueprints
|
||||
</p>
|
||||
</div>
|
||||
<Button onClick={() => navigate('/sites/builder')} variant="primary">
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
Create Blueprint
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="text-gray-500">Loading blueprints...</div>
|
||||
</div>
|
||||
) : blueprints.length === 0 ? (
|
||||
<Card className="p-12 text-center">
|
||||
<FileText className="w-16 h-16 mx-auto mb-4 text-gray-400" />
|
||||
<p className="text-gray-600 dark:text-gray-400 mb-4">
|
||||
No blueprints created yet
|
||||
</p>
|
||||
<Button onClick={() => navigate('/sites/builder')} variant="primary">
|
||||
Create Your First Blueprint
|
||||
</Button>
|
||||
</Card>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{blueprints.map((blueprint) => (
|
||||
<Card key={blueprint.id} className="p-4 hover:shadow-lg transition-shadow">
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
{blueprint.name}
|
||||
</h3>
|
||||
{blueprint.description && (
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
||||
{blueprint.description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center justify-between pt-3 border-t border-gray-200 dark:border-gray-700">
|
||||
<div className="text-xs text-gray-600 dark:text-gray-400">
|
||||
Status: {blueprint.status}
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => navigate(`/sites/${blueprint.site}/editor`)}
|
||||
>
|
||||
View
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
44
frontend/src/pages/Sites/Builder/Preview.tsx
Normal file
44
frontend/src/pages/Sites/Builder/Preview.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Site Builder Preview
|
||||
* Moved from site-builder container to main app
|
||||
* TODO: Migrate full implementation from site-builder/src/pages/preview/
|
||||
*/
|
||||
import React from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import PageMeta from '../../../components/common/PageMeta';
|
||||
import { Card } from '../../../components/ui/card';
|
||||
import Button from '../../../components/ui/button/Button';
|
||||
import { Eye } from 'lucide-react';
|
||||
|
||||
export default function SiteBuilderPreview() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
<PageMeta title="Site Preview - IGNY8" />
|
||||
<div className="mb-6">
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">
|
||||
Site Preview
|
||||
</h1>
|
||||
<p className="text-gray-600 dark:text-gray-400 mt-1">
|
||||
Preview your site during building
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Card className="p-12 text-center">
|
||||
<Eye className="w-16 h-16 mx-auto mb-4 text-gray-400" />
|
||||
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-2">
|
||||
Site Preview
|
||||
</h2>
|
||||
<p className="text-gray-600 dark:text-gray-400 mb-6">
|
||||
The Site Builder preview is being integrated into the main app.
|
||||
Full implementation coming soon.
|
||||
</p>
|
||||
<Button onClick={() => navigate('/sites/builder')} variant="outline">
|
||||
Back to Builder
|
||||
</Button>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
44
frontend/src/pages/Sites/Builder/Wizard.tsx
Normal file
44
frontend/src/pages/Sites/Builder/Wizard.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Site Builder Wizard
|
||||
* Moved from site-builder container to main app
|
||||
* TODO: Migrate full implementation from site-builder/src/pages/wizard/
|
||||
*/
|
||||
import React from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import PageMeta from '../../../components/common/PageMeta';
|
||||
import { Card } from '../../../components/ui/card';
|
||||
import Button from '../../../components/ui/button/Button';
|
||||
import { Wand2 } from 'lucide-react';
|
||||
|
||||
export default function SiteBuilderWizard() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
<PageMeta title="Site Builder - IGNY8" />
|
||||
<div className="mb-6">
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">
|
||||
Site Builder
|
||||
</h1>
|
||||
<p className="text-gray-600 dark:text-gray-400 mt-1">
|
||||
Create a new site using AI-powered wizard
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Card className="p-12 text-center">
|
||||
<Wand2 className="w-16 h-16 mx-auto mb-4 text-gray-400" />
|
||||
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-2">
|
||||
Site Builder Wizard
|
||||
</h2>
|
||||
<p className="text-gray-600 dark:text-gray-400 mb-6">
|
||||
The Site Builder wizard is being integrated into the main app.
|
||||
Full implementation coming soon.
|
||||
</p>
|
||||
<Button onClick={() => navigate('/sites')} variant="outline">
|
||||
Back to Sites
|
||||
</Button>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ export default function SiteContentEditor() {
|
||||
<p className="text-gray-600 dark:text-gray-400 mb-4">
|
||||
No site blueprints found for this site
|
||||
</p>
|
||||
<Button onClick={() => navigate('/site-builder')} variant="primary">
|
||||
<Button onClick={() => navigate('/sites/builder')} variant="primary">
|
||||
Create Site Blueprint
|
||||
</Button>
|
||||
</Card>
|
||||
|
||||
@@ -5,13 +5,15 @@
|
||||
*/
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { PlusIcon, EditIcon, SettingsIcon, EyeIcon, TrashIcon, FilterIcon, SearchIcon } from 'lucide-react';
|
||||
import { PlusIcon, EditIcon, SettingsIcon, EyeIcon, TrashIcon, FilterIcon, SearchIcon, PlugIcon } from 'lucide-react';
|
||||
import PageMeta from '../../components/common/PageMeta';
|
||||
import { Card } from '../../components/ui/card';
|
||||
import Button from '../../components/ui/button/Button';
|
||||
import SelectDropdown from '../../components/form/SelectDropdown';
|
||||
import { useToast } from '../../components/ui/toast/ToastContainer';
|
||||
import { fetchAPI } from '../../services/api';
|
||||
import SiteTypeBadge from '../../components/sites/SiteTypeBadge';
|
||||
import Badge from '../../components/ui/badge/Badge';
|
||||
|
||||
interface Site {
|
||||
id: number;
|
||||
@@ -25,6 +27,7 @@ interface Site {
|
||||
updated_at: string;
|
||||
page_count?: number;
|
||||
integration_count?: number;
|
||||
has_wordpress_integration?: boolean;
|
||||
}
|
||||
|
||||
export default function SiteList() {
|
||||
@@ -54,7 +57,24 @@ export default function SiteList() {
|
||||
setLoading(true);
|
||||
const data = await fetchAPI('/v1/auth/sites/');
|
||||
if (data && Array.isArray(data)) {
|
||||
setSites(data);
|
||||
// Check for WordPress integrations
|
||||
const sitesWithIntegrations = await Promise.all(
|
||||
data.map(async (site: Site) => {
|
||||
if (site.hosting_type === 'wordpress') {
|
||||
try {
|
||||
const integrations = await fetchAPI(`/v1/integration/integrations/?site=${site.id}&platform=wordpress`);
|
||||
return {
|
||||
...site,
|
||||
has_wordpress_integration: integrations?.results?.length > 0 || integrations?.length > 0,
|
||||
};
|
||||
} catch {
|
||||
return { ...site, has_wordpress_integration: false };
|
||||
}
|
||||
}
|
||||
return site;
|
||||
})
|
||||
);
|
||||
setSites(sitesWithIntegrations);
|
||||
}
|
||||
} catch (error: any) {
|
||||
toast.error(`Failed to load sites: ${error.message}`);
|
||||
@@ -107,7 +127,11 @@ export default function SiteList() {
|
||||
};
|
||||
|
||||
const handleCreateSite = () => {
|
||||
navigate('/site-builder');
|
||||
navigate('/sites/builder');
|
||||
};
|
||||
|
||||
const handleIntegration = (siteId: number) => {
|
||||
navigate(`/sites/${siteId}/settings?tab=integrations`);
|
||||
};
|
||||
|
||||
const handleEdit = (siteId: number) => {
|
||||
|
||||
@@ -4,12 +4,14 @@
|
||||
*/
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { PlusIcon, EditIcon, SettingsIcon, EyeIcon, TrashIcon } from 'lucide-react';
|
||||
import { PlusIcon, EditIcon, SettingsIcon, EyeIcon, TrashIcon, PlugIcon } from 'lucide-react';
|
||||
import PageMeta from '../../components/common/PageMeta';
|
||||
import { Card } from '../../components/ui/card';
|
||||
import Button from '../../components/ui/button/Button';
|
||||
import { useToast } from '../../components/ui/toast/ToastContainer';
|
||||
import { fetchAPI } from '../../services/api';
|
||||
import SiteTypeBadge from '../../components/sites/SiteTypeBadge';
|
||||
import Badge from '../../components/ui/badge/Badge';
|
||||
|
||||
interface Site {
|
||||
id: number;
|
||||
@@ -23,6 +25,7 @@ interface Site {
|
||||
updated_at: string;
|
||||
page_count?: number;
|
||||
integration_count?: number;
|
||||
has_wordpress_integration?: boolean;
|
||||
}
|
||||
|
||||
export default function SiteManagement() {
|
||||
@@ -40,7 +43,24 @@ export default function SiteManagement() {
|
||||
setLoading(true);
|
||||
const data = await fetchAPI('/v1/auth/sites/');
|
||||
if (data && Array.isArray(data)) {
|
||||
setSites(data);
|
||||
// Check for WordPress integrations
|
||||
const sitesWithIntegrations = await Promise.all(
|
||||
data.map(async (site: Site) => {
|
||||
if (site.hosting_type === 'wordpress') {
|
||||
try {
|
||||
const integrations = await fetchAPI(`/v1/integration/integrations/?site=${site.id}&platform=wordpress`);
|
||||
return {
|
||||
...site,
|
||||
has_wordpress_integration: integrations?.results?.length > 0 || integrations?.length > 0,
|
||||
};
|
||||
} catch {
|
||||
return { ...site, has_wordpress_integration: false };
|
||||
}
|
||||
}
|
||||
return site;
|
||||
})
|
||||
);
|
||||
setSites(sitesWithIntegrations);
|
||||
}
|
||||
} catch (error: any) {
|
||||
toast.error(`Failed to load sites: ${error.message}`);
|
||||
@@ -49,8 +69,12 @@ export default function SiteManagement() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleIntegration = (siteId: number) => {
|
||||
navigate(`/sites/${siteId}/settings?tab=integrations`);
|
||||
};
|
||||
|
||||
const handleCreateSite = () => {
|
||||
navigate('/site-builder');
|
||||
navigate('/sites/builder');
|
||||
};
|
||||
|
||||
const handleEdit = (siteId: number) => {
|
||||
|
||||
Reference in New Issue
Block a user