mono/packages/ui/src/modules/pages/PageManager.tsx
2026-02-17 20:18:27 +01:00

265 lines
9.2 KiB
TypeScript

import { useState, useEffect } from "react";
import { useAuth } from "@/hooks/useAuth";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import { Link, useNavigate } from "react-router-dom";
import { FileText, Plus, Eye, EyeOff, Trash2, Edit } from "lucide-react";
import { T, translate } from "@/i18n";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import { Page } from "./types";
import { fetchUserPages, deletePage, updatePage } from "./client-pages";
interface PageManagerProps {
userId: string;
username?: string;
isOwnProfile: boolean;
orgSlug?: string;
}
const PageManager = ({ userId, username, isOwnProfile, orgSlug }: PageManagerProps) => {
const { user } = useAuth();
const navigate = useNavigate();
const [pages, setPages] = useState<Page[]>([]);
const [loading, setLoading] = useState(true);
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [pageToDelete, setPageToDelete] = useState<Page | null>(null);
useEffect(() => {
loadPages();
}, [userId]);
const loadPages = async () => {
try {
setLoading(true);
const data = await fetchUserPages(userId);
setPages(data || []);
} catch (error) {
console.error('Error fetching pages:', error);
toast.error(translate('Failed to load pages'));
} finally {
setLoading(false);
}
};
const handleDeletePage = async () => {
if (!pageToDelete || !isOwnProfile) return;
try {
await deletePage(pageToDelete.id);
setPages(pages.filter(p => p.id !== pageToDelete.id));
toast.success(translate('Page deleted successfully'));
setDeleteDialogOpen(false);
setPageToDelete(null);
} catch (error) {
console.error('Error deleting page:', error);
toast.error(translate('Failed to delete page'));
}
};
const handleToggleVisibility = async (page: Page) => {
if (!isOwnProfile) return;
try {
await updatePage(page.id, { visible: !page.visible });
setPages(pages.map(p =>
p.id === page.id ? { ...p, visible: !p.visible } : p
));
toast.success(translate(page.visible ? 'Page hidden' : 'Page made visible'));
} catch (error) {
console.error('Error toggling visibility:', error);
toast.error(translate('Failed to update page visibility'));
}
};
const handleTogglePublic = async (page: Page) => {
if (!isOwnProfile) return;
try {
await updatePage(page.id, { is_public: !page.is_public });
setPages(pages.map(p =>
p.id === page.id ? { ...p, is_public: !p.is_public } : p
));
toast.success(translate(page.is_public ? 'Page made private' : 'Page made public'));
} catch (error) {
console.error('Error toggling public status:', error);
toast.error(translate('Failed to update page status'));
}
};
const getPageUrl = (slug: string) => {
if (orgSlug) {
return `/org/${orgSlug}/user/${username || userId}/pages/${slug}`;
}
return `/user/${username || userId}/pages/${slug}`;
};
if (loading) {
return (
<div className="text-center py-8">
<div className="text-muted-foreground"><T>Loading pages...</T></div>
</div>
);
}
if (!isOwnProfile && pages.length === 0) {
return null; // Don't show anything if not own profile and no pages
}
return (
<div className="space-y-4">
<div className="flex items-center justify-between">
<h3 className="text-lg font-semibold"><T>Pages</T></h3>
{isOwnProfile && (
<Button
size="sm"
variant="outline"
onClick={() => {
const newPageUrl = orgSlug
? `/org/${orgSlug}/user/${username || userId}/pages/new`
: `/user/${username || userId}/pages/new`;
navigate(newPageUrl);
}}
>
<Plus className="h-4 w-4 mr-2" />
<T>New Page</T>
</Button>
)}
</div>
{pages.length === 0 ? (
<Card>
<CardContent className="pt-6 text-center">
<FileText className="h-12 w-12 mx-auto mb-4 text-muted-foreground" />
<p className="text-muted-foreground">
<T>{isOwnProfile ? "No pages yet. Create your first page to get started." : "No pages to display."}</T>
</p>
</CardContent>
</Card>
) : (
<div className="grid gap-4 md:grid-cols-2">
{pages.map((page) => (
<Card key={page.id}>
<CardHeader>
<div className="flex items-start justify-between">
<div className="flex-1">
<CardTitle className="text-base">
<Link
to={getPageUrl(page.slug)}
className="hover:text-primary transition-colors"
>
{page.title}
</Link>
</CardTitle>
<div className="mt-1 text-xs text-muted-foreground flex items-center gap-2">
{!page.visible && (
<span className="flex items-center gap-1 text-orange-500">
<EyeOff className="h-3 w-3" />
<T>Hidden</T>
</span>
)}
{!page.is_public && (
<span className="flex items-center gap-1 text-blue-500">
<T>Private</T>
</span>
)}
{page.type && (
<span className="text-muted-foreground">
{page.type}
</span>
)}
</div>
</div>
<FileText className="h-5 w-5 text-muted-foreground" />
</div>
</CardHeader>
{isOwnProfile && (
<CardContent>
<div className="flex items-center gap-2">
<Button
size="sm"
variant="ghost"
onClick={() => handleToggleVisibility(page)}
title={page.visible ? translate('Hide page') : translate('Show page')}
>
{page.visible ? (
<Eye className="h-4 w-4" />
) : (
<EyeOff className="h-4 w-4" />
)}
</Button>
<Button
size="sm"
variant="ghost"
onClick={() => handleTogglePublic(page)}
title={page.is_public ? translate('Make private') : translate('Make public')}
>
{page.is_public ? (
<span className="text-xs">Public</span>
) : (
<span className="text-xs">Private</span>
)}
</Button>
<Link to={getPageUrl(page.slug)}>
<Button size="sm" variant="ghost">
<Edit className="h-4 w-4" />
</Button>
</Link>
<Button
size="sm"
variant="ghost"
onClick={() => {
setPageToDelete(page);
setDeleteDialogOpen(true);
}}
className="text-destructive hover:text-destructive"
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
</CardContent>
)}
</Card>
))}
</div>
)}
<AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle><T>Delete Page</T></AlertDialogTitle>
<AlertDialogDescription>
<T>Are you sure you want to delete "{pageToDelete?.title}"? This action cannot be undone.</T>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel onClick={() => setPageToDelete(null)}>
<T>Cancel</T>
</AlertDialogCancel>
<AlertDialogAction onClick={handleDeletePage} className="bg-destructive text-destructive-foreground hover:bg-destructive/90">
<T>Delete</T>
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
);
};
export default PageManager;