From 1d98409385e776d586ee1a10a874842cf8ebd174 Mon Sep 17 00:00:00 2001 From: AyrisAI Date: Mon, 13 Jul 2026 15:00:46 +0300 Subject: [PATCH] feat: widget partner system with JS embed, isHidden toggle, category/neighborhood filters --- .../[id]/WidgetPartnerForm.tsx | 47 ++- app/actions.ts | 4 +- app/api/widget/nearby/route.ts | 46 +-- lib/mockDb.ts | 1 + prisma/schema.prisma | 1 + public/test-widget.html | 76 ++--- public/widget.js | 300 +++++------------- 7 files changed, 177 insertions(+), 298 deletions(-) diff --git a/app/[locale]/admin/widget-partners/[id]/WidgetPartnerForm.tsx b/app/[locale]/admin/widget-partners/[id]/WidgetPartnerForm.tsx index 41566c9..abf6dda 100644 --- a/app/[locale]/admin/widget-partners/[id]/WidgetPartnerForm.tsx +++ b/app/[locale]/admin/widget-partners/[id]/WidgetPartnerForm.tsx @@ -30,14 +30,21 @@ export default function WidgetPartnerForm({ const isEditing = !!partner + const [isHidden, setIsHidden] = useState(partner ? Boolean((partner as any).isHidden) : false) + const [selectedNeighborhood, setSelectedNeighborhood] = useState(partner?.neighborhoodSlug || '') + const embedCode = partner - ? `` + ? [ + '', + '', + '' + ].filter(Boolean).join('\n') : '' const handleCopy = () => { @@ -107,7 +114,7 @@ export default function WidgetPartnerForm({

Aşağıdaki kodu kopyalayarak partnerin web sitesine yerleştirin. Bu kod, sitenizden backlink almayı sağlar.

-
+          
             {embedCode}
           
@@ -155,7 +162,8 @@ export default function WidgetPartnerForm({ Bu widget yayında mı? + + diff --git a/app/actions.ts b/app/actions.ts index f96c914..db57980 100644 --- a/app/actions.ts +++ b/app/actions.ts @@ -652,6 +652,7 @@ export async function saveWidgetPartnerAction(formData: FormData) { const url = formData.get('url') as string const neighborhoodSlug = formData.get('neighborhoodSlug') as string | null const isActive = formData.get('isActive') === 'true' + const isHidden = formData.get('isHidden') === 'true' if (!name || !url) { return { error: 'Lütfen zorunlu alanları doldurun.' } @@ -661,7 +662,8 @@ export async function saveWidgetPartnerAction(formData: FormData) { name, url, neighborhoodSlug: neighborhoodSlug || null, - isActive + isActive, + isHidden } try { diff --git a/app/api/widget/nearby/route.ts b/app/api/widget/nearby/route.ts index 877c041..fe565f9 100644 --- a/app/api/widget/nearby/route.ts +++ b/app/api/widget/nearby/route.ts @@ -4,29 +4,33 @@ import { mockDb } from '@/lib/mockDb' export async function GET(req: NextRequest) { const { searchParams } = new URL(req.url) const neighborhoodSlug = searchParams.get('neighborhood') - - if (!neighborhoodSlug) { - return NextResponse.json({ error: 'Neighborhood is required' }, { status: 400 }) + const categorySlug = searchParams.get('category') + + const filters: Parameters[0] = {} + + // Neighborhood filtresi (opsiyonel - boşsa tüm Marmaris) + if (neighborhoodSlug) { + const neighborhood = await mockDb.getNeighborhoodBySlug(neighborhoodSlug) + if (neighborhood) { + filters.neighborhoodId = neighborhood.id + } } - // Get neighborhood - const neighborhood = await mockDb.getNeighborhoodBySlug(neighborhoodSlug) - if (!neighborhood) { - return NextResponse.json({ listings: [] }, { - headers: { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': 'GET, OPTIONS', - } - }) + // Category filtresi (opsiyonel) + if (categorySlug) { + const categories = await mockDb.getCategories() + const cat = categories.find(c => c.slug === categorySlug) + if (cat) { + filters.categoryId = cat.id + } } - // Get listings in that neighborhood - const listings = await mockDb.getListings({ - neighborhoodId: neighborhood.id - }) + const listings = await mockDb.getListings(filters) - // Format response matching widget needs - const formattedListings = listings.map(l => ({ + // Shuffle for variety, limit to 5 + const shuffled = listings.sort(() => 0.5 - Math.random()).slice(0, 5) + + const formattedListings = shuffled.map(l => ({ id: l.id, name: l.nameTr, nameEn: l.nameEn, @@ -36,8 +40,10 @@ export async function GET(req: NextRequest) { categoryName: l.category?.nameTr || '', neighborhoodName: l.neighborhood?.nameTr || '', rating: l.rating, - priceSymbols: '₺'.repeat(l.priceRange), - coverImage: l.images && l.images.length > 0 ? l.images[0].url : 'https://images.unsplash.com/photo-1555396273-367ea4eb4db5?w=800&auto=format&fit=crop&q=80' + priceSymbols: '₺'.repeat(l.priceRange || 1), + coverImage: l.images && l.images.length > 0 + ? l.images[0].url + : 'https://images.unsplash.com/photo-1555396273-367ea4eb4db5?w=800&auto=format&fit=crop&q=80' })) return NextResponse.json({ listings: formattedListings }, { diff --git a/lib/mockDb.ts b/lib/mockDb.ts index 2954d5e..b24240d 100644 --- a/lib/mockDb.ts +++ b/lib/mockDb.ts @@ -133,6 +133,7 @@ export interface WidgetPartner { url: string neighborhoodSlug: string | null isActive: boolean + isHidden: boolean createdAt: Date updatedAt: Date } diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 985fe23..710234d 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -270,6 +270,7 @@ model WidgetPartner { url String neighborhoodSlug String? isActive Boolean @default(true) + isHidden Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } diff --git a/public/test-widget.html b/public/test-widget.html index 3adf3a8..de5b964 100644 --- a/public/test-widget.html +++ b/public/test-widget.html @@ -3,63 +3,37 @@ - Marmaris Local - Widget Test Sayfası + Marmaris Local Widget Test +

🧪 Marmaris Local Widget Test Sayfası

-
-

marmaris local widget demo

-

Bu sayfa, Muğla Dijital Medya ortak sitelerine yerleştirilecek olan bölgesel öneri widget'ının önizlemesini test etmek amacıyla oluşturulmuştur.

- -

1. Açık Tema Demo (data-neighborhood="yat-limani")

-
- - -
- -

2. Koyu Tema Demo (data-neighborhood="yat-limani" data-theme="dark")

-
- - -
+
+

Akyaka Bölgesi — Light Tema

+ <div data-marmaris-widget="akyaka" data-limit="3"></div> +<script src="/widget.js" async></script> +
+
+

Marmaris — Dark Tema

+ <div data-marmaris-widget="marmaris" data-theme="dark" data-limit="3"></div> +
+
+ +
+

Gizli Widget (display:none) — Sadece indexlenir

+ <div data-marmaris-widget="marmaris" style="display:none"></div> +
+

⬆️ Bu div gizli - içerik yüklenir ama kullanıcı görmez

+
+ + diff --git a/public/widget.js b/public/widget.js index 76c1dcb..ea9b0b9 100644 --- a/public/widget.js +++ b/public/widget.js @@ -1,224 +1,92 @@ -(function() { - const currentScript = document.currentScript; - if (!currentScript) return; +(function () { + const API_BASE = 'https://marmarislocal.com'; - const neighborhood = currentScript.getAttribute('data-neighborhood') || ''; - const theme = currentScript.getAttribute('data-theme') || 'light'; - - // Extract base URL of the running script dynamically - const scriptUrl = currentScript.src; - const baseUrl = new URL(scriptUrl).origin; + // Sayfadaki tüm widget container'larını bul + const containers = document.querySelectorAll('[data-marmaris-widget]'); - if (!neighborhood) { - console.error('Marmaris Local Widget: data-neighborhood parameter is missing.'); - return; + containers.forEach(function (container) { + // data-marmaris-widget = marker (boolean), data-neighborhood = bölge slug (opsiyonel) + const neighborhood = container.dataset.neighborhood || ''; + const category = container.dataset.category || ''; + const limit = parseInt(container.dataset.limit || '3', 10); + const lang = container.dataset.lang || document.documentElement.lang || 'tr'; + + container.innerHTML = '
Yükleniyor...
'; + + var params = new URLSearchParams(); + if (neighborhood) params.set('neighborhood', neighborhood); + if (category) params.set('category', category); + + fetch(API_BASE + '/api/widget/nearby?' + params) + .then(function (res) { + if (!res.ok) throw new Error('fetch failed'); + return res.json(); + }) + .then(function (data) { + // API { listings: [...] } formatında dönüyor + var items = (data.listings || []).slice(0, limit); + if (!items || items.length === 0) { + container.innerHTML = ''; + return; + } + render(container, items, lang, neighborhood); + }) + .catch(function () { + // Sessizce gizle — kırık widget partner sitesinde kötü görünür + container.innerHTML = ''; + }); + }); + + function getName(item, lang) { + if (lang === 'en' && item.nameEn) return item.nameEn; + if (lang === 'ru' && item.nameRu) return item.nameRu; + return item.name; } - // Create widget container element - const container = document.createElement('div'); - container.className = 'marmaris-local-widget-wrapper'; - currentScript.parentNode.insertBefore(container, currentScript); + function render(container, items, lang, neighborhood) { + var labels = { + tr: { title: 'Bölgede Öneriler', by: 'Marmaris Local tarafından' }, + en: { title: 'Recommended Nearby', by: 'by Marmaris Local' }, + ru: { title: 'Рекомендации поблизости', by: 'от Marmaris Local' } + }; + var t = labels[lang] || labels.tr; - // Inject CSS Styles - const style = document.createElement('style'); - style.textContent = ` - .marmaris-local-widget-wrapper { - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; - border: 1px solid ${theme === 'dark' ? '#254449' : '#12323815'}; - background-color: ${theme === 'dark' ? '#123238' : '#FBFAF6'}; - color: ${theme === 'dark' ? '#FBFAF6' : '#123238'}; - border-radius: 16px; - padding: 20px; - box-sizing: border-box; - max-width: 100%; - margin: 15px 0; - box-shadow: 0 4px 12px rgba(18, 50, 56, 0.04); - } - .marmaris-local-widget-header { - margin-bottom: 16px; - display: flex; - align-items: center; - justify-content: space-between; - } - .marmaris-local-widget-header h4 { - margin: 0; - font-size: 14px; - font-weight: 800; - letter-spacing: -0.02em; - text-transform: lowercase; - color: ${theme === 'dark' ? '#FBFAF6' : '#123238'}; - } - .marmaris-local-widget-header h4 span { - color: #39C3C3; - } - .marmaris-local-widget-header .neighborhood-badge { - font-size: 10px; - font-weight: 700; - font-family: monospace; - text-transform: uppercase; - letter-spacing: 0.05em; - color: #D23B68; - background-color: rgba(210, 59, 104, 0.06); - padding: 3px 8px; - border-radius: 99px; - border: 1px solid rgba(210, 59, 104, 0.1); - } - .marmaris-local-widget-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - gap: 16px; - margin-bottom: 14px; - } - .marmaris-local-widget-card { - display: flex; - flex-direction: column; - text-decoration: none; - background: ${theme === 'dark' ? '#254449' : '#FFFFFF'}; - border: 1px solid ${theme === 'dark' ? '#ffffff10' : '#12323808'}; - border-radius: 12px; - overflow: hidden; - transition: all 0.2s ease-in-out; - box-sizing: border-box; - } - .marmaris-local-widget-card:hover { - transform: translateY(-2px); - border-color: rgba(57, 195, 195, 0.35); - box-shadow: 0 4px 10px rgba(18, 50, 56, 0.06); - } - .marmaris-local-widget-card-image { - aspect-ratio: 16 / 10; - width: 100%; - background: #12323810; - position: relative; - overflow: hidden; - } - .marmaris-local-widget-card-image img { - width: 100%; - height: 100%; - object-fit: cover; - transition: transform 0.3s ease; - } - .marmaris-local-widget-card:hover .marmaris-local-widget-card-image img { - transform: scale(1.03); - } - .marmaris-local-widget-card-content { - padding: 12px; - flex: 1; - display: flex; - flex-direction: column; - justify-content: space-between; - } - .marmaris-local-widget-card-category { - font-size: 9px; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0.05em; - color: #D23B68; - margin-bottom: 4px; - } - .marmaris-local-widget-card-title { - font-size: 13px; - font-weight: 700; - color: ${theme === 'dark' ? '#FBFAF6' : '#123238'}; - margin: 0 0 6px 0; - line-height: 1.25; - text-transform: lowercase; - } - .marmaris-local-widget-card-footer { - display: flex; - align-items: center; - justify-content: space-between; - font-size: 11px; - font-family: monospace; - color: ${theme === 'dark' ? '#FBFAF680' : '#12323865'}; - border-top: 1px dashed ${theme === 'dark' ? '#ffffff10' : '#12323808'}; - padding-top: 8px; - margin-top: 8px; - } - .marmaris-local-widget-card-footer .stars { - color: #FFB020; - font-weight: bold; - } - .marmaris-local-widget-card-footer .price { - color: ${theme === 'dark' ? '#FBFAF6' : '#123238'}; - font-weight: bold; - } - .marmaris-local-widget-footer { - display: flex; - align-items: center; - justify-content: space-between; - border-top: 1px solid ${theme === 'dark' ? '#ffffff10' : '#12323810'}; - padding-top: 12px; - font-size: 10px; - font-weight: 600; - color: ${theme === 'dark' ? '#FBFAF650' : '#12323850'}; - } - .marmaris-local-widget-footer a { - color: inherit; - text-decoration: underline; - transition: color 0.15s; - } - .marmaris-local-widget-footer a:hover { - color: #39C3C3; - } - `; - document.head.appendChild(style); + var cards = items.map(function (item) { + var href = API_BASE + '/tr/' + item.categorySlug + '/' + item.slug + '?utm_source=widget&utm_medium=partner'; + var name = getName(item, lang); + var img = item.coverImage + ? '' + : ''; + var meta = item.categoryName + (item.rating ? ' · ★ ' + Number(item.rating).toFixed(1) : ''); - // Fetch nearby approved listings - fetch(`${baseUrl}/api/widget/nearby?neighborhood=${encodeURIComponent(neighborhood)}`) - .then(res => res.json()) - .then(data => { - const listings = data.listings || []; - - if (listings.length === 0) { - container.style.display = 'none'; - return; - } + return '' + + img + + '
' + + '
' + name + '
' + + '
' + meta + '
' + + '
' + + '
'; + }).join(''); - // Render Header - let html = ` -
-

yakında nerede yenir?

- ${neighborhood} -
-
- `; - - // Render Cards - listings.slice(0, 3).forEach(item => { - const itemUrl = `${baseUrl}/${item.categorySlug}/${item.slug}`; - html += ` - -
- ${item.name} -
-
-
-
${item.categoryName}
-
${item.name.toLowerCase()}
-
- -
-
- `; - }); - - // Render Footer Stamp (Mandatory visibility for search quality guidelines) - html += ` -
- - `; - - container.innerHTML = html; - }) - .catch(err => { - console.error('Marmaris Local Widget load error:', err); - container.style.display = 'none'; - }); + container.innerHTML = '
' + + '
' + + '

' + t.title + '

' + + '' + t.by + '' + + '
' + + '
' + + cards + + '
' + + '
'; + } })();