fix: auto-save hashtags, plan-based platforms, archive with text preview
- Hashtag auto-save: debounced 500ms save on add/remove/edit, no manual button - Platform chips: Freemium sees only Instagram/Facebook, Pro sees all 4 - Platform badge: changed from tab-like to informative "per instagram" label - Add "Archivio →" link in content page header - Rewrite ContentArchive: show text_content preview (was showing only hashtags), add edit button, use Editorial Fresh design system, fix post.text → post.text_content Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useState, useEffect, useRef } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { api } from '../api'
|
||||
import { useAuth } from '../AuthContext'
|
||||
|
||||
const PLATFORMS = [
|
||||
{ value: 'instagram', label: 'Instagram' },
|
||||
@@ -35,6 +36,8 @@ const STATUS_COLORS = {
|
||||
}
|
||||
|
||||
export default function ContentPage() {
|
||||
const { isPro } = useAuth()
|
||||
const availablePlatforms = isPro ? PLATFORMS : PLATFORMS.filter(p => ['instagram', 'facebook'].includes(p.value))
|
||||
const [characters, setCharacters] = useState([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [charsLoading, setCharsLoading] = useState(true)
|
||||
@@ -129,9 +132,14 @@ export default function ContentPage() {
|
||||
<h2 style={{ fontFamily: "'Fraunces', serif", fontSize: '1.75rem', fontWeight: 600, letterSpacing: '-0.02em', color: 'var(--ink)', margin: '0.5rem 0 0.3rem' }}>
|
||||
Genera Contenuti
|
||||
</h2>
|
||||
<p style={{ fontSize: '0.875rem', color: 'var(--ink-muted)', margin: 0 }}>
|
||||
Definisci un brief editoriale, scegli piattaforma e tipo, poi genera. L'AI terrà conto del tono e dei topic del personaggio selezionato.
|
||||
</p>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '1rem' }}>
|
||||
<p style={{ fontSize: '0.875rem', color: 'var(--ink-muted)', margin: 0 }}>
|
||||
Definisci un brief editoriale, scegli piattaforma e tipo, poi genera. L'AI terrà conto del tono e dei topic del personaggio selezionato.
|
||||
</p>
|
||||
<Link to="/content/archive" style={{ fontSize: '0.8rem', color: 'var(--accent)', whiteSpace: 'nowrap', textDecoration: 'none', fontWeight: 600 }}>
|
||||
Archivio →
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* No characters → gate */}
|
||||
@@ -224,7 +232,7 @@ export default function ContentPage() {
|
||||
<div>
|
||||
<label style={labelStyle}>Piattaforme <span style={{ fontWeight: 400, textTransform: 'none', letterSpacing: 0, color: 'var(--ink-muted)', fontSize: '0.75rem' }}>(seleziona una o più)</span></label>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.4rem', marginTop: '0.4rem' }}>
|
||||
{PLATFORMS.map(p => {
|
||||
{availablePlatforms.map(p => {
|
||||
const active = form.platforms.includes(p.value)
|
||||
return (
|
||||
<button key={p.value} type="button" onClick={() => toggleChip('platforms', p.value)} style={{
|
||||
@@ -321,8 +329,8 @@ export default function ContentPage() {
|
||||
</span>
|
||||
)})()}
|
||||
{generated.platform_hint && (
|
||||
<span style={{ fontSize: '0.72rem', fontWeight: 600, padding: '0.2rem 0.5rem', backgroundColor: 'var(--cream-dark)', color: 'var(--ink-muted)' }}>
|
||||
{generated.platform_hint}
|
||||
<span style={{ fontSize: '0.72rem', fontWeight: 500, padding: '0.2rem 0.5rem', backgroundColor: 'var(--cream-dark)', color: 'var(--ink-muted)', borderLeft: '2px solid var(--border)' }}>
|
||||
per {generated.platform_hint}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@@ -381,11 +389,22 @@ function HashtagEditor({ hashtags, onChange, postId }) {
|
||||
const [newTag, setNewTag] = useState('')
|
||||
const [editIdx, setEditIdx] = useState(null)
|
||||
const [editValue, setEditValue] = useState('')
|
||||
const [saving, setSaving] = useState(false)
|
||||
const saveTimer = useRef(null)
|
||||
|
||||
const persistHashtags = (tags) => {
|
||||
clearTimeout(saveTimer.current)
|
||||
saveTimer.current = setTimeout(() => {
|
||||
api.put(`/content/posts/${postId}`, { hashtags: tags }).catch(() => {})
|
||||
}, 500)
|
||||
}
|
||||
|
||||
const updateAndSave = (newTags) => {
|
||||
onChange(newTags)
|
||||
persistHashtags(newTags)
|
||||
}
|
||||
|
||||
const removeTag = (idx) => {
|
||||
const updated = hashtags.filter((_, i) => i !== idx)
|
||||
onChange(updated)
|
||||
updateAndSave(hashtags.filter((_, i) => i !== idx))
|
||||
}
|
||||
|
||||
const addTag = () => {
|
||||
@@ -393,7 +412,7 @@ function HashtagEditor({ hashtags, onChange, postId }) {
|
||||
if (!tag) return
|
||||
if (!tag.startsWith('#')) tag = `#${tag}`
|
||||
if (!hashtags.includes(tag)) {
|
||||
onChange([...hashtags, tag])
|
||||
updateAndSave([...hashtags, tag])
|
||||
}
|
||||
setNewTag('')
|
||||
}
|
||||
@@ -410,18 +429,10 @@ function HashtagEditor({ hashtags, onChange, postId }) {
|
||||
if (!tag.startsWith('#')) tag = `#${tag}`
|
||||
const updated = [...hashtags]
|
||||
updated[editIdx] = tag
|
||||
onChange(updated)
|
||||
updateAndSave(updated)
|
||||
setEditIdx(null)
|
||||
}
|
||||
|
||||
const saveHashtags = async () => {
|
||||
setSaving(true)
|
||||
try {
|
||||
await api.put(`/content/posts/${postId}`, { hashtags })
|
||||
} catch (e) { /* silent */ }
|
||||
setSaving(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ marginBottom: '1rem' }}>
|
||||
<span style={{ ...labelStyle, display: 'block', marginBottom: '0.4rem' }}>Hashtag</span>
|
||||
@@ -447,10 +458,6 @@ function HashtagEditor({ hashtags, onChange, postId }) {
|
||||
onFocus={e => e.target.style.borderColor = 'var(--ink)'} onBlur={e => e.target.style.borderColor = 'var(--border)'} />
|
||||
<button type="button" onClick={addTag} style={{ fontSize: '0.78rem', padding: '0.3rem 0.6rem', backgroundColor: 'var(--cream-dark)', border: 'none', cursor: 'pointer', color: 'var(--ink-light)' }}>+</button>
|
||||
</div>
|
||||
<button type="button" onClick={saveHashtags} disabled={saving}
|
||||
style={{ fontSize: '0.72rem', marginTop: '0.35rem', padding: '0.2rem 0.5rem', backgroundColor: 'transparent', border: '1px solid var(--border)', cursor: 'pointer', color: 'var(--ink-muted)' }}>
|
||||
{saving ? 'Salvataggio…' : 'Salva hashtag'}
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user