265 lines
9.2 KiB
TypeScript
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;
|