feat: Implement Phase 2 features including blog, collections, saved listings, manifest, and api cron sync

This commit is contained in:
AyrisAI
2026-07-12 20:49:41 +03:00
parent 862544b5a1
commit f696d359b0
29 changed files with 3066 additions and 194 deletions
+133
View File
@@ -93,6 +93,7 @@ export async function approveSubmissionAction(id: string) {
priceRange: 2,
rating: 5.0,
isLocalApproved: false,
isFeatured: false,
images: submission.imageUrl ? [submission.imageUrl] : []
})
@@ -149,6 +150,7 @@ export async function createOrUpdateListingAction(formData: FormData) {
const priceRange = parseInt(formData.get('priceRange') as string)
const rating = formData.get('rating') ? parseFloat(formData.get('rating') as string) : null
const isLocalApproved = formData.get('isLocalApproved') === 'true'
const isFeatured = formData.get('isFeatured') === 'true'
const latitude = formData.get('latitude') ? parseFloat(formData.get('latitude') as string) : null
const longitude = formData.get('longitude') ? parseFloat(formData.get('longitude') as string) : null
@@ -209,6 +211,7 @@ export async function createOrUpdateListingAction(formData: FormData) {
priceRange,
rating,
isLocalApproved,
isFeatured,
latitude,
longitude,
openingHours,
@@ -230,3 +233,133 @@ export async function createOrUpdateListingAction(formData: FormData) {
return { success: false, error: err.message || 'Mekan kaydedilemedi.' }
}
}
// Blog Actions
export async function deleteBlogPostAction(id: string) {
await mockDb.deleteBlogPost(id)
revalidatePath('/admin/blog')
revalidatePath('/blog')
return { success: true }
}
export async function createOrUpdateBlogPostAction(formData: FormData) {
const id = formData.get('id') as string | null
const slug = formData.get('slug') as string
const titleTr = formData.get('titleTr') as string
const titleEn = formData.get('titleEn') as string
const titleRu = formData.get('titleRu') as string
const contentTr = formData.get('contentTr') as string
const contentEn = formData.get('contentEn') as string
const contentRu = formData.get('contentRu') as string
const tagsStr = formData.get('tags') as string || ''
const tags = tagsStr.split(',').map(t => t.trim()).filter(Boolean)
const relatedListingIdsStr = formData.get('relatedListingIds') as string || ''
const relatedListingIds = relatedListingIdsStr.split(',').map(i => i.trim()).filter(Boolean)
const isPublished = formData.get('isPublished') === 'true'
const publishedAt = isPublished ? new Date() : null
// Handle Cover Image
const coverImageFile = formData.get('coverImageFile') as File | null
let coverImage = formData.get('coverImageUrl') as string | null || null
if (coverImageFile && coverImageFile.size > 0) {
try {
coverImage = await uploadToOpeninary(coverImageFile, `blog/${slug}`)
} catch (e: any) {
console.error('Blog cover upload error:', e)
return { success: false, error: `Kapak görseli yüklenemedi: ${e.message}` }
}
}
const data = {
slug,
titleTr,
titleEn,
titleRu,
contentTr,
contentEn,
contentRu,
coverImage,
tags,
relatedListingIds,
publishedAt
}
try {
if (id && id !== 'new') {
await mockDb.updateBlogPost(id, data)
} else {
await mockDb.createBlogPost(data)
}
revalidatePath('/admin/blog')
revalidatePath('/blog')
revalidatePath(`/blog/${slug}`)
return { success: true }
} catch (err: any) {
return { success: false, error: err.message || 'Yazı kaydedilemedi.' }
}
}
// Collections Actions
export async function deleteCollectionAction(id: string) {
await mockDb.deleteCollection(id)
revalidatePath('/admin/collections')
revalidatePath('/seckiler')
return { success: true }
}
export async function createOrUpdateCollectionAction(formData: FormData) {
const id = formData.get('id') as string | null
const slug = formData.get('slug') as string
const titleTr = formData.get('titleTr') as string
const titleEn = formData.get('titleEn') as string
const titleRu = formData.get('titleRu') as string
const descriptionTr = formData.get('descriptionTr') as string
const descriptionEn = formData.get('descriptionEn') as string
const descriptionRu = formData.get('descriptionRu') as string
const listingIdsStr = formData.get('listingIds') as string || ''
const listingIds = listingIdsStr.split(',').map(i => i.trim()).filter(Boolean)
// Handle Cover Image
const coverImageFile = formData.get('coverImageFile') as File | null
let coverImage = formData.get('coverImageUrl') as string | null || null
if (coverImageFile && coverImageFile.size > 0) {
try {
coverImage = await uploadToOpeninary(coverImageFile, `collections/${slug}`)
} catch (e: any) {
console.error('Collection cover upload error:', e)
return { success: false, error: `Kapak görseli yüklenemedi: ${e.message}` }
}
}
const data = {
slug,
titleTr,
titleEn,
titleRu,
descriptionTr,
descriptionEn,
descriptionRu,
coverImage,
listingIds
}
try {
if (id && id !== 'new') {
await mockDb.updateCollection(id, data)
} else {
await mockDb.createCollection(data)
}
revalidatePath('/admin/collections')
revalidatePath('/seckiler')
revalidatePath(`/secki/${slug}`)
return { success: true }
} catch (err: any) {
return { success: false, error: err.message || 'Koleksiyon kaydedilemedi.' }
}
}