feat: mobile UX fixes + Phase C one-click generation

Mobile UX:
- index.css: comprehensive mobile media queries — headings scale down,
  touch targets enforced, grid-2col-mobile collapse class, tablet breakpoint
- ContentArchive/ContentPage: grid minmax uses min(100%, Npx) to prevent
  overflow on small screens
- CharacterForm: visual style + rules editor grids collapse on mobile
- Dashboard: stat cards grid mobile-safe
- Layout: better nav touch targets, footer responsive gap

Phase C — One-Click Generation:
- Backend: GET /api/content/suggestions endpoint — LLM generates 3 topic
  ideas based on character profile and avoids repeating recent posts
- Dashboard: "Suggerimenti per oggi" section loads suggestions on mount,
  each card links to /content with prefilled topic + character
- ContentPage: reads ?topic= and ?character= URL params, auto-fills form
  and auto-triggers generation (one-click flow from Dashboard)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Michele
2026-04-05 01:28:25 +02:00
parent 16c7c4404c
commit 67bc0d2980
7 changed files with 186 additions and 25 deletions

View File

@@ -1,5 +1,5 @@
import { useState, useEffect, useRef } from 'react'
import { Link } from 'react-router-dom'
import { Link, useSearchParams } from 'react-router-dom'
import { api } from '../api'
import { useAuth } from '../AuthContext'
import ConfirmModal from './ConfirmModal'
@@ -38,7 +38,9 @@ const STATUS_COLORS = {
export default function ContentPage() {
const { isPro } = useAuth()
const [searchParams, setSearchParams] = useSearchParams()
const availablePlatforms = isPro ? PLATFORMS : PLATFORMS.filter(p => ['instagram', 'facebook'].includes(p.value))
const autoGenerateRef = useRef(false)
const [characters, setCharacters] = useState([])
const [loading, setLoading] = useState(false)
const [charsLoading, setCharsLoading] = useState(true)
@@ -60,9 +62,32 @@ export default function ContentPage() {
})
useEffect(() => {
api.get('/characters/').then(d => { setCharacters(d); setCharsLoading(false) }).catch(() => setCharsLoading(false))
api.get('/characters/').then(d => {
setCharacters(d)
setCharsLoading(false)
// One-click flow: pre-fill from URL params
const urlTopic = searchParams.get('topic')
const urlCharacter = searchParams.get('character')
if (urlTopic && d.length > 0) {
const charId = urlCharacter || String(d[0].id)
setForm(prev => ({ ...prev, character_id: charId, topic_hint: urlTopic }))
autoGenerateRef.current = true
setSearchParams({}, { replace: true }) // clean URL
}
}).catch(() => setCharsLoading(false))
}, [])
// Auto-generate when arriving from one-click flow
useEffect(() => {
if (autoGenerateRef.current && form.character_id && !charsLoading) {
autoGenerateRef.current = false
// Small delay to let React render the form
setTimeout(() => {
document.querySelector('form')?.requestSubmit()
}, 300)
}
}, [form.character_id, charsLoading])
const toggleChip = (field, value) => {
setForm(prev => {
const arr = prev[field]
@@ -196,7 +221,7 @@ export default function ContentPage() {
</div>
))}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))', gap: '1.25rem' }}>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 300px), 1fr))', gap: '1.25rem' }}>
{/* Generation form */}
<div style={{ backgroundColor: 'var(--surface)', border: '1px solid var(--border)', borderTop: '4px solid var(--accent)', padding: '1.5rem' }}>
<div style={{ marginBottom: '1.25rem' }}>