Commit Graph

43 Commits

Author SHA1 Message Date
egecankomur 12397d409d fix(invoices): serialize Appwrite rows before passing to client component 2026-05-14 23:17:25 +03:00
egecankomur 7660901eb0 fix(paytr): encode hyphens in tenantId as Z in merchantOid
PayTR rejects merchant_oid with non-alphanumeric chars. Real Appwrite
tenant IDs are hex (a-z0-9) and safe, but demo-tenant-001 contains
hyphens. Encode - as Z (never appears in Appwrite hex IDs) in the
merchantOid; decode back in the callback.
2026-05-14 23:12:36 +03:00
egecankomur 1618db57db feat(theme): remove collapsible=none option from sidebar customizer 2026-05-14 22:39:30 +03:00
egecankomur 2b48422b68 fix(sidebar): check isMobile before collapsible=none; sticky desktop layout
- Swap isMobile and collapsible=none checks: mobile always gets Sheet
  overlay regardless of collapsible setting. Previously collapsible=none
  rendered an inline div on mobile, pushing content and preventing close.
- collapsible=none desktop path: use sticky top-0 h-svh instead of
  h-full so the sidebar stays visible while the page scrolls.
- Update sidebar-none-mode CSS to use align-items:flex-start which is
  required for sticky children in a flex container.

Root cause: stored sidebarCollapsible=none pref (set via theme
customizer) triggered this code path on both mobile and desktop.
2026-05-14 22:32:59 +03:00
egecankomur 856e577f4b fix: server-side UA mobile detection — prevents desktop sidebar flash on mobile before JS hydration 2026-05-14 20:03:41 +03:00
egecankomur 0d6e773197 fix: add viewport meta tag — iOS was using 980px default width causing desktop sidebar on mobile 2026-05-14 19:53:37 +03:00
egecankomur 5622b0ef07 fix: mobile sidebar — cursor-pointer on SheetOverlay (iOS tap-to-close), modal=false prevents body position:fixed layout shift, scrollbar-gutter stable on desktop 2026-05-14 19:47:18 +03:00
egecankomur 668fb7108b feat: subscription upgrade-only flow, discount codes, proration, enterprise inquiry form, payment history invoices page, fix mobile sidebar close on navigate 2026-05-14 19:09:11 +03:00
egecankomur 37b0928da6 feat: watermark tool complete — parallel processing, logo opacity, preview fix, logo upload fix, rename to Fotoğraf Damgala 2026-05-13 14:00:00 +03:00
egecankomur 7c677dfa4b perf: memoize parseImageIds, fix checkLimit OR query, loading skeletons, dashboard cache, compound indexes, sidebar active state, matches notified fix, padding fixes, match criteria in property detail 2026-05-13 13:08:05 +03:00
egecankomur 933cb17107 feat: plan/limit system + role-based page access
Plan & limit enforcement:
- src/lib/plans.ts: PLAN_LIMITS (free: 15 props, 25 customers, 5 presentations, 1 member, 5 imgs/prop)
- checkLimit() + limitErrorMessage() — role-aware error messages (owner: upgrade CTA, others: contact admin)
- Create actions (property, customer, presentation, team invite): hard limit check before create
- PropertyImageUploader: maxImages prop + isOwner-aware toast
- UsageBadge component: usage counter shown only to owner (green→amber→red)

Role-based access:
- TenantContext + ActiveContext: add memberCount + role fields
- Dashboard layout: non-owner on free plan with >1 member → /plan-limit
- /plan-limit: blocked-access page with owner contact info + sign-out
- AppSidebar: minRole filtering — Plan & Faturalama (owner only), Çalışma Alanı/Yatırımcılar (admin+)
- settings/billing: owner-only hard redirect
- settings/workspace + settings/members: member redirect, admin read-only
- settings/investors: member redirect
- workspace-actions + logo-actions: restricted to owner only
- Workspace form: canEdit = owner only (admin sees read-only view)
2026-05-12 18:46:02 +03:00
egecankomur 7c23a2b4ae feat: activity assignment + team view for owner/admin
db: activities.assigneeId column (string, optional)

activity-actions: assigneeId saved on create (default: self), cleared on update
validation: assigneeId added to activitySchema
schema: assigneeId field on Activity type

activity-form-sheet: Atanan Kişi dropdown for owner/admin with member list
activity-team-view: new component — activities grouped by assignee,
  completion/edit/delete actions, overdue indicator, member avatars
activities-client: Ekip tab (owner/admin only), members + currentUserId props
activities page: fetches team memberships + user details, passes to client
activity-email-actions: filter by assigneeId ?? createdBy for both me/team modes
2026-05-12 17:40:21 +03:00
egecankomur 5ac6a1f8b0 feat: daily activity summary email
- activity-email-actions: sendDailySummaryAction — filters today's
  activities by dueDate, sends personalized email via Appwrite Messaging
  - 'me': current user's activities only
  - 'team' (owner/admin): each member gets their own activities separately
- send-summary-dialog: dialog with me/team radio (owner/admin only sees
  team option), inline error + toast on success
- activities-client: 'Günlük Özet' button in header, role prop added
- activities page: passes ctx.role to client
2026-05-12 17:26:50 +03:00
egecankomur fe86bfe6b2 fix: resolve auth/tenant loop and serialization errors
- middleware: remove auth-path→/dashboard redirect; stale session cookies
  caused dashboard→onboarding→sign-in→dashboard infinite loop
- dashboard layout: check getCurrentUser first, redirect to /sign-in
  directly instead of going through /onboarding
- getActiveContext: use admin client (users.listMemberships) for tenant
  resolution instead of session-dependent getUserTeams()
- requireTenant: validate membership before trusting stored tenantId;
  clear stale cookie and re-resolve if user is not a member
- sunum page: JSON.parse/stringify property rows before passing to
  Client Component (Appwrite SDK objects have non-plain prototypes)
2026-05-12 17:18:19 +03:00
egecankomur a3bcb464ea feat: custom password reset flow (token-based, Appwrite Messaging)
- db: new password_resets table (email, userId, tokenHash, expiresAt, usedAt)
- lib: password-reset-actions.ts — requestPasswordResetAction, verifyResetToken, resetPasswordAction
- lib: Messaging added to createAdminClient
- page: /reset-password — validates token server-side, shows form or error card
- page: /forgot-password — now uses requestPasswordResetAction (custom flow)
- page: /sign-in — shows success banner after ?reset=success redirect
2026-05-12 16:49:04 +03:00
egecankomur 95e30a74c7 fix: use admin client in onboarding guard to prevent accidental workspace creation
Session client (getUserTeams) can return null when the session token is
expired or stale. The old guard 'if (teams && teams.total > 0) redirect'
was bypassed when teams was null, allowing users to create duplicate
workspaces.

New guard uses admin client (API key, never session-dependent):
- lists user memberships via users.listMemberships(userId)
- checks for a tenant_settings row in this app
- redirects to /dashboard if found; shows form only for genuinely new users
2026-05-12 15:45:33 +03:00
egecankomur b71edd880b fix: harden getActiveContext and add error logging on presentations page
- getActiveContext now verifies team still exists (teams.get) before
  trusting the cookie/prefs tenantId, preventing stale E Ofis cookie
  from causing an onboarding redirect loop
- Resolved tenantId from getUserTeams() is now persisted to cookie so
  subsequent requests skip the resolution path
- Added catch+log on presentations data fetch to surface the actual
  error in server logs
2026-05-12 15:33:38 +03:00
egecankomur 84be9ec5e3 fix: auto-create tenant_settings when missing to prevent onboarding loop
- ensureSettings() creates a minimal settings row if one is missing for
  a team the user is already a member of, instead of returning null and
  letting the onboarding redirect fire
- resolveFirstValidTenantId() falls back to any membership when no team
  has settings yet, so new registrations without a completed onboarding
  still land on the correct tenant
- teams.get() is now fetched alongside listMemberships in a single
  Promise.all so the team name is available for the fallback row
2026-05-12 14:19:24 +03:00
egecankomur 04e11c3fed fix: normalize image filename extension before Appwrite upload 2026-05-12 05:14:37 +03:00
egecankomur 8dd970d2ad fix: add force-dynamic to auth-protected pages, sunum gallery+detail modal, form step 3 layout, academy tour updates 2026-05-12 05:02:44 +03:00
egecankomur 3554b39800 feat: desktop image thumbnails, gallery lightbox portal, client-side compression, clickable table rows, fix header gap 2026-05-12 04:49:36 +03:00
egecankomur 3cce632eb3 feat(billing): payment infrastructure pre-prep
db: add plan, planExpiresAt, planProvider to tenant_settings (Appwrite MCP)

- schema.ts: TenantPlan type, TenantSettings plan fields
- subscription-types.ts: Emlak plan catalog (Free / Pro 499₺/ay)
- plan-limits.ts: resource limits (properties/customers/members/presentations)
  + getPlanUsage, requirePlanCapacity, PlanLimitError helpers
- subscription-actions.ts: startCheckoutAction (Polar→Shopier→mock fallback),
  activatePlanInDb / deactivatePlanInDb for webhook handlers,
  downgradeToFreeAction, getCurrentPlanAction
- /api/payments/polar/callback: verify webhook → activatePlanInDb on order/subscription events
- /api/payments/shopier/callback: verify HMAC → activate on fulfilled+paid (tenant
  email-matching TODO pending Shopier metadata support)
- /settings/billing: CurrentPlanCard with usage progress bars + UpgradeSection
- sidebar: Plan & Faturalama nav item
- PlanLimitDialog: Emlak-specific feature list
2026-05-08 15:26:18 +03:00
egecankomur 95a7cbaf0d feat: onboarding'de diğer KovakSoft uygulamalarından workspace içe aktarma 2026-05-06 23:04:26 +03:00
egecankomur 9f462c2f1f fix: filter teams by app — getUserTeams and setActiveTenant now reject cross-app teams 2026-05-06 22:31:58 +03:00
egecankomur 54f6112e7e fix: harita görünümü tam yükseklik dolduracak şekilde düzeltildi
- Yükseklik: calc(100dvh - var(--header-height) - 8rem)
- Sağ panel ve sol panel h-full aldı
- PropertiesMapView sarmalayıcısına h-full w-full eklendi
- Sol kart listesi w-72'ye daraltıldı
2026-05-06 20:20:48 +03:00
egecankomur b1d3ee3681 feat: ilan listesine harita görünümü eklendi (split layout)
- Liste / Harita toggle butonu header'da
- Harita modunda sol panel: kart listesi (fotoğraf, fiyat, oda/m²)
  + sağ panel: MapLibre harita tüm koordinatlı ilanlar
- İlan renkleri duruma göre: aktif=mavi, pasif=gri, satıldı=turuncu, kiralandı=mor
- Pini tıkla → kart listesinde o ilanın kartına scroll
- Kartı tıkla → haritada o ilanın pinine flyTo + popup açılır
- Popup içinde başlık, fiyat, özellikler, 'Detay →' linki
- Koordinatsız ilanlar listede görünür ama haritada pin yok (📍 Konum yok)
- PropertiesMapView: dynamic next/dynamic wrapper (ssr: false)
2026-05-05 22:00:41 +03:00
egecankomur 92428cb4fd feat: harita adres araması autocomplete dropdown'a dönüştürüldü
- 3 karakter sonrası 400ms debounce ile Nominatim'den öneri çeker
- Yükleniyor spinner input sağında
- Dropdown: MapPin + tam adres, onMouseDown ile blur çakışması önlendi
- Seçim sonrası kısa isim input'a yazılır, harita flyTo ile konuma gider
- Dışarı tıklanınca dropdown kapanır
2026-05-05 21:53:43 +03:00
egecankomur fd5c6c645f fix: tema kaydetme race condition düzeltildi
Önceki hata: ThemeTab her iki setter'ı da çağırıyordu (setSelectedTheme +
setSelectedTweakcnTheme). Bunlar wrapper'a bağlıydı, her wrapper kendi
saveThemePrefsAction'ını çağırıyordu. İkinci çağrı colorTheme:'' yazarak
birincinin kaydını siliyordu.

Düzeltme:
- ThemeTab'a RAW React state setter'ları iletildi (wrapper değil)
- ThemeTab'ın cross-clear mantığı olduğu gibi kaldı
- Appwrite kaydı useEffect'e taşındı: React 18 olay yöneticisindeki
  tüm state güncellemelerini batch'ledikten SONRA tek seferde tetiklenir
  → selectedTheme ve selectedTweakcnTheme doğru nihai değerleriyle kaydedilir
2026-05-05 21:23:54 +03:00
egecankomur 63392bab7b fix: getPrefs sonucunu plain object'e çevir (Server→Client prop hatası) 2026-05-05 21:06:51 +03:00
egecankomur 237ec92691 feat: tema ayarları Appwrite user prefs ile kalıcı hale getirildi
- saveThemePrefsAction: account.updatePrefs ile mevcut prefs'i merge eder
- Dashboard layout'ta account.getPrefs ile prefs server-side yüklenir
- PrefsInitializer: mount'ta dark/light, renk teması, radius, sidebar
  config'ini Appwrite'dan gelen initialPrefs ile uygular
- ThemeCustomizer: renk teması / tweakcn / radius / sidebar değişikliği
  anında Appwrite'a kaydedilir; dark/light toggle useEffect ile izlenir
- Sayfa yenileme ve farklı cihazda giriş sonrasında ayarlar korunur
2026-05-05 20:57:30 +03:00
egecankomur 9497cc72ce feat: sunum sayfasında danışman adı ve şirket adı göster
Header'da ilan sayısının yanına creator adı + şirket adı eklendi.
Footer'da 'Kovak Emlak CRM' yerine dinamik ad/şirket yazıyor.
users.get(createdBy) + teams.get(tenantId) ile çekilir;
her ikisi de allSettled ile hata toleranslı.
2026-05-05 20:47:40 +03:00
egecankomur 1fdeefc472 design: sunum sayfası yeniden tasarlandı
- Koyu gradyan hero header (slate-900), Building2 ikon, ilan sayısı pill
- Kartlar: h-52 fotoğraf, hover scale efekti, gölge animasyonu
- Status + listing type badge overlay fotoğraf üstünde (backdrop-blur)
- Fotoğraf sayısı pill (sağ alt köşe)
- Spec pill'leri: emlak tipi, oda, m²
- Fiyat büyük ve dominant (2xl), mt-auto ile alta sabitlenmiş
- MapPin ikonu konum satırında ve harita linkinde
- Footer: Kovak Emlak CRM branding
- Süresi dolmuş sunum için iyileştirilmiş boş state
2026-05-05 20:39:58 +03:00
egecankomur 3caddff515 feat: add MapLibre GL map to property form and detail page
- Install maplibre-gl; use OpenFreeMap tiles (no API key)
- PropertyMapPickerInner: address search via Nominatim, draggable
  marker, click-to-place, geolocation, clear button
- PropertyMapPicker/View: dynamic next/dynamic wrappers (ssr: false)
- PropertyMapViewInner: read-only marker view with navigation control
- PropertyFormSheet: hidden mapLat/mapLng inputs, picker renders only
  when sheet is open, resets on property change
- Property detail page: Konum section with PropertyMapView + Google Maps link
- Sunum page: Google Maps deep link on PropertyCard when coordinates exist
2026-05-05 20:32:45 +03:00
egecankomur 1d5ad5f62f fix: presentation expiresAt — skip empty date, convert YYYY-MM-DD to end-of-day UTC
Empty string sent to Appwrite datetime column was being interpreted as creation
timestamp, making every no-expiry sunum immediately expired. Fix:
- Only include expiresAt in payload when user actually set a date
- Convert date-only string (YYYY-MM-DD) to YYYY-MM-DDT23:59:59.000Z so the
  sunum stays valid for the entire chosen day across all time zones
- Strip empty customerId/notes to avoid empty-string writes
2026-05-05 20:19:02 +03:00
egecankomur f043f4acd7 feat: redesign login page — split layout, Emlak CRM + Kovak Yazılım branding
- Sol: koyu panel — logo, özellikler listesi (portföy/eşleşme/sunum/müşteri), alt imza
- Sağ: temiz form — email + şifre, mobil responsive
- İşletmem referansları kaldırıldı, Emlak CRM olarak güncellendi
2026-05-05 20:10:01 +03:00
egecankomur d9aff26376 fix: delete stale matches when score drops below threshold or listing type changes
matchPropertyToSearches now:
- scores every search (listing type mismatch = 0 score)
- score >= 20: create or update match
- score < 20 AND existing match: delete stale record

Prevents outdated match records after criteria/weight updates.
2026-05-05 20:03:49 +03:00
egecankomur a40e68254b feat: weighted match scoring, photo upload, property detail page
- scoring.ts: pure scoreMatch + scoreMatchBreakdown with per-criterion weights
- matching.ts: soft scoring (0-100), updates score on re-sync, threshold 20
- search-form-sheet: weight selectors (1-5) per criterion
- customer-search-actions: save/update weight fields
- storage-actions: upload/delete property images to property-images bucket
- storage-utils: getPropertyImagePreviewUrl, parseImageIds helpers
- property-image-uploader: client component with preview grid + delete
- property-form-sheet: integrated image uploader
- properties/[id]: detail page with gallery, specs, matches sidebar
- properties-client: Detay link in dropdown
- matches page: MatchesClient with click-to-breakdown dialog
- sunum page: cover image from first imageIds entry
- matches-client + match-breakdown-dialog: score breakdown per criterion
2026-05-05 19:55:34 +03:00
egecankomur 3d044c5d5b fix: onboarding createWorkspace — emlak fields (officeName/phone/createdBy), remove old CRM fields 2026-05-05 13:24:10 +03:00
egecankomur 19c1ecef47 fix: workspace settings — emlak fields (officeName/phone/email/address), add createdBy to createRow 2026-05-05 13:15:50 +03:00
egecankomur 115e5cd159 fix: lazy env var check in server.ts — prevent module-level throw during Docker build 2026-05-05 12:11:42 +03:00
egecankomur 4ef0482732 feat: all core modules — properties, customers, searches, matches, presentations, activities, investors + public sunum page
- Server actions: property/customer/search/presentation/activity/investor CRUD
- Matching engine: matchPropertyToSearches + syncMatchesForSearch on search save
- UI: form sheets + table clients for all modules
- Public /sunum/[token] page (no auth) with property card grid + expiry check
- All pages force-dynamic for auth guard compatibility
2026-05-05 12:03:48 +03:00
egecankomur 2f17c342ca feat: emlak CRM iskelet kurulumu
- schema.ts tamamen yeniden yazıldı (properties, customers, customer_searches, property_matches, presentations, investors, activities, tenant_settings)
- Sidebar emlak modüllerine güncellendi (İlanlar, Müşteriler, Yatırımcılar, Sunumlar, Aktiviteler)
- Eski CRM lib dosyaları temizlendi (finance, invoice, lead, task, software, vs.)
- Yeni modül dizinleri oluşturuldu (stub pages)
- command-search emlak navigasyonuna güncellendi
- site-header temizlendi
- Typecheck: 0 hata (chart.tsx template hariç)
2026-05-05 11:43:29 +03:00
egecankomur 37679e83e6 init: kovakemlak-crm project scaffold
- Next.js 16 + Appwrite multi-tenant emlak CRM
- Database: kovakemlak-db (properties, customers, customer_searches, property_matches, presentations, investors, activities, tenant_settings)
- Same stack as isletmem-kovakcrm (shadcn/ui template base)
- Modules: portföy, müşteri takibi, arama kriterleri, otomatik eşleştirme, sunum linki, yatırımcı portalı
2026-05-05 04:37:04 +03:00