first commit

This commit is contained in:
2026-08-05 19:40:55 +03:00
commit 3952b61edf
55 changed files with 10964 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
import { NextResponse } from 'next/server'
// NextAuth kaldırıldı — admin koruması şimdilik açık
export async function requireAdmin() {
// İleride env-based token veya başka auth mekanizması eklenebilir
}
export function unauthorized() {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
+2
View File
@@ -0,0 +1,2 @@
// NextAuth kaldırıldı. Admin koruması artık basit env-based token ile yapılır.
export {}
+24
View File
@@ -0,0 +1,24 @@
import { v2 as cloudinary } from 'cloudinary'
cloudinary.config({
cloud_name: process.env.CLOUDINARY_CLOUD_NAME!,
api_key: process.env.CLOUDINARY_API_KEY!,
api_secret: process.env.CLOUDINARY_API_SECRET!,
})
export async function uploadImage(
file: string,
folder: string = 'vesta-mugla'
): Promise<{ url: string; publicId: string }> {
const result = await cloudinary.uploader.upload(file, {
folder,
transformation: [{ quality: 'auto', fetch_format: 'auto' }],
})
return { url: result.secure_url, publicId: result.public_id }
}
export async function deleteImage(publicId: string): Promise<void> {
await cloudinary.uploader.destroy(publicId)
}
export { cloudinary }
+8
View File
@@ -0,0 +1,8 @@
export const SITE = {
name: 'Vesta Muğla',
logoUrl: '/logo.svg',
phoneHref: 'tel:+905000000000',
phone: '+90 500 000 00 00',
email: 'info@vestamugla.com',
address: 'Muğla, Türkiye',
}
+9
View File
@@ -0,0 +1,9 @@
import { PrismaClient } from '@prisma/client'
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient }
export const prisma =
globalForPrisma.prisma ??
new PrismaClient({ log: process.env.NODE_ENV === 'development' ? ['query'] : [] })
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
+191
View File
@@ -0,0 +1,191 @@
// ─── KULLANICI ─────────────────────────────────────────────────────────────────
export const MOCK_USERS = [
{
id: 'mock-user-1',
name: 'Admin',
email: 'admin@vestamugla.com',
role: 'ADMIN' as const,
createdAt: new Date('2024-01-01'),
updatedAt: new Date('2024-01-01'),
},
]
// ─── DAİRE TİPLERİ ─────────────────────────────────────────────────────────────
export const MOCK_UNITS = [
{
id: 'mock-unit-1',
slug: '2-1-tip-a',
typeTr: '2+1 Tip A',
typeEn: '2+1 Type A',
size: 89.5,
rooms: 3, // salon + 2 yatak odası
bathrooms: 1,
floor: '1-4. Kat',
descTr: 'Geniş salon ve iki yatak odasından oluşan bu daire tipi, orman manzarası ile birleşince eşsiz bir yaşam alanına dönüşüyor. Modern mutfak ve ferah banyo ile her detay özenle tasarlandı.',
descEn: 'This apartment type, consisting of a spacious living room and two bedrooms, turns into a unique living space when combined with a forest view. Every detail has been carefully designed with a modern kitchen and a spacious bathroom.',
imageUrl: 'https://images.unsplash.com/photo-1560448204-e02f11c3d0e2?w=800&q=80',
planUrl: null,
featured: true,
available: true,
order: 1,
createdAt: new Date('2024-01-01'),
updatedAt: new Date('2024-01-01'),
deletedAt: null,
},
{
id: 'mock-unit-2',
slug: '2-1-tip-b',
typeTr: '2+1 Tip B',
typeEn: '2+1 Type B',
size: 84.8,
rooms: 3,
bathrooms: 1,
floor: '1-4. Kat',
descTr: 'Kompakt ama son derece fonksiyonel tasarımıyla 2+1 Tip B, doğayla iç içe yaşamın keyfini çıkarmak isteyenler için ideal. Balkonundan Muğla\'nın yeşil siluetini seyredebilirsiniz.',
descEn: 'Compact yet extremely functional, the 2+1 Type B is ideal for those who want to enjoy living in harmony with nature. You can watch Muğla\'s green silhouette from your balcony.',
imageUrl: 'https://images.unsplash.com/photo-1502672260266-1c1ef2d93688?w=800&q=80',
planUrl: null,
featured: false,
available: true,
order: 2,
createdAt: new Date('2024-01-01'),
updatedAt: new Date('2024-01-01'),
deletedAt: null,
},
{
id: 'mock-unit-3',
slug: '1-1-tip-a',
typeTr: '1+1 Tip A',
typeEn: '1+1 Type A',
size: 47.1,
rooms: 2,
bathrooms: 1,
floor: '1-4. Kat',
descTr: 'Yatırım değeri yüksek ve bakımı kolay 1+1 Tip A daireler, hem bireyler hem de yatırımcılar için mükemmel bir seçenek. Akıllı tasarımı ile her metrekare verimli kullanılıyor.',
descEn: 'With high investment value and easy maintenance, 1+1 Type A apartments are a perfect choice for both individuals and investors. With its smart design, every square meter is used efficiently.',
imageUrl: 'https://images.unsplash.com/photo-1493809842364-78817add7ffb?w=800&q=80',
planUrl: null,
featured: false,
available: true,
order: 3,
createdAt: new Date('2024-01-01'),
updatedAt: new Date('2024-01-01'),
deletedAt: null,
},
{
id: 'mock-unit-4',
slug: '1-1-tip-b',
typeTr: '1+1 Tip B',
typeEn: '1+1 Type B',
size: 60.8,
rooms: 2,
bathrooms: 1,
floor: '1-4. Kat',
descTr: 'Geniş 1+1 konseptiyle tasarlanan Tip B daireler, tek başına ya da çift olarak yaşayanlar için maksimum konfor sunuyor. Ferah yaşam alanı ve büyük pencereler ile doğal ışık her zaman içeride.',
descEn: 'Designed with a spacious 1+1 concept, Type B apartments offer maximum comfort for those living alone or as a couple. With a spacious living area and large windows, natural light is always inside.',
imageUrl: 'https://images.unsplash.com/photo-1484154218962-a197022b5858?w=800&q=80',
planUrl: null,
featured: false,
available: true,
order: 4,
createdAt: new Date('2024-01-01'),
updatedAt: new Date('2024-01-01'),
deletedAt: null,
},
]
// ─── GALERİ ────────────────────────────────────────────────────────────────────
export const MOCK_GALLERY = [
{
id: 'mock-g-1',
titleTr: 'Proje Genel Görünüm',
titleEn: 'Project Overview',
imageUrl: 'https://images.unsplash.com/photo-1580587771525-78b9dba3b914?w=800&q=80',
category: 'exterior',
order: 1,
createdAt: new Date(),
updatedAt: new Date(),
deletedAt: null,
},
{
id: 'mock-g-2',
titleTr: 'Orman İçi Yürüyüş Yolları',
titleEn: 'Forest Walking Paths',
imageUrl: 'https://images.unsplash.com/photo-1448375240586-882707db888b?w=800&q=80',
category: 'amenities',
order: 2,
createdAt: new Date(),
updatedAt: new Date(),
deletedAt: null,
},
{
id: 'mock-g-3',
titleTr: 'Çocuk Oyun Alanı',
titleEn: 'Children\'s Playground',
imageUrl: 'https://images.unsplash.com/photo-1575783970733-1aaedde1db74?w=800&q=80',
category: 'amenities',
order: 3,
createdAt: new Date(),
updatedAt: new Date(),
deletedAt: null,
},
{
id: 'mock-g-4',
titleTr: 'Spor Tesisleri',
titleEn: 'Sports Facilities',
imageUrl: 'https://images.unsplash.com/photo-1534438327276-14e5300c3a48?w=800&q=80',
category: 'amenities',
order: 4,
createdAt: new Date(),
updatedAt: new Date(),
deletedAt: null,
},
{
id: 'mock-g-5',
titleTr: 'Muğla Doğası',
titleEn: 'Muğla Nature',
imageUrl: 'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800&q=80',
category: 'location',
order: 5,
createdAt: new Date(),
updatedAt: new Date(),
deletedAt: null,
},
{
id: 'mock-g-6',
titleTr: 'Yaşam Alanları',
titleEn: 'Living Areas',
imageUrl: 'https://images.unsplash.com/photo-1571896349842-33c89424de2d?w=800&q=80',
category: 'interior',
order: 6,
createdAt: new Date(),
updatedAt: new Date(),
deletedAt: null,
},
]
// ─── MESAJLAR ──────────────────────────────────────────────────────────────────
export const MOCK_MESSAGES = [
{
id: 'mock-msg-1',
fullName: 'Ahmet Yıldız',
email: 'ahmet@example.com',
phone: '+90 532 111 2233',
message: '2+1 Tip A daire hakkında bilgi almak istiyorum. Fiyatları ve ödeme koşullarını öğrenebilir miyim?',
read: false,
createdAt: new Date('2025-06-01'),
updatedAt: new Date('2025-06-01'),
deletedAt: null,
},
{
id: 'mock-msg-2',
fullName: 'Selin Kaya',
email: 'selin@example.com',
phone: '+90 541 987 6543',
message: 'Projenizle ilgileniyorum. Yerinde ziyaret için randevu alabilir miyim?',
read: true,
createdAt: new Date('2025-05-28'),
updatedAt: new Date('2025-05-28'),
deletedAt: null,
},
]
+21
View File
@@ -0,0 +1,21 @@
import { clsx, type ClassValue } from 'clsx'
import { twMerge } from 'tailwind-merge'
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
export function slugify(text: string) {
return text
.toLowerCase()
.replace(/ğ/g, 'g')
.replace(/ü/g, 'u')
.replace(/ş/g, 's')
.replace(/ı/g, 'i')
.replace(/ö/g, 'o')
.replace(/ç/g, 'c')
.replace(/\s+/g, '-')
.replace(/[^\w-]+/g, '')
.replace(/--+/g, '-')
.trim()
}
+29
View File
@@ -0,0 +1,29 @@
import { z } from 'zod'
export const ContactSchema = z.object({
fullName: z.string().min(2, 'Ad soyad en az 2 karakter olmalı'),
email: z.string().email('Geçerli bir email adresi girin'),
phone: z.string().optional(),
message: z.string().min(10, 'Mesaj en az 10 karakter olmalı'),
})
export type ContactInput = z.infer<typeof ContactSchema>
export const UnitSchema = z.object({
slug: z.string().min(1),
typeTr: z.string().min(1),
typeEn: z.string().min(1),
size: z.number().positive(),
rooms: z.number().int().positive(),
bathrooms: z.number().int().positive().default(1),
floor: z.string().optional(),
descTr: z.string().optional(),
descEn: z.string().optional(),
imageUrl: z.string().optional(),
planUrl: z.string().optional(),
featured: z.boolean().default(false),
available: z.boolean().default(true),
order: z.number().int().default(0),
})
export type UnitInput = z.infer<typeof UnitSchema>