fix: replace hardcoded site name with dynamic settings

- Add public /api/settings endpoint (force-dynamic, no auth) exposing
  site_name, site_description, footer_copyright, footer_links
- Navbar, login, register pages fetch site_name via useEffect
- Homepage hero and footer read site_name and site_description from DB
- Fix admin settings form silently ignoring API errors on save
This commit is contained in:
2026-05-19 11:30:05 +02:00
parent 9797519e5c
commit ea5fca6561
6 changed files with 68 additions and 11 deletions
+8 -1
View File
@@ -52,12 +52,19 @@ export default function AdminSettingsPage() {
e.preventDefault() e.preventDefault()
setSaving(true) setSaving(true)
setError('') setError('')
setMessage('')
for (const key of ALL_KEYS) { for (const key of ALL_KEYS) {
await fetch('/api/admin/settings', { const res = await fetch('/api/admin/settings', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ key, value: values[key] }), body: JSON.stringify({ key, value: values[key] }),
}) })
if (!res.ok) {
const data = await res.json().catch(() => ({}))
setSaving(false)
setError(data.error || `Errore nel salvare "${key}" (${res.status})`)
return
}
} }
setSaving(false) setSaving(false)
setMessage('Impostazioni salvate!') setMessage('Impostazioni salvate!')
+14
View File
@@ -0,0 +1,14 @@
import { NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
export const dynamic = 'force-dynamic'
const PUBLIC_KEYS = ['site_name', 'site_description', 'footer_copyright', 'footer_links'] as const
export async function GET() {
const rows = await prisma.siteSettings.findMany({
where: { key: { in: [...PUBLIC_KEYS] } },
})
const settings = Object.fromEntries(rows.map((r) => [r.key, r.value]))
return NextResponse.json({ settings })
}
+10 -2
View File
@@ -1,6 +1,6 @@
'use client' 'use client'
import { Suspense, useState } from 'react' import { Suspense, useState, useEffect } from 'react'
import Link from 'next/link' import Link from 'next/link'
import { useRouter, useSearchParams } from 'next/navigation' import { useRouter, useSearchParams } from 'next/navigation'
import { Input } from '@/components/ui/Input' import { Input } from '@/components/ui/Input'
@@ -21,11 +21,19 @@ function LoginForm() {
const [password, setPassword] = useState('') const [password, setPassword] = useState('')
const [error, setError] = useState('') const [error, setError] = useState('')
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [siteName, setSiteName] = useState('ShopX')
const router = useRouter() const router = useRouter()
const searchParams = useSearchParams() const searchParams = useSearchParams()
const redirect = searchParams.get('redirect') || '/' const redirect = searchParams.get('redirect') || '/'
const { refreshUser } = useUser() const { refreshUser } = useUser()
useEffect(() => {
fetch('/api/settings')
.then((r) => r.json())
.then((data) => { if (data.settings?.site_name) setSiteName(data.settings.site_name as string) })
.catch(() => {})
}, [])
async function handleSubmit(e: React.FormEvent) { async function handleSubmit(e: React.FormEvent) {
e.preventDefault() e.preventDefault()
setError('') setError('')
@@ -66,7 +74,7 @@ function LoginForm() {
<div className="w-full max-w-md"> <div className="w-full max-w-md">
<div className="bg-white rounded-lg border border-gray-200 shadow-sm p-8"> <div className="bg-white rounded-lg border border-gray-200 shadow-sm p-8">
<div className="text-center mb-8"> <div className="text-center mb-8">
<Link href="/" className="text-2xl font-bold text-gray-900">ShopX</Link> <Link href="/" className="text-2xl font-bold text-gray-900">{siteName}</Link>
<h1 className="text-xl font-semibold mt-4">Welcome back</h1> <h1 className="text-xl font-semibold mt-4">Welcome back</h1>
<p className="text-gray-600 text-sm mt-1">Sign in to your account</p> <p className="text-gray-600 text-sm mt-1">Sign in to your account</p>
</div> </div>
+13 -4
View File
@@ -14,8 +14,17 @@ async function getFeaturedProducts() {
}) })
} }
async function getSiteSettings() {
const rows = await prisma.siteSettings.findMany({
where: { key: { in: ['site_name', 'site_description'] } },
})
return Object.fromEntries(rows.map((r) => [r.key, r.value as string]))
}
export default async function HomePage() { export default async function HomePage() {
const products = await getFeaturedProducts() const [products, settings] = await Promise.all([getFeaturedProducts(), getSiteSettings()])
const siteName = settings.site_name || 'ShopX'
const siteDescription = settings.site_description || 'Discover our curated collection of products'
return ( return (
<div> <div>
@@ -24,9 +33,9 @@ export default async function HomePage() {
{/* Hero */} {/* Hero */}
<section className="bg-blue-600 text-white py-20"> <section className="bg-blue-600 text-white py-20">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
<h1 className="text-4xl font-bold mb-4">Welcome to ShopX</h1> <h1 className="text-4xl font-bold mb-4">Welcome to {siteName}</h1>
<p className="text-xl text-blue-100 mb-8"> <p className="text-xl text-blue-100 mb-8">
Discover our curated collection of products {siteDescription}
</p> </p>
<Link <Link
href="/products" href="/products"
@@ -90,7 +99,7 @@ export default async function HomePage() {
<footer className="bg-gray-800 text-gray-300 py-8"> <footer className="bg-gray-800 text-gray-300 py-8">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center text-sm"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center text-sm">
<p>&copy; {new Date().getFullYear()} ShopX. All rights reserved.</p> <p>&copy; {new Date().getFullYear()} {siteName}. All rights reserved.</p>
</div> </div>
</footer> </footer>
</div> </div>
+11 -3
View File
@@ -1,6 +1,6 @@
'use client' 'use client'
import { useState } from 'react' import { useState, useEffect } from 'react'
import Link from 'next/link' import Link from 'next/link'
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
import { Input } from '@/components/ui/Input' import { Input } from '@/components/ui/Input'
@@ -14,9 +14,17 @@ export default function RegisterPage() {
const [password, setPassword] = useState('') const [password, setPassword] = useState('')
const [error, setError] = useState('') const [error, setError] = useState('')
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [siteName, setSiteName] = useState('ShopX')
const router = useRouter() const router = useRouter()
const { refreshUser } = useUser() const { refreshUser } = useUser()
useEffect(() => {
fetch('/api/settings')
.then((r) => r.json())
.then((data) => { if (data.settings?.site_name) setSiteName(data.settings.site_name as string) })
.catch(() => {})
}, [])
async function handleSubmit(e: React.FormEvent) { async function handleSubmit(e: React.FormEvent) {
e.preventDefault() e.preventDefault()
setError('') setError('')
@@ -50,9 +58,9 @@ export default function RegisterPage() {
<div className="w-full max-w-md"> <div className="w-full max-w-md">
<div className="bg-white rounded-lg border border-gray-200 shadow-sm p-8"> <div className="bg-white rounded-lg border border-gray-200 shadow-sm p-8">
<div className="text-center mb-8"> <div className="text-center mb-8">
<Link href="/" className="text-2xl font-bold text-gray-900">ShopX</Link> <Link href="/" className="text-2xl font-bold text-gray-900">{siteName}</Link>
<h1 className="text-xl font-semibold mt-4">Create Account</h1> <h1 className="text-xl font-semibold mt-4">Create Account</h1>
<p className="text-gray-600 text-sm mt-1">Join ShopX today</p> <p className="text-gray-600 text-sm mt-1">Join {siteName} today</p>
</div> </div>
{error && <Alert variant="error" className="mb-4">{error}</Alert>} {error && <Alert variant="error" className="mb-4">{error}</Alert>}
+12 -1
View File
@@ -7,6 +7,7 @@ import { useUser } from '@/context/UserContext'
export function Navbar() { export function Navbar() {
const [cartCount, setCartCount] = useState(0) const [cartCount, setCartCount] = useState(0)
const [siteName, setSiteName] = useState('ShopX')
const { user, refreshUser } = useUser() const { user, refreshUser } = useUser()
const router = useRouter() const router = useRouter()
@@ -16,6 +17,16 @@ export function Navbar() {
setCartCount(count) setCartCount(count)
}, []) }, [])
useEffect(() => {
fetch('/api/settings')
.then((r) => r.json())
.then((data) => {
const name = data.settings?.site_name
if (name) setSiteName(name as string)
})
.catch(() => {})
}, [])
async function handleLogout() { async function handleLogout() {
await fetch('/api/auth/logout', { method: 'POST' }) await fetch('/api/auth/logout', { method: 'POST' })
await refreshUser() await refreshUser()
@@ -28,7 +39,7 @@ export function Navbar() {
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex items-center justify-between h-16"> <div className="flex items-center justify-between h-16">
<Link href="/" className="text-xl font-bold text-gray-900"> <Link href="/" className="text-xl font-bold text-gray-900">
ShopX {siteName}
</Link> </Link>
<nav className="flex items-center gap-6 text-sm"> <nav className="flex items-center gap-6 text-sm">