Site içerik yönetimi
This commit is contained in:
@@ -1,42 +0,0 @@
|
|||||||
"use server"
|
|
||||||
|
|
||||||
import { createClient } from "@/lib/supabase-server"
|
|
||||||
import { revalidatePath } from "next/cache"
|
|
||||||
|
|
||||||
export async function updateSiteSettings(data: {
|
|
||||||
site_title: string
|
|
||||||
site_description: string
|
|
||||||
contact_email: string
|
|
||||||
contact_phone: string
|
|
||||||
currency: string
|
|
||||||
}) {
|
|
||||||
const supabase = createClient()
|
|
||||||
|
|
||||||
// Check admin is already handled by RLS on database level, but we can double check here
|
|
||||||
const { data: { user } } = await supabase.auth.getUser()
|
|
||||||
if (!user) return { error: "Oturum açmanız gerekiyor." }
|
|
||||||
|
|
||||||
// We update the single row where id is likely 1 or just the first row
|
|
||||||
// Since we initialized it with one row, we can just update match on something true or fetch id first.
|
|
||||||
// Easier: Update all rows (there should only be one) or fetch the specific ID first.
|
|
||||||
|
|
||||||
// Let's first get the ID just to be precise
|
|
||||||
const { data: existing } = await supabase.from('site_settings').select('id').single()
|
|
||||||
|
|
||||||
if (!existing) {
|
|
||||||
return { error: "Ayarlar bulunamadı." }
|
|
||||||
}
|
|
||||||
|
|
||||||
const { error } = await supabase
|
|
||||||
.from('site_settings')
|
|
||||||
.update(data)
|
|
||||||
.eq('id', existing.id)
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
return { error: "Ayarlar güncellenemedi: " + error.message }
|
|
||||||
}
|
|
||||||
|
|
||||||
revalidatePath("/dashboard/settings")
|
|
||||||
revalidatePath("/") // Revalidate home as it might use these settings
|
|
||||||
return { success: true }
|
|
||||||
}
|
|
||||||
@@ -1,28 +1,62 @@
|
|||||||
import { createClient } from "@/lib/supabase-server"
|
import { createClient } from "@/lib/supabase-server"
|
||||||
import { SettingsTabs } from "@/components/dashboard/settings-tabs"
|
import { SettingsTabs } from "@/components/dashboard/settings-tabs"
|
||||||
import { getSmsSettings } from "@/lib/sms/actions"
|
import { getSmsSettings } from "@/lib/sms/actions"
|
||||||
|
import { SiteContent } from "@/types/cms"
|
||||||
|
|
||||||
export default async function SettingsPage() {
|
export default async function SettingsPage() {
|
||||||
const supabase = createClient()
|
const supabase = createClient()
|
||||||
|
|
||||||
// Fetch site settings
|
// Fetch SMS settings
|
||||||
const { data: siteSettings } = await supabase
|
|
||||||
.from('site_settings')
|
|
||||||
.select('*')
|
|
||||||
.single()
|
|
||||||
|
|
||||||
// Fetch SMS settings (server-side call to our action/db)
|
|
||||||
// Note: getSmsSettings is an action that checks for admin, which is fine.
|
|
||||||
// However, since we are in a server component with a Supabase client, we could also fetch directly if we had admin client.
|
|
||||||
// But let's use the clean action or direct logic.
|
|
||||||
// Actually, calling server action from server component is fine, but we need to handle the return structure.
|
|
||||||
const smsResponse = await getSmsSettings()
|
const smsResponse = await getSmsSettings()
|
||||||
const smsSettings = smsResponse.data || null
|
const smsSettings = smsResponse.data || null
|
||||||
|
|
||||||
|
// Fetch Users (Profiles)
|
||||||
|
const { data: profiles } = await supabase
|
||||||
|
.from("profiles")
|
||||||
|
.select("*")
|
||||||
|
.order("created_at", { ascending: false })
|
||||||
|
|
||||||
|
// Fetch Site Contents (CMS)
|
||||||
|
const { data: contents } = await supabase
|
||||||
|
.from('site_contents')
|
||||||
|
.select('*')
|
||||||
|
.order('key')
|
||||||
|
|
||||||
|
// Define default contents for CMS
|
||||||
|
const DEFAULT_CONTENTS: SiteContent[] = [
|
||||||
|
// General
|
||||||
|
{ key: 'site_title', value: 'ParaKasa', type: 'text', section: 'general' },
|
||||||
|
{ key: 'site_description', value: '', type: 'long_text', section: 'general' },
|
||||||
|
{ key: 'site_logo', value: '', type: 'image_url', section: 'general' },
|
||||||
|
|
||||||
|
// Contact
|
||||||
|
{ key: 'contact_phone', value: '', type: 'text', section: 'contact' },
|
||||||
|
{ key: 'contact_email', value: '', type: 'text', section: 'contact' },
|
||||||
|
{ key: 'contact_address', value: '', type: 'long_text', section: 'contact' },
|
||||||
|
{ key: 'social_instagram', value: '', type: 'text', section: 'contact' },
|
||||||
|
{ key: 'social_youtube', value: '', type: 'text', section: 'contact' },
|
||||||
|
{ key: 'social_tiktok', value: '', type: 'text', section: 'contact' },
|
||||||
|
{ key: 'contact_map_embed', value: '', type: 'html', section: 'contact' },
|
||||||
|
]
|
||||||
|
|
||||||
|
// Merge default contents with existing contents
|
||||||
|
const mergedContents = [...(contents as SiteContent[] || [])]
|
||||||
|
const existingKeys = new Set(mergedContents.map(c => c.key))
|
||||||
|
|
||||||
|
DEFAULT_CONTENTS.forEach(item => {
|
||||||
|
if (!existingKeys.has(item.key)) {
|
||||||
|
mergedContents.push(item)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex-1 space-y-4 p-8 pt-6">
|
<div className="flex-1 space-y-4 p-8 pt-6">
|
||||||
<h2 className="text-3xl font-bold tracking-tight">Ayarlar</h2>
|
<h2 className="text-3xl font-bold tracking-tight">Ayarlar</h2>
|
||||||
<SettingsTabs siteSettings={siteSettings} smsSettings={smsSettings} />
|
<SettingsTabs
|
||||||
|
smsSettings={smsSettings}
|
||||||
|
users={profiles || []}
|
||||||
|
contents={mergedContents}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,14 +6,14 @@ import "./globals.css";
|
|||||||
const inter = Inter({ subsets: ["latin"] });
|
const inter = Inter({ subsets: ["latin"] });
|
||||||
const outfit = Outfit({ subsets: ["latin"], variable: "--font-outfit" });
|
const outfit = Outfit({ subsets: ["latin"], variable: "--font-outfit" });
|
||||||
|
|
||||||
import { getSiteSettings } from "@/lib/site-settings";
|
import { getSiteContents } from "@/lib/data";
|
||||||
|
|
||||||
export async function generateMetadata() {
|
export async function generateMetadata() {
|
||||||
const settings = await getSiteSettings();
|
const settings = await getSiteContents();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: settings?.site_title || "ParaKasa - Premium Çelik Kasalar",
|
title: settings.site_title || "ParaKasa - Premium Çelik Kasalar",
|
||||||
description: settings?.site_description || "Eviniz ve iş yeriniz için en yüksek güvenlikli çelik kasa ve para sayma çözümleri.",
|
description: settings.site_description || "Eviniz ve iş yeriniz için en yüksek güvenlikli çelik kasa ve para sayma çözümleri.",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,32 +1,56 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||||
import { SiteSettingsForm, SettingsFormValues } from "@/components/dashboard/site-settings-form"
|
|
||||||
import { SmsSettingsForm } from "@/components/dashboard/sms-settings-form"
|
import { SmsSettingsForm } from "@/components/dashboard/sms-settings-form"
|
||||||
import { AppearanceForm } from "@/components/dashboard/appearance-form"
|
import { AppearanceForm } from "@/components/dashboard/appearance-form"
|
||||||
|
import { UsersTable } from "@/components/dashboard/users-table"
|
||||||
|
import { ContentForm } from "@/components/dashboard/content-form"
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
|
import { SiteContent } from "@/types/cms"
|
||||||
|
|
||||||
interface SettingsTabsProps {
|
interface SettingsTabsProps {
|
||||||
siteSettings: Partial<SettingsFormValues> | null
|
|
||||||
smsSettings: {
|
smsSettings: {
|
||||||
username: string
|
username: string
|
||||||
header: string
|
header: string
|
||||||
} | null
|
} | null
|
||||||
|
users: any[]
|
||||||
|
contents: SiteContent[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SettingsTabs({ siteSettings, smsSettings }: SettingsTabsProps) {
|
export function SettingsTabs({ smsSettings, users, contents }: SettingsTabsProps) {
|
||||||
return (
|
return (
|
||||||
<Tabs defaultValue="general" className="space-y-4">
|
<Tabs defaultValue="content" className="space-y-4">
|
||||||
<TabsList>
|
<TabsList className="grid w-full grid-cols-2 md:grid-cols-5 h-auto">
|
||||||
<TabsTrigger value="general">Genel</TabsTrigger>
|
<TabsTrigger value="content">İçerik Yönetimi</TabsTrigger>
|
||||||
|
<TabsTrigger value="users">Kullanıcılar</TabsTrigger>
|
||||||
<TabsTrigger value="sms">SMS / Bildirimler</TabsTrigger>
|
<TabsTrigger value="sms">SMS / Bildirimler</TabsTrigger>
|
||||||
<TabsTrigger value="appearance">Görünüm</TabsTrigger>
|
<TabsTrigger value="appearance">Görünüm</TabsTrigger>
|
||||||
<TabsTrigger value="security">Güvenlik</TabsTrigger>
|
<TabsTrigger value="security">Güvenlik</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
<TabsContent value="general" className="space-y-4">
|
<TabsContent value="content" className="space-y-4">
|
||||||
<SiteSettingsForm initialData={siteSettings} />
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-medium">Site İçerik Yönetimi</h3>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Site genel ayarları, iletişim bilgileri ve logolar.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<ContentForm initialContent={contents} />
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="users" className="space-y-4">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-medium">Kullanıcı Yönetimi</h3>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Sistemdeki kayıtlı kullanıcıları ve rollerini yönetin.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<UsersTable users={users} />
|
||||||
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="sms" className="space-y-4">
|
<TabsContent value="sms" className="space-y-4">
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
import { usePathname } from "next/navigation"
|
import { usePathname } from "next/navigation"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { LayoutDashboard, Package, ShoppingCart, Users, Settings, Globe, Tags, FileText } from "lucide-react"
|
import { LayoutDashboard, Package, ShoppingCart, Settings, Globe, Tags } from "lucide-react"
|
||||||
|
|
||||||
const sidebarItems = [
|
const sidebarItems = [
|
||||||
{
|
{
|
||||||
@@ -26,16 +26,6 @@ const sidebarItems = [
|
|||||||
href: "/dashboard/categories",
|
href: "/dashboard/categories",
|
||||||
icon: Tags,
|
icon: Tags,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: "Kullanıcılar",
|
|
||||||
href: "/dashboard/users",
|
|
||||||
icon: Users,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "İçerik Yönetimi",
|
|
||||||
href: "/dashboard/cms/content",
|
|
||||||
icon: FileText,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: "Ayarlar",
|
title: "Ayarlar",
|
||||||
href: "/dashboard/settings",
|
href: "/dashboard/settings",
|
||||||
|
|||||||
@@ -1,163 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { useState } from "react"
|
|
||||||
import { useRouter } from "next/navigation"
|
|
||||||
import { useForm } from "react-hook-form"
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod"
|
|
||||||
import * as z from "zod"
|
|
||||||
import { Button } from "@/components/ui/button"
|
|
||||||
import { Input } from "@/components/ui/input"
|
|
||||||
import { Textarea } from "@/components/ui/textarea"
|
|
||||||
import {
|
|
||||||
Form,
|
|
||||||
FormControl,
|
|
||||||
FormDescription,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormMessage,
|
|
||||||
} from "@/components/ui/form"
|
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
|
||||||
import { toast } from "sonner"
|
|
||||||
import { Loader2 } from "lucide-react"
|
|
||||||
import { updateSiteSettings } from "@/app/(dashboard)/dashboard/settings/actions"
|
|
||||||
|
|
||||||
const settingsSchema = z.object({
|
|
||||||
site_title: z.string().min(2, "Site başlığı en az 2 karakter olmalıdır."),
|
|
||||||
site_description: z.string(),
|
|
||||||
contact_email: z.literal("").or(z.string().email("Geçerli bir e-posta adresi giriniz.")),
|
|
||||||
contact_phone: z.string(),
|
|
||||||
currency: z.string(),
|
|
||||||
})
|
|
||||||
|
|
||||||
export type SettingsFormValues = z.infer<typeof settingsSchema>
|
|
||||||
|
|
||||||
interface SiteSettingsFormProps {
|
|
||||||
initialData: Partial<SettingsFormValues> | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export function SiteSettingsForm({ initialData }: SiteSettingsFormProps) {
|
|
||||||
const router = useRouter()
|
|
||||||
const [loading, setLoading] = useState(false)
|
|
||||||
|
|
||||||
const form = useForm<SettingsFormValues>({
|
|
||||||
resolver: zodResolver(settingsSchema),
|
|
||||||
defaultValues: {
|
|
||||||
site_title: initialData?.site_title || "ParaKasa",
|
|
||||||
site_description: initialData?.site_description || "",
|
|
||||||
contact_email: initialData?.contact_email || "",
|
|
||||||
contact_phone: initialData?.contact_phone || "",
|
|
||||||
currency: initialData?.currency || "TRY",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const onSubmit = async (data: SettingsFormValues) => {
|
|
||||||
setLoading(true)
|
|
||||||
try {
|
|
||||||
const result = await updateSiteSettings(data)
|
|
||||||
|
|
||||||
if (result.error) {
|
|
||||||
toast.error(result.error)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
toast.success("Site ayarları güncellendi.")
|
|
||||||
router.refresh()
|
|
||||||
} catch {
|
|
||||||
toast.error("Bir sorun oluştu.")
|
|
||||||
} finally {
|
|
||||||
setLoading(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Genel Ayarlar</CardTitle>
|
|
||||||
<CardDescription>Web sitesinin genel yapılandırma ayarları.</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<Form {...form}>
|
|
||||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="site_title"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Site Başlığı</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input placeholder="ParaKasa" {...field} />
|
|
||||||
</FormControl>
|
|
||||||
<FormDescription>Tarayıcı sekmesinde görünen ad.</FormDescription>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="site_description"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Site Açıklaması</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Textarea placeholder="Premium çelik kasalar..." {...field} />
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="contact_email"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>İletişim E-posta</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input placeholder="info@parakasa.com" {...field} />
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="contact_phone"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>İletişim Telefon</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input placeholder="+90 555 123 45 67" {...field} />
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="currency"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Para Birimi</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input placeholder="TRY" {...field} />
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Button type="submit" disabled={loading}>
|
|
||||||
{loading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
|
||||||
Ayarları Kaydet
|
|
||||||
</Button>
|
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
69
components/dashboard/users-table.tsx
Normal file
69
components/dashboard/users-table.tsx
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import Link from "next/link"
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
} from "@/components/ui/table"
|
||||||
|
import { Badge } from "@/components/ui/badge"
|
||||||
|
|
||||||
|
interface Profile {
|
||||||
|
id: string
|
||||||
|
full_name: string
|
||||||
|
role: string
|
||||||
|
created_at: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UsersTableProps {
|
||||||
|
users: Profile[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function UsersTable({ users }: UsersTableProps) {
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="flex items-center justify-between space-y-2">
|
||||||
|
<h3 className="text-lg font-medium">Kullanıcı Listesi</h3>
|
||||||
|
{/*
|
||||||
|
<Link href="/dashboard/users/new">
|
||||||
|
<Button size="sm">Yeni Kullanıcı</Button>
|
||||||
|
</Link>
|
||||||
|
*/}
|
||||||
|
</div>
|
||||||
|
<div className="rounded-md border">
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead>Ad Soyad</TableHead>
|
||||||
|
<TableHead>Rol</TableHead>
|
||||||
|
<TableHead>Kayıt Tarihi</TableHead>
|
||||||
|
<TableHead className="text-right">İşlemler</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{users?.map((profile) => (
|
||||||
|
<TableRow key={profile.id}>
|
||||||
|
<TableCell className="font-medium">{profile.full_name}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Badge variant={profile.role === 'admin' ? 'default' : 'secondary'}>
|
||||||
|
{profile.role === 'admin' ? 'Yönetici' : 'Kullanıcı'}
|
||||||
|
</Badge>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>{new Date(profile.created_at).toLocaleDateString('tr-TR')}</TableCell>
|
||||||
|
<TableCell className="text-right">
|
||||||
|
<Link href={`/dashboard/users/${profile.id}`}>
|
||||||
|
<Button variant="ghost" size="sm">Düzenle</Button>
|
||||||
|
</Link>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
2
drop_site_settings.sql
Normal file
2
drop_site_settings.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
-- Drop the site_settings table as it is replaced by site_contents
|
||||||
|
DROP TABLE IF EXISTS site_settings;
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
import { createClient } from "@/lib/supabase-server"
|
|
||||||
import { cache } from "react"
|
|
||||||
|
|
||||||
export const getSiteSettings = cache(async () => {
|
|
||||||
const supabase = createClient()
|
|
||||||
const { data } = await supabase.from('site_settings').select('*').single()
|
|
||||||
return data
|
|
||||||
})
|
|
||||||
Reference in New Issue
Block a user