feat: daily suggestion cache, saved ideas (Swipe File), remove Piani Attivi
Backend:
- Suggestions cached in DB per user, regenerated only after 24h
- ?force=true parameter to regenerate on demand
- New endpoints: GET/POST/DELETE /content/ideas for saved ideas
- POST /content/ideas/{id}/mark-used to track usage
Frontend:
- Dashboard: suggestions loaded from cache, not regenerated on every visit
- Dashboard: "Salva idea" button on each suggestion card
- Dashboard: "Dammi altri suggerimenti" CTA to force regeneration
- Dashboard: removed "Piani Attivi" stat card
- SavedIdeas page: list saved ideas, add new, delete, generate from idea
- Sidebar: added "Idee" nav item after "Contenuti"
- App.jsx: added /ideas route
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -342,34 +342,14 @@ def approve_post(
|
||||
return post
|
||||
|
||||
|
||||
@router.get("/suggestions")
|
||||
def get_topic_suggestions(
|
||||
character_id: int | None = Query(None),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""Suggest 3 content topics based on character profile and recent posts."""
|
||||
if character_id:
|
||||
character = (
|
||||
db.query(Character)
|
||||
.filter(Character.id == character_id, Character.user_id == current_user.id)
|
||||
.first()
|
||||
)
|
||||
else:
|
||||
character = (
|
||||
db.query(Character)
|
||||
.filter(Character.user_id == current_user.id, Character.is_active == True)
|
||||
.first()
|
||||
)
|
||||
if not character:
|
||||
return {"suggestions": [], "character_id": None}
|
||||
|
||||
def _generate_suggestions(db: Session, current_user: User, character) -> list[str]:
|
||||
"""Internal: call LLM to generate topic suggestions."""
|
||||
provider_name = _get_setting(db, "llm_provider", current_user.id)
|
||||
api_key = _get_setting(db, "llm_api_key", current_user.id)
|
||||
model = _get_setting(db, "llm_model", current_user.id)
|
||||
|
||||
if not provider_name or not api_key:
|
||||
return {"suggestions": [], "character_id": character.id, "needs_setup": True}
|
||||
return []
|
||||
|
||||
recent_posts = (
|
||||
db.query(Post)
|
||||
@@ -406,12 +386,174 @@ def get_topic_suggestions(
|
||||
try:
|
||||
result = llm.generate(prompt, system=system_prompt)
|
||||
lines = [line.strip() for line in result.strip().splitlines() if line.strip()]
|
||||
suggestions = lines[:3]
|
||||
return lines[:3]
|
||||
except Exception:
|
||||
suggestions = []
|
||||
return []
|
||||
|
||||
|
||||
@router.get("/suggestions")
|
||||
def get_topic_suggestions(
|
||||
force: bool = Query(False),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""Get cached daily suggestions or generate new ones. Use force=true to regenerate."""
|
||||
character = (
|
||||
db.query(Character)
|
||||
.filter(Character.user_id == current_user.id, Character.is_active == True)
|
||||
.first()
|
||||
)
|
||||
if not character:
|
||||
return {"suggestions": [], "character_id": None}
|
||||
|
||||
provider_name = _get_setting(db, "llm_provider", current_user.id)
|
||||
api_key = _get_setting(db, "llm_api_key", current_user.id)
|
||||
if not provider_name or not api_key:
|
||||
return {"suggestions": [], "character_id": character.id, "needs_setup": True}
|
||||
|
||||
today = date.today().isoformat()
|
||||
|
||||
# Check cache
|
||||
if not force:
|
||||
cached = _get_setting(db, "daily_suggestions", current_user.id)
|
||||
if cached and isinstance(cached, dict) and cached.get("date") == today:
|
||||
return {
|
||||
"suggestions": cached.get("suggestions", []),
|
||||
"character_id": cached.get("character_id", character.id),
|
||||
"character_name": cached.get("character_name", character.name),
|
||||
"cached": True,
|
||||
}
|
||||
|
||||
# Generate new suggestions
|
||||
suggestions = _generate_suggestions(db, current_user, character)
|
||||
|
||||
# Save to cache
|
||||
cache_data = {
|
||||
"date": today,
|
||||
"suggestions": suggestions,
|
||||
"character_id": character.id,
|
||||
"character_name": character.name,
|
||||
}
|
||||
existing = (
|
||||
db.query(SystemSetting)
|
||||
.filter(SystemSetting.key == "daily_suggestions", SystemSetting.user_id == current_user.id)
|
||||
.first()
|
||||
)
|
||||
if existing:
|
||||
existing.value = cache_data
|
||||
existing.updated_at = datetime.utcnow()
|
||||
else:
|
||||
db.add(SystemSetting(key="daily_suggestions", value=cache_data, user_id=current_user.id))
|
||||
db.commit()
|
||||
|
||||
return {
|
||||
"suggestions": suggestions,
|
||||
"character_id": character.id,
|
||||
"character_name": character.name,
|
||||
"cached": False,
|
||||
}
|
||||
|
||||
|
||||
# === Saved Ideas (Swipe File) ===
|
||||
|
||||
@router.get("/ideas")
|
||||
def get_saved_ideas(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""Get all saved ideas for the user."""
|
||||
setting = (
|
||||
db.query(SystemSetting)
|
||||
.filter(SystemSetting.key == "saved_ideas", SystemSetting.user_id == current_user.id)
|
||||
.first()
|
||||
)
|
||||
ideas = setting.value if setting and isinstance(setting.value, list) else []
|
||||
return {"ideas": ideas, "total": len(ideas)}
|
||||
|
||||
|
||||
@router.post("/ideas")
|
||||
def save_idea(
|
||||
data: dict,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""Save an idea for later use."""
|
||||
text = data.get("text", "").strip()
|
||||
if not text:
|
||||
raise HTTPException(status_code=400, detail="Text is required")
|
||||
|
||||
setting = (
|
||||
db.query(SystemSetting)
|
||||
.filter(SystemSetting.key == "saved_ideas", SystemSetting.user_id == current_user.id)
|
||||
.first()
|
||||
)
|
||||
ideas = setting.value if setting and isinstance(setting.value, list) else []
|
||||
|
||||
new_idea = {
|
||||
"id": str(uuid.uuid4())[:8],
|
||||
"text": text,
|
||||
"note": data.get("note", ""),
|
||||
"saved_at": datetime.utcnow().isoformat(),
|
||||
"used": False,
|
||||
}
|
||||
ideas.insert(0, new_idea)
|
||||
|
||||
if setting:
|
||||
setting.value = ideas
|
||||
setting.updated_at = datetime.utcnow()
|
||||
else:
|
||||
db.add(SystemSetting(key="saved_ideas", value=ideas, user_id=current_user.id))
|
||||
db.commit()
|
||||
return new_idea
|
||||
|
||||
|
||||
@router.delete("/ideas/{idea_id}")
|
||||
def delete_idea(
|
||||
idea_id: str,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""Delete a saved idea."""
|
||||
setting = (
|
||||
db.query(SystemSetting)
|
||||
.filter(SystemSetting.key == "saved_ideas", SystemSetting.user_id == current_user.id)
|
||||
.first()
|
||||
)
|
||||
if not setting or not isinstance(setting.value, list):
|
||||
raise HTTPException(status_code=404, detail="Idea not found")
|
||||
|
||||
ideas = [i for i in setting.value if i.get("id") != idea_id]
|
||||
if len(ideas) == len(setting.value):
|
||||
raise HTTPException(status_code=404, detail="Idea not found")
|
||||
|
||||
setting.value = ideas
|
||||
setting.updated_at = datetime.utcnow()
|
||||
db.commit()
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
@router.post("/ideas/{idea_id}/mark-used")
|
||||
def mark_idea_used(
|
||||
idea_id: str,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""Mark an idea as used."""
|
||||
setting = (
|
||||
db.query(SystemSetting)
|
||||
.filter(SystemSetting.key == "saved_ideas", SystemSetting.user_id == current_user.id)
|
||||
.first()
|
||||
)
|
||||
if not setting or not isinstance(setting.value, list):
|
||||
raise HTTPException(status_code=404, detail="Idea not found")
|
||||
|
||||
for idea in setting.value:
|
||||
if idea.get("id") == idea_id:
|
||||
idea["used"] = True
|
||||
break
|
||||
else:
|
||||
raise HTTPException(status_code=404, detail="Idea not found")
|
||||
|
||||
setting.updated_at = datetime.utcnow()
|
||||
db.commit()
|
||||
return {"ok": True}
|
||||
|
||||
@@ -18,6 +18,7 @@ import SocialAccounts from './components/SocialAccounts'
|
||||
import CommentsQueue from './components/CommentsQueue'
|
||||
import SettingsPage from './components/SettingsPage'
|
||||
import EditorialCalendar from './components/EditorialCalendar'
|
||||
import SavedIdeas from './components/SavedIdeas'
|
||||
import AdminSettings from './components/AdminSettings'
|
||||
import LandingPage from './components/LandingPage'
|
||||
import CookieBanner from './components/CookieBanner'
|
||||
@@ -52,6 +53,7 @@ export default function App() {
|
||||
<Route path="/characters/:id/edit" element={<CharacterForm />} />
|
||||
<Route path="/content" element={<ContentPage />} />
|
||||
<Route path="/content/archive" element={<ContentArchive />} />
|
||||
<Route path="/ideas" element={<SavedIdeas />} />
|
||||
<Route path="/affiliates" element={<AffiliateList />} />
|
||||
<Route path="/affiliates/new" element={<AffiliateForm />} />
|
||||
<Route path="/affiliates/:id/edit" element={<AffiliateForm />} />
|
||||
|
||||
@@ -38,7 +38,7 @@ export default function Dashboard() {
|
||||
setRecentPosts(posts.slice(0, 5))
|
||||
setProviderStatus(providers)
|
||||
setLoading(false)
|
||||
// Load suggestions if LLM is configured
|
||||
// Load cached suggestions (won't regenerate if already generated today)
|
||||
if (providers?.llm?.configured && chars.length > 0) {
|
||||
setSuggestionsLoading(true)
|
||||
api.get('/content/suggestions')
|
||||
@@ -106,7 +106,6 @@ export default function Dashboard() {
|
||||
<StatCard label="Schedulati" value={loading ? '—' : stats.scheduled} sub="in coda" accent="#10B981" delay={0.2} />
|
||||
<StatCard label="Commenti" value={loading ? '—' : stats.pendingComments} sub="in attesa" accent="#8B5CF6" delay={0.15} />
|
||||
<StatCard label="Link Affiliati" value={loading ? '—' : stats.affiliates} accent="#F59E0B" delay={0.25} />
|
||||
<StatCard label="Piani Attivi" value={loading ? '—' : stats.plans} accent="#14B8A6" delay={0.3} />
|
||||
</div>
|
||||
|
||||
{/* ── Provider status ─────────────────────────────────────── */}
|
||||
@@ -153,29 +152,68 @@ export default function Dashboard() {
|
||||
<span style={{ fontSize: '0.85rem', color: 'var(--ink-muted)' }}>Genero idee per te...</span>
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(min(100%, 280px), 1fr))', gap: '0.75rem' }}>
|
||||
{suggestions.suggestions.map((topic, i) => (
|
||||
<Link
|
||||
key={i}
|
||||
to={`/content?topic=${encodeURIComponent(topic)}&character=${suggestions.character_id}`}
|
||||
style={{
|
||||
display: 'block', padding: '1rem 1.25rem',
|
||||
backgroundColor: 'var(--surface)', border: '1px solid var(--border)',
|
||||
borderLeft: '4px solid var(--accent)', textDecoration: 'none',
|
||||
transition: 'border-color 0.15s, background-color 0.15s',
|
||||
<>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(min(100%, 280px), 1fr))', gap: '0.75rem' }}>
|
||||
{suggestions.suggestions.map((topic, i) => (
|
||||
<div key={i} style={{
|
||||
padding: '1rem 1.25rem', backgroundColor: 'var(--surface)',
|
||||
border: '1px solid var(--border)', borderLeft: '4px solid var(--accent)',
|
||||
transition: 'border-color 0.15s',
|
||||
}}>
|
||||
<p style={{ fontSize: '0.88rem', color: 'var(--ink)', margin: '0 0 0.5rem', lineHeight: 1.5, fontWeight: 500 }}>
|
||||
{topic}
|
||||
</p>
|
||||
<div style={{ display: 'flex', gap: '0.75rem', alignItems: 'center' }}>
|
||||
<Link
|
||||
to={`/content?topic=${encodeURIComponent(topic)}&character=${suggestions.character_id}`}
|
||||
style={{ fontSize: '0.72rem', color: 'var(--accent)', fontWeight: 600, textDecoration: 'none' }}
|
||||
>
|
||||
Genera →
|
||||
</Link>
|
||||
<button
|
||||
onClick={async () => {
|
||||
try {
|
||||
await api.post('/content/ideas', { text: topic })
|
||||
// Brief visual feedback
|
||||
const btn = document.getElementById(`save-idea-${i}`)
|
||||
if (btn) { btn.textContent = '✓ Salvata'; btn.style.color = 'var(--success)' }
|
||||
} catch {}
|
||||
}}
|
||||
id={`save-idea-${i}`}
|
||||
style={{
|
||||
fontSize: '0.72rem', color: 'var(--ink-muted)', fontWeight: 500,
|
||||
background: 'none', border: 'none', cursor: 'pointer', padding: 0,
|
||||
transition: 'color 0.15s',
|
||||
}}
|
||||
>
|
||||
Salva idea
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: '0.5rem' }}>
|
||||
<button
|
||||
onClick={async () => {
|
||||
setSuggestionsLoading(true)
|
||||
try {
|
||||
const data = await api.get('/content/suggestions?force=true')
|
||||
setSuggestions(data)
|
||||
} catch {}
|
||||
setSuggestionsLoading(false)
|
||||
}}
|
||||
onMouseEnter={e => { e.currentTarget.style.backgroundColor = 'var(--accent-light)'; e.currentTarget.style.borderColor = 'var(--accent)' }}
|
||||
onMouseLeave={e => { e.currentTarget.style.backgroundColor = 'var(--surface)'; e.currentTarget.style.borderColor = 'var(--border)' }}
|
||||
style={{
|
||||
fontSize: '0.75rem', color: 'var(--ink-muted)', fontWeight: 500,
|
||||
background: 'none', border: 'none', cursor: 'pointer', padding: '0.25rem 0',
|
||||
transition: 'color 0.15s',
|
||||
}}
|
||||
onMouseEnter={e => e.target.style.color = 'var(--accent)'}
|
||||
onMouseLeave={e => e.target.style.color = 'var(--ink-muted)'}
|
||||
>
|
||||
<p style={{ fontSize: '0.88rem', color: 'var(--ink)', margin: 0, lineHeight: 1.5, fontWeight: 500 }}>
|
||||
{topic}
|
||||
</p>
|
||||
<span style={{ fontSize: '0.72rem', color: 'var(--accent)', fontWeight: 600, marginTop: '0.4rem', display: 'inline-block' }}>
|
||||
Genera →
|
||||
</span>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
Dammi altri suggerimenti
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -9,6 +9,7 @@ const nav = [
|
||||
{ to: '/', label: 'Dashboard' },
|
||||
{ to: '/characters',label: 'Personaggi' },
|
||||
{ to: '/content', label: 'Contenuti' },
|
||||
{ to: '/ideas', label: 'Idee' },
|
||||
{ to: '/affiliates',label: 'Link Affiliati' },
|
||||
{ to: '/editorial', label: 'Pianificazione' },
|
||||
{ to: '/schedule', label: 'Schedulazione' },
|
||||
|
||||
159
frontend/src/components/SavedIdeas.jsx
Normal file
159
frontend/src/components/SavedIdeas.jsx
Normal file
@@ -0,0 +1,159 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { api } from '../api'
|
||||
import ConfirmModal from './ConfirmModal'
|
||||
|
||||
export default function SavedIdeas() {
|
||||
const [ideas, setIdeas] = useState([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [newIdea, setNewIdea] = useState('')
|
||||
const [deleteTarget, setDeleteTarget] = useState(null)
|
||||
|
||||
useEffect(() => { loadIdeas() }, [])
|
||||
|
||||
const loadIdeas = async () => {
|
||||
setLoading(true)
|
||||
try {
|
||||
const data = await api.get('/content/ideas')
|
||||
setIdeas(data.ideas || [])
|
||||
} catch {} finally { setLoading(false) }
|
||||
}
|
||||
|
||||
const handleAdd = async () => {
|
||||
const text = newIdea.trim()
|
||||
if (!text) return
|
||||
try {
|
||||
await api.post('/content/ideas', { text })
|
||||
setNewIdea('')
|
||||
loadIdeas()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
const handleDelete = async () => {
|
||||
if (!deleteTarget) return
|
||||
try {
|
||||
await api.delete(`/content/ideas/${deleteTarget}`)
|
||||
setDeleteTarget(null)
|
||||
loadIdeas()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
const handleMarkUsed = async (id) => {
|
||||
try {
|
||||
await api.post(`/content/ideas/${id}/mark-used`)
|
||||
loadIdeas()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
const formatDate = (d) => {
|
||||
if (!d) return ''
|
||||
const diff = Date.now() - new Date(d).getTime()
|
||||
const mins = Math.floor(diff / 60000)
|
||||
if (mins < 60) return `${mins}m fa`
|
||||
const hours = Math.floor(mins / 60)
|
||||
if (hours < 24) return `${hours}h fa`
|
||||
const days = Math.floor(hours / 24)
|
||||
return `${days}g fa`
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ animation: 'fade-up 0.5s ease-out both' }}>
|
||||
<div style={{ marginBottom: '2rem' }}>
|
||||
<span className="editorial-tag">Idee</span>
|
||||
<div className="editorial-line" />
|
||||
<h2 style={{ fontFamily: "'Fraunces', serif", fontSize: '1.75rem', fontWeight: 600, letterSpacing: '-0.02em', color: 'var(--ink)', margin: '0.5rem 0 0.3rem' }}>
|
||||
Idee Salvate
|
||||
</h2>
|
||||
<p style={{ fontSize: '0.875rem', color: 'var(--ink-muted)', margin: 0 }}>
|
||||
Cattura spunti e topic interessanti. Usali quando vuoi per generare contenuti.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Add new idea */}
|
||||
<div style={{ display: 'flex', gap: '0.5rem', marginBottom: '1.5rem' }}>
|
||||
<input
|
||||
type="text" value={newIdea} onChange={e => setNewIdea(e.target.value)}
|
||||
onKeyDown={e => { if (e.key === 'Enter') handleAdd() }}
|
||||
placeholder="Scrivi un'idea per un post..."
|
||||
style={inputStyle}
|
||||
/>
|
||||
<button onClick={handleAdd} style={btnPrimary}>Aggiungi</button>
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
<div style={{ display: 'flex', justifyContent: 'center', padding: '3rem 0' }}>
|
||||
<div style={{ width: 32, height: 32, border: '2px solid var(--border)', borderTopColor: 'var(--accent)', borderRadius: '50%', animation: 'spin 0.8s linear infinite' }} />
|
||||
</div>
|
||||
) : ideas.length === 0 ? (
|
||||
<div style={{ textAlign: 'center', padding: '4rem 1rem', backgroundColor: 'var(--surface)', border: '1px solid var(--border)' }}>
|
||||
<div style={{ fontSize: '2.5rem', color: 'var(--border-strong)', marginBottom: '1rem' }}>✦</div>
|
||||
<p style={{ fontFamily: "'Fraunces', serif", fontSize: '1rem', color: 'var(--ink)', margin: '0 0 0.5rem' }}>Nessuna idea salvata</p>
|
||||
<p style={{ fontSize: '0.82rem', color: 'var(--ink-muted)', margin: 0 }}>
|
||||
Salva idee dalla Dashboard o aggiungile qui per usarle quando vuoi.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
|
||||
{ideas.map(idea => (
|
||||
<div key={idea.id} style={{
|
||||
display: 'flex', alignItems: 'center', gap: '0.75rem',
|
||||
padding: '0.875rem 1.25rem', backgroundColor: 'var(--surface)',
|
||||
border: '1px solid var(--border)',
|
||||
borderLeft: idea.used ? '4px solid var(--border-strong)' : '4px solid var(--accent)',
|
||||
opacity: idea.used ? 0.6 : 1,
|
||||
}}>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<p style={{ fontSize: '0.88rem', color: 'var(--ink)', margin: 0, fontWeight: 500 }}>
|
||||
{idea.text}
|
||||
</p>
|
||||
<div style={{ display: 'flex', gap: '0.5rem', alignItems: 'center', marginTop: '0.3rem' }}>
|
||||
<span style={{ fontSize: '0.7rem', color: 'var(--ink-muted)' }}>{formatDate(idea.saved_at)}</span>
|
||||
{idea.used && (
|
||||
<span style={{ fontSize: '0.65rem', fontWeight: 700, padding: '0.1rem 0.35rem', backgroundColor: '#FFFBEB', color: '#D97706' }}>
|
||||
USATA
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: '0.5rem', flexShrink: 0 }}>
|
||||
<Link
|
||||
to={`/content?topic=${encodeURIComponent(idea.text)}`}
|
||||
onClick={() => handleMarkUsed(idea.id)}
|
||||
style={{ fontSize: '0.75rem', color: 'var(--accent)', fontWeight: 600, textDecoration: 'none', whiteSpace: 'nowrap' }}
|
||||
>
|
||||
Genera →
|
||||
</Link>
|
||||
<button onClick={() => setDeleteTarget(idea.id)}
|
||||
style={{ fontSize: '0.75rem', color: 'var(--error)', background: 'none', border: 'none', cursor: 'pointer', padding: 0 }}>
|
||||
Elimina
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ConfirmModal
|
||||
open={deleteTarget !== null}
|
||||
title="Elimina idea"
|
||||
message="Sei sicuro di voler eliminare questa idea?"
|
||||
confirmLabel="Elimina"
|
||||
confirmStyle="danger"
|
||||
onConfirm={handleDelete}
|
||||
onCancel={() => setDeleteTarget(null)}
|
||||
/>
|
||||
<style>{`@keyframes spin { to { transform: rotate(360deg) } }`}</style>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const inputStyle = {
|
||||
flex: 1, padding: '0.625rem 0.875rem', border: '1px solid var(--border)',
|
||||
fontSize: '0.875rem', color: 'var(--ink)', backgroundColor: 'var(--surface)',
|
||||
outline: 'none', boxSizing: 'border-box', fontFamily: "'DM Sans', sans-serif",
|
||||
}
|
||||
const btnPrimary = {
|
||||
padding: '0.6rem 1.25rem', backgroundColor: 'var(--ink)', color: 'white',
|
||||
fontFamily: "'DM Sans', sans-serif", fontWeight: 600, fontSize: '0.875rem',
|
||||
border: 'none', cursor: 'pointer', whiteSpace: 'nowrap',
|
||||
}
|
||||
Reference in New Issue
Block a user