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:
@@ -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' }}>
|
||||
|
||||
Reference in New Issue
Block a user