feat: pages publiées utilisateur (settings) + fix import Shield dupliqué
- /settings/published : l'utilisateur voit ses notes publiées - Copier le lien, ouvrir, dépublier - API /api/user/published - Onglet 'Mes pages' (Globe) dans la nav settings - Fix: import Shield dupliqué dans admin-sidebar.tsx - i18n FR/EN
This commit is contained in:
103
memento-note/app/(main)/settings/published/page.tsx
Normal file
103
memento-note/app/(main)/settings/published/page.tsx
Normal file
@@ -0,0 +1,103 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react'
|
||||
import { Globe, ExternalLink, Trash2, Loader2, Copy, Check } from 'lucide-react'
|
||||
import { toast } from 'sonner'
|
||||
import { useLanguage } from '@/lib/i18n'
|
||||
|
||||
interface PublishedNote {
|
||||
id: string
|
||||
title: string | null
|
||||
publicSlug: string | null
|
||||
publishedAt: string | null
|
||||
}
|
||||
|
||||
export default function UserPublishedPage() {
|
||||
const { t } = useLanguage()
|
||||
const [notes, setNotes] = useState<PublishedNote[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [copiedId, setCopiedId] = useState<string | null>(null)
|
||||
|
||||
const load = useCallback(async () => {
|
||||
setLoading(true)
|
||||
try {
|
||||
const res = await fetch('/api/user/published')
|
||||
const data = await res.json()
|
||||
setNotes(data.notes || [])
|
||||
} catch { toast.error('Erreur') }
|
||||
finally { setLoading(false) }
|
||||
}, [])
|
||||
|
||||
useEffect(() => { load() }, [load])
|
||||
|
||||
const unpublish = async (noteId: string) => {
|
||||
try {
|
||||
const res = await fetch('/api/notes/publish', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ noteId, action: 'unpublish' }),
|
||||
})
|
||||
if (res.ok) { toast.success(t('richTextEditor.unpublishSuccess') || 'Note dépubliée'); load() }
|
||||
else { toast.error('Erreur') }
|
||||
} catch { toast.error('Erreur') }
|
||||
}
|
||||
|
||||
const copyLink = (slug: string, noteId: string) => {
|
||||
const url = `${window.location.origin}/p/${slug}`
|
||||
navigator.clipboard?.writeText(url).then(() => {
|
||||
setCopiedId(noteId); setTimeout(() => setCopiedId(null), 2000); toast.success('Lien copié !')
|
||||
}).catch(() => {
|
||||
const el = document.createElement('textarea'); el.value = url; document.body.appendChild(el); el.select()
|
||||
document.execCommand('copy'); document.body.removeChild(el)
|
||||
setCopiedId(noteId); setTimeout(() => setCopiedId(null), 2000); toast.success('Lien copié !')
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6" dir="auto">
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold mb-1">{t('settings.publishedTitle') || 'Mes pages publiées'}</h2>
|
||||
<p className="text-sm text-muted-foreground">{t('settings.publishedDesc') || 'Gérez les notes que vous avez publiées publiquement.'}</p>
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
<div className="flex justify-center py-8"><Loader2 className="h-5 w-5 animate-spin text-muted-foreground" /></div>
|
||||
) : notes.length === 0 ? (
|
||||
<div className="text-center py-12 rounded-xl border border-dashed border-border">
|
||||
<Globe size={32} className="mx-auto text-muted-foreground/40 mb-3" />
|
||||
<p className="text-sm text-muted-foreground">{t('settings.publishedEmpty') || 'Aucune note publiée pour le moment.'}</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{notes.map(note => (
|
||||
<div key={note.id} className="flex items-center gap-3 p-3.5 rounded-xl border border-border bg-card">
|
||||
<div className="w-9 h-9 rounded-lg bg-green-50 dark:bg-green-950/20 flex items-center justify-center shrink-0">
|
||||
<Globe size={15} className="text-green-600 dark:text-green-400" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="text-sm font-medium truncate">{note.title || 'Sans titre'}</div>
|
||||
<div className="text-[11px] text-muted-foreground">
|
||||
{note.publishedAt ? new Date(note.publishedAt).toLocaleDateString() : ''}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1 shrink-0">
|
||||
<button onClick={() => copyLink(note.publicSlug!, note.id)}
|
||||
className="p-2 rounded-lg hover:bg-muted text-muted-foreground hover:text-foreground transition-colors" title="Copier le lien">
|
||||
{copiedId === note.id ? <Check size={15} className="text-green-500" /> : <Copy size={15} />}
|
||||
</button>
|
||||
<a href={`/p/${note.publicSlug}`} target="_blank" rel="noopener noreferrer"
|
||||
className="p-2 rounded-lg hover:bg-muted text-muted-foreground hover:text-foreground transition-colors" title="Ouvrir">
|
||||
<ExternalLink size={15} />
|
||||
</a>
|
||||
<button onClick={() => unpublish(note.id)}
|
||||
className="p-2 rounded-lg hover:bg-red-50 dark:hover:bg-red-950/20 text-muted-foreground hover:text-red-500 transition-colors" title="Dépublier">
|
||||
<Trash2 size={15} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
16
memento-note/app/api/user/published/route.ts
Normal file
16
memento-note/app/api/user/published/route.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { auth } from '@/auth'
|
||||
import prisma from '@/lib/prisma'
|
||||
|
||||
export async function GET() {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
|
||||
const notes = await prisma.note.findMany({
|
||||
where: { userId: session.user.id, isPublic: true, trashedAt: null },
|
||||
select: { id: true, title: true, publicSlug: true, publishedAt: true },
|
||||
orderBy: { publishedAt: 'desc' },
|
||||
})
|
||||
|
||||
return NextResponse.json({ notes })
|
||||
}
|
||||
Reference in New Issue
Block a user