first commit
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
import { auth } from '@/lib/auth'
|
||||
import { NextResponse } from 'next/server'
|
||||
|
||||
export async function requireAdmin() {
|
||||
const session = await auth()
|
||||
if (!session || (session.user as { role?: string }).role !== 'ADMIN') {
|
||||
throw new Error('Unauthorized')
|
||||
}
|
||||
return session
|
||||
}
|
||||
|
||||
export function unauthorized() {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
import NextAuth from 'next-auth'
|
||||
import { PrismaAdapter } from '@auth/prisma-adapter'
|
||||
import Credentials from 'next-auth/providers/credentials'
|
||||
import bcrypt from 'bcryptjs'
|
||||
import { prisma } from '@/lib/db'
|
||||
|
||||
export const { handlers, auth, signIn, signOut } = NextAuth({
|
||||
adapter: PrismaAdapter(prisma),
|
||||
providers: [
|
||||
Credentials({
|
||||
credentials: {
|
||||
email: { label: 'Email', type: 'email' },
|
||||
password: { label: 'Password', type: 'password' },
|
||||
},
|
||||
async authorize(credentials) {
|
||||
if (!credentials?.email || !credentials?.password) return null
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { email: credentials.email as string },
|
||||
})
|
||||
if (!user?.password) return null
|
||||
const valid = await bcrypt.compare(credentials.password as string, user.password)
|
||||
if (!valid) return null
|
||||
return { id: user.id, email: user.email, name: user.name, role: user.role }
|
||||
},
|
||||
}),
|
||||
],
|
||||
callbacks: {
|
||||
jwt({ token, user }) {
|
||||
if (user) token.role = (user as { role: string }).role
|
||||
return token
|
||||
},
|
||||
session({ session, token }) {
|
||||
if (token && session.user) {
|
||||
session.user.role = token.role as string
|
||||
}
|
||||
return session
|
||||
},
|
||||
},
|
||||
pages: {
|
||||
signIn: '/tr/admin/login',
|
||||
},
|
||||
session: { strategy: 'jwt' },
|
||||
})
|
||||
@@ -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 }
|
||||
@@ -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
@@ -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,
|
||||
},
|
||||
]
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user