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:
@@ -52,12 +52,19 @@ export default function AdminSettingsPage() {
|
||||
e.preventDefault()
|
||||
setSaving(true)
|
||||
setError('')
|
||||
setMessage('')
|
||||
for (const key of ALL_KEYS) {
|
||||
await fetch('/api/admin/settings', {
|
||||
const res = await fetch('/api/admin/settings', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
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)
|
||||
setMessage('Impostazioni salvate!')
|
||||
|
||||
@@ -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 })
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { Suspense, useState } from 'react'
|
||||
import { Suspense, useState, useEffect } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { useRouter, useSearchParams } from 'next/navigation'
|
||||
import { Input } from '@/components/ui/Input'
|
||||
@@ -21,11 +21,19 @@ function LoginForm() {
|
||||
const [password, setPassword] = useState('')
|
||||
const [error, setError] = useState('')
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [siteName, setSiteName] = useState('ShopX')
|
||||
const router = useRouter()
|
||||
const searchParams = useSearchParams()
|
||||
const redirect = searchParams.get('redirect') || '/'
|
||||
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) {
|
||||
e.preventDefault()
|
||||
setError('')
|
||||
@@ -66,7 +74,7 @@ function LoginForm() {
|
||||
<div className="w-full max-w-md">
|
||||
<div className="bg-white rounded-lg border border-gray-200 shadow-sm p-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>
|
||||
<p className="text-gray-600 text-sm mt-1">Sign in to your account</p>
|
||||
</div>
|
||||
|
||||
+13
-4
@@ -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() {
|
||||
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 (
|
||||
<div>
|
||||
@@ -24,9 +33,9 @@ export default async function HomePage() {
|
||||
{/* Hero */}
|
||||
<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">
|
||||
<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">
|
||||
Discover our curated collection of products
|
||||
{siteDescription}
|
||||
</p>
|
||||
<Link
|
||||
href="/products"
|
||||
@@ -90,7 +99,7 @@ export default async function HomePage() {
|
||||
|
||||
<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">
|
||||
<p>© {new Date().getFullYear()} ShopX. All rights reserved.</p>
|
||||
<p>© {new Date().getFullYear()} {siteName}. All rights reserved.</p>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { useState, useEffect } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { Input } from '@/components/ui/Input'
|
||||
@@ -14,9 +14,17 @@ export default function RegisterPage() {
|
||||
const [password, setPassword] = useState('')
|
||||
const [error, setError] = useState('')
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [siteName, setSiteName] = useState('ShopX')
|
||||
const router = useRouter()
|
||||
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) {
|
||||
e.preventDefault()
|
||||
setError('')
|
||||
@@ -50,9 +58,9 @@ export default function RegisterPage() {
|
||||
<div className="w-full max-w-md">
|
||||
<div className="bg-white rounded-lg border border-gray-200 shadow-sm p-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>
|
||||
<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>
|
||||
|
||||
{error && <Alert variant="error" className="mb-4">{error}</Alert>}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useUser } from '@/context/UserContext'
|
||||
|
||||
export function Navbar() {
|
||||
const [cartCount, setCartCount] = useState(0)
|
||||
const [siteName, setSiteName] = useState('ShopX')
|
||||
const { user, refreshUser } = useUser()
|
||||
const router = useRouter()
|
||||
|
||||
@@ -16,6 +17,16 @@ export function Navbar() {
|
||||
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() {
|
||||
await fetch('/api/auth/logout', { method: 'POST' })
|
||||
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="flex items-center justify-between h-16">
|
||||
<Link href="/" className="text-xl font-bold text-gray-900">
|
||||
ShopX
|
||||
{siteName}
|
||||
</Link>
|
||||
|
||||
<nav className="flex items-center gap-6 text-sm">
|
||||
|
||||
Reference in New Issue
Block a user