From 3554b3980015c65700aef1dfc53f2310e4044ee5 Mon Sep 17 00:00:00 2001 From: egecankomur Date: Tue, 12 May 2026 04:49:36 +0300 Subject: [PATCH] feat: desktop image thumbnails, gallery lightbox portal, client-side compression, clickable table rows, fix header gap --- next.config.ts | 4 + package.json | 4 + pnpm-lock.yaml | 521 +++++++++++++++ .../forbidden/components/forbidden-error.tsx | 2 +- .../components/internal-server-error.tsx | 2 +- .../not-found/components/not-found-error.tsx | 2 +- .../components/unauthorized-error.tsx | 2 +- .../components/under-maintenance-error.tsx | 2 +- .../components/forgot-password-form-1.tsx | 6 +- src/app/(auth)/forgot-password/page.tsx | 2 +- .../sign-in-3/components/login-form-3.tsx | 2 +- .../sign-in/components/login-form-1.tsx | 12 +- .../sign-up-3/components/signup-form-3.tsx | 2 +- .../sign-up/components/signup-form-1.tsx | 18 +- src/app/(dashboard)/academy/page.tsx | 23 + src/app/(dashboard)/customers/[id]/page.tsx | 301 +++++++++ src/app/(dashboard)/dashboard-shell.tsx | 19 +- .../components/chart-area-interactive.tsx | 487 ++++++-------- .../components/dashboard-carousel.tsx | 53 ++ .../dashboard/components/data-table.tsx | 54 +- .../dashboard/components/follow-up-widget.tsx | 78 +++ .../dashboard/components/quick-actions.tsx | 2 +- .../components/recent-activities.tsx | 63 ++ .../components/recent-properties.tsx | 77 +++ .../dashboard/components/section-cards.tsx | 146 ++--- src/app/(dashboard)/dashboard/page.tsx | 100 ++- src/app/(dashboard)/finance/page.tsx | 45 ++ src/app/(dashboard)/layout.tsx | 17 +- src/app/(dashboard)/properties/[id]/page.tsx | 228 +------ .../account/components/email-form.tsx | 6 +- .../settings/account/components/name-form.tsx | 6 +- .../account/components/password-form.tsx | 6 +- .../(dashboard)/settings/appearance/page.tsx | 2 +- .../billing/components/current-plan-card.tsx | 6 +- .../billing/components/upgrade-section.tsx | 2 +- src/app/(dashboard)/settings/billing/page.tsx | 4 +- .../(dashboard)/settings/connections/page.tsx | 42 +- .../members/components/invite-form.tsx | 4 +- .../members/components/members-table.tsx | 12 +- .../components/pending-invites-table.tsx | 4 +- .../settings/notifications/page.tsx | 8 +- src/app/(dashboard)/settings/user/page.tsx | 6 +- .../workspace/components/logo-uploader.tsx | 14 +- .../workspace/components/workspace-form.tsx | 8 +- src/app/(dashboard)/sidebar-overlay.tsx | 14 + src/app/api/properties/images/route.ts | 65 ++ src/app/d/[code]/accept-invite-button.tsx | 6 +- src/app/globals.css | 8 + src/app/kullanim-sartlari/page.tsx | 268 ++++++++ src/app/kvkk/page.tsx | 301 +++++++++ src/app/landing/components/about-section.tsx | 4 +- src/app/landing/components/blog-section.tsx | 2 +- .../landing/components/contact-section.tsx | 8 +- src/app/landing/components/cta-section.tsx | 8 +- src/app/landing/components/faq-section.tsx | 4 +- .../landing/components/features-section.tsx | 10 +- src/app/landing/components/footer.tsx | 10 +- src/app/landing/components/hero-section.tsx | 2 +- .../components/landing-theme-customizer.tsx | 14 +- src/app/landing/components/logo-carousel.tsx | 2 +- src/app/landing/components/navbar.tsx | 20 +- .../landing/components/pricing-section.tsx | 4 +- src/app/landing/components/stats-section.tsx | 2 +- src/app/landing/components/team-section.tsx | 8 +- src/app/layout.tsx | 4 +- .../components/create-workspace-form.tsx | 10 +- src/app/sunum/[token]/page.tsx | 10 +- src/components/academy/academy-client.tsx | 134 ++++ .../academy/academy-sidebar-badge.tsx | 50 ++ .../academy/academy-tour-button.tsx | 98 +++ .../activities/activities-client.tsx | 112 +++- .../activities/activity-calendar.tsx | 320 +++++++++ .../activities/activity-form-sheet.tsx | 142 ++-- src/components/app-sidebar.tsx | 45 +- src/components/billing/plan-limit-dialog.tsx | 2 +- src/components/command-search.tsx | 28 +- .../customers/customer-form-sheet.tsx | 142 ++-- src/components/customers/customers-client.tsx | 297 ++++++--- .../customers/customers-pipeline.tsx | 163 +++++ .../customers/search-form-sheet.tsx | 245 +++---- src/components/customers/searches-client.tsx | 42 +- src/components/finance/deal-form-sheet.tsx | 173 +++++ src/components/finance/finance-client.tsx | 459 +++++++++++++ src/components/finance/scope-toggle.tsx | 4 +- .../investors/investor-form-sheet.tsx | 126 ++-- src/components/investors/investors-client.tsx | 42 +- src/components/landing/mega-menu.tsx | 22 +- src/components/map/properties-map-view.tsx | 2 +- .../map/property-map-picker-inner.tsx | 10 +- src/components/map/property-map-picker.tsx | 2 +- src/components/matches/matches-client.tsx | 168 ++++- src/components/mode-toggle.tsx | 2 +- src/components/nav-main.tsx | 12 +- src/components/nav-secondary.tsx | 4 +- src/components/nav-user.tsx | 18 +- .../presentations/presentation-form-sheet.tsx | 118 ++-- .../presentations/presentations-client.tsx | 59 +- src/components/pricing-plans.tsx | 4 +- src/components/properties/image-lightbox.tsx | 116 ++++ .../properties/properties-client.tsx | 615 +++++++++++++++--- .../properties/property-detail-client.tsx | 390 +++++++++++ .../properties/property-form-sheet.tsx | 241 +++---- .../properties/property-image-sheet.tsx | 102 +++ .../properties/property-image-uploader.tsx | 210 ++++-- src/components/sidebar-notification.tsx | 26 +- src/components/site-footer.tsx | 21 +- src/components/site-header.tsx | 4 +- src/components/theme-customizer/index.tsx | 8 +- .../theme-customizer/layout-tab.tsx | 2 +- src/components/theme-customizer/theme-tab.tsx | 6 +- src/components/ui/alert-dialog.tsx | 194 ++++++ src/components/ui/delete-confirm-dialog.tsx | 53 ++ src/components/ui/responsive-sheet.tsx | 168 +++++ src/components/upgrade-to-pro-button.tsx | 6 +- src/hooks/use-sidebar-config.ts | 11 +- src/lib/academy/progress.ts | 30 + src/lib/academy/tours.ts | 326 ++++++++++ src/lib/appwrite/active-context.ts | 28 +- src/lib/appwrite/customer-actions.ts | 29 +- src/lib/appwrite/dashboard-queries.ts | 158 +++++ src/lib/appwrite/deal-actions.ts | 187 ++++++ src/lib/appwrite/match-actions.ts | 43 ++ src/lib/appwrite/plan-limits-shared.ts | 29 + src/lib/appwrite/plan-limits.ts | 37 +- src/lib/appwrite/property-actions.ts | 19 + src/lib/appwrite/schema.ts | 62 +- src/lib/appwrite/storage-actions.ts | 34 - src/lib/appwrite/tenant-guard.ts | 67 +- src/lib/icons.ts | 106 +++ src/lib/validation/customers.ts | 17 + src/lib/validation/deals.ts | 23 + src/lib/validation/properties.ts | 2 +- src/utils/shadcn-ui-theme-presets.ts | 44 +- src/utils/tweakcn-theme-presets.ts | 2 +- 134 files changed, 7736 insertions(+), 1913 deletions(-) create mode 100644 src/app/(dashboard)/academy/page.tsx create mode 100644 src/app/(dashboard)/customers/[id]/page.tsx create mode 100644 src/app/(dashboard)/dashboard/components/dashboard-carousel.tsx create mode 100644 src/app/(dashboard)/dashboard/components/follow-up-widget.tsx create mode 100644 src/app/(dashboard)/dashboard/components/recent-activities.tsx create mode 100644 src/app/(dashboard)/dashboard/components/recent-properties.tsx create mode 100644 src/app/(dashboard)/finance/page.tsx create mode 100644 src/app/(dashboard)/sidebar-overlay.tsx create mode 100644 src/app/api/properties/images/route.ts create mode 100644 src/app/kullanim-sartlari/page.tsx create mode 100644 src/app/kvkk/page.tsx create mode 100644 src/components/academy/academy-client.tsx create mode 100644 src/components/academy/academy-sidebar-badge.tsx create mode 100644 src/components/academy/academy-tour-button.tsx create mode 100644 src/components/activities/activity-calendar.tsx create mode 100644 src/components/customers/customers-pipeline.tsx create mode 100644 src/components/finance/deal-form-sheet.tsx create mode 100644 src/components/finance/finance-client.tsx create mode 100644 src/components/properties/image-lightbox.tsx create mode 100644 src/components/properties/property-detail-client.tsx create mode 100644 src/components/properties/property-image-sheet.tsx create mode 100644 src/components/ui/alert-dialog.tsx create mode 100644 src/components/ui/delete-confirm-dialog.tsx create mode 100644 src/components/ui/responsive-sheet.tsx create mode 100644 src/lib/academy/progress.ts create mode 100644 src/lib/academy/tours.ts create mode 100644 src/lib/appwrite/dashboard-queries.ts create mode 100644 src/lib/appwrite/deal-actions.ts create mode 100644 src/lib/appwrite/match-actions.ts create mode 100644 src/lib/appwrite/plan-limits-shared.ts create mode 100644 src/lib/icons.ts create mode 100644 src/lib/validation/deals.ts diff --git a/next.config.ts b/next.config.ts index eb09734..40519aa 100644 --- a/next.config.ts +++ b/next.config.ts @@ -3,6 +3,10 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { turbopack: {}, + experimental: { + optimizePackageImports: ["@phosphor-icons/react"], + }, + typescript: { ignoreBuildErrors: true }, images: { diff --git a/package.json b/package.json index 9c7065f..b018c16 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", "@hookform/resolvers": "^5.2.2", + "@phosphor-icons/react": "^2.1.10", "@polar-sh/sdk": "^0.47.1", "@radix-ui/react-accordion": "^1.2.12", "@radix-ui/react-avatar": "^1.1.11", @@ -40,16 +41,19 @@ "@tailwindcss/postcss": "^4.1.18", "@tanstack/react-table": "^8.21.3", "appwrite": "^24.2.0", + "browser-image-compression": "^2.0.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", "date-fns": "^4.1.0", + "driver.js": "^1.4.0", "lucide-react": "^0.562.0", "maplibre-gl": "^5.24.0", "next": "16.1.1", "next-themes": "^0.4.6", "node-appwrite": "^23.1.0", "postcss": "^8.5.6", + "radix-ui": "^1.4.3", "react": "19.2.3", "react-day-picker": "^9.13.0", "react-dom": "19.2.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 434c782..7fd1320 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,6 +23,9 @@ importers: '@hookform/resolvers': specifier: ^5.2.2 version: 5.2.2(react-hook-form@7.69.0(react@19.2.3)) + '@phosphor-icons/react': + specifier: ^2.1.10 + version: 2.1.10(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@polar-sh/sdk': specifier: ^0.47.1 version: 0.47.1 @@ -98,6 +101,9 @@ importers: appwrite: specifier: ^24.2.0 version: 24.2.0 + browser-image-compression: + specifier: ^2.0.2 + version: 2.0.2 class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -110,6 +116,9 @@ importers: date-fns: specifier: ^4.1.0 version: 4.1.0 + driver.js: + specifier: ^1.4.0 + version: 1.4.0 lucide-react: specifier: ^0.562.0 version: 0.562.0(react@19.2.3) @@ -128,6 +137,9 @@ importers: postcss: specifier: ^8.5.6 version: 8.5.6 + radix-ui: + specifier: ^1.4.3 + version: 1.4.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react: specifier: 19.2.3 version: 19.2.3 @@ -662,6 +674,13 @@ packages: resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} engines: {node: '>=12.4.0'} + '@phosphor-icons/react@2.1.10': + resolution: {integrity: sha512-vt8Tvq8GLjheAZZYa+YG/pW7HDbov8El/MANW8pOAz4eGxrwhnbfrQZq0Cp4q8zBEu8NIhHdnr+r8thnfRSNYA==} + engines: {node: '>=10'} + peerDependencies: + react: '>= 16.8' + react-dom: '>= 16.8' + '@polar-sh/sdk@0.47.1': resolution: {integrity: sha512-fkz7wPLbqfuDmY9LxuXpE2uP2TAV6J0q/YN5hJ4UBxpjbkB0hKM6c4R35N89t83dfzMlG6EOlqOn+Rd1T6XrJQ==} @@ -671,6 +690,19 @@ packages: '@radix-ui/primitive@1.1.3': resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} + '@radix-ui/react-accessible-icon@1.1.7': + resolution: {integrity: sha512-XM+E4WXl0OqUJFovy6GjmxxFyx9opfCAIUku4dlKRd5YEPqt4kALOkQOp0Of6reHuUkJuiPBEc5k0o4z4lTC8A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-accordion@1.2.12': resolution: {integrity: sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==} peerDependencies: @@ -684,6 +716,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-alert-dialog@1.1.15': + resolution: {integrity: sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-arrow@1.1.7': resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==} peerDependencies: @@ -697,6 +742,32 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-aspect-ratio@1.1.7': + resolution: {integrity: sha512-Yq6lvO9HQyPwev1onK1daHCHqXVLzPhSVjmsNjCa2Zcxy2f7uJD2itDtxknv6FzAKCwD1qQkeVDmX/cev13n/g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-avatar@1.1.10': + resolution: {integrity: sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-avatar@1.1.11': resolution: {integrity: sha512-0Qk603AHGV28BOBO34p7IgD5m+V5Sg/YovfayABkoDDBM5d3NCx0Mp4gGrjzLGes1jV5eNOE1r3itqOR33VC6Q==} peerDependencies: @@ -758,6 +829,19 @@ packages: '@types/react': optional: true + '@radix-ui/react-context-menu@2.2.16': + resolution: {integrity: sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-context@1.1.2': resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} peerDependencies: @@ -846,6 +930,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-form@0.1.8': + resolution: {integrity: sha512-QM70k4Zwjttifr5a4sZFts9fn8FzHYvQ5PiB19O2HsYibaHSVt9fH9rzB0XZo/YcM+b7t/p7lYCT/F5eOeF5yQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-hover-card@1.1.15': resolution: {integrity: sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg==} peerDependencies: @@ -868,6 +965,19 @@ packages: '@types/react': optional: true + '@radix-ui/react-label@2.1.7': + resolution: {integrity: sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-label@2.1.8': resolution: {integrity: sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==} peerDependencies: @@ -894,6 +1004,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-menubar@1.1.16': + resolution: {integrity: sha512-EB1FktTz5xRRi2Er974AUQZWg2yVBb1yjip38/lgwtCVRd3a+maUoGHN/xs9Yv8SY8QwbSEb+YrxGadVWbEutA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-navigation-menu@1.2.14': resolution: {integrity: sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w==} peerDependencies: @@ -907,6 +1030,32 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-one-time-password-field@0.1.8': + resolution: {integrity: sha512-ycS4rbwURavDPVjCb5iS3aG4lURFDILi6sKI/WITUMZ13gMmn/xGjpLoqBAalhJaDk8I3UbCM5GzKHrnzwHbvg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-password-toggle-field@0.1.3': + resolution: {integrity: sha512-/UuCrDBWravcaMix4TdT+qlNdVwOM1Nck9kWx/vafXsdfj1ChfhOdfi3cy9SGBpWgTXwYCuboT/oYpJy3clqfw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-popover@1.1.15': resolution: {integrity: sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==} peerDependencies: @@ -985,6 +1134,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-progress@1.1.7': + resolution: {integrity: sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-progress@1.1.8': resolution: {integrity: sha512-+gISHcSPUJ7ktBy9RnTqbdKW78bcGke3t6taawyZ71pio1JewwGSJizycs7rLhGTvMJYCQB1DBK4KQsxs7U8dA==} peerDependencies: @@ -1050,6 +1212,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-separator@1.1.7': + resolution: {integrity: sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-separator@1.1.8': resolution: {integrity: sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==} peerDependencies: @@ -1063,6 +1238,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-slider@1.3.6': + resolution: {integrity: sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-slot@1.2.3': resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} peerDependencies: @@ -1107,6 +1295,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-toast@1.2.15': + resolution: {integrity: sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-toggle-group@1.1.11': resolution: {integrity: sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q==} peerDependencies: @@ -1133,6 +1334,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-toolbar@1.1.11': + resolution: {integrity: sha512-4ol06/1bLoFu1nwUqzdD4Y5RZ9oDdKeiHIsntug54Hcr1pgaHiPqHFEaXI1IFP/EsOfROQZ8Mig9VTIRza6Tjg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-tooltip@1.2.8': resolution: {integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==} peerDependencies: @@ -1696,6 +1910,9 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + browser-image-compression@2.0.2: + resolution: {integrity: sha512-pBLlQyUf6yB8SmmngrcOw3EoS4RpQ1BcylI3T9Yqn7+4nrQTXJD4sJDe5ODnJdrvNMaio5OicFo75rDyJD2Ucw==} + browserslist@4.28.1: resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} @@ -1867,6 +2084,9 @@ packages: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} + driver.js@1.4.0: + resolution: {integrity: sha512-Gm64jm6PmcU+si21sQhBrTAM1JvUrR0QhNmjkprNLxohOBzul9+pNHXgQaT9lW84gwg9GMLB3NZGuGolsz5uew==} + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -2691,6 +2911,19 @@ packages: quickselect@3.0.0: resolution: {integrity: sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==} + radix-ui@1.4.3: + resolution: {integrity: sha512-aWizCQiyeAenIdUbqEpXgRA1ya65P13NKn/W8rWkcN0OPkRDxdBVLWnIEDsS2RpwCK2nobI7oMUSmexzTDyAmA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + react-day-picker@9.13.0: resolution: {integrity: sha512-euzj5Hlq+lOHqI53NiuNhCP8HWgsPf/bBAVijR50hNaY1XwjKjShAnIe8jm8RD2W9IJUvihDIZ+KrmqfFzNhFQ==} engines: {node: '>=18'} @@ -3076,6 +3309,9 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + uzip@0.20201231.0: + resolution: {integrity: sha512-OZeJfZP+R0z9D6TmBgLq2LHzSSptGMGDGigGiEe0pr8UBe/7fdflgHlHBNDASTXB5jnFuxHpNaJywSg8YFeGng==} + vaul@1.1.2: resolution: {integrity: sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA==} peerDependencies: @@ -3589,6 +3825,11 @@ snapshots: '@nolyfill/is-core-module@1.0.39': {} + '@phosphor-icons/react@2.1.10(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + '@polar-sh/sdk@0.47.1': dependencies: standardwebhooks: 1.0.0 @@ -3598,6 +3839,15 @@ snapshots: '@radix-ui/primitive@1.1.3': {} + '@radix-ui/react-accessible-icon@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + '@radix-ui/react-accordion@1.2.12(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -3615,6 +3865,20 @@ snapshots: '@types/react': 19.2.7 '@types/react-dom': 19.2.3(@types/react@19.2.7) + '@radix-ui/react-alert-dialog@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.7)(react@19.2.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -3624,6 +3888,28 @@ snapshots: '@types/react': 19.2.7 '@types/react-dom': 19.2.3(@types/react@19.2.7) + '@radix-ui/react-aspect-ratio@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + + '@radix-ui/react-avatar@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.2.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + '@radix-ui/react-avatar@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@radix-ui/react-context': 1.1.3(@types/react@19.2.7)(react@19.2.3) @@ -3687,6 +3973,20 @@ snapshots: optionalDependencies: '@types/react': 19.2.7 + '@radix-ui/react-context-menu@2.2.16(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.7)(react@19.2.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + '@radix-ui/react-context@1.1.2(@types/react@19.2.7)(react@19.2.3)': dependencies: react: 19.2.3 @@ -3772,6 +4072,20 @@ snapshots: '@types/react': 19.2.7 '@types/react-dom': 19.2.3(@types/react@19.2.7) + '@radix-ui/react-form@0.1.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-label': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + '@radix-ui/react-hover-card@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -3796,6 +4110,15 @@ snapshots: optionalDependencies: '@types/react': 19.2.7 + '@radix-ui/react-label@2.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + '@radix-ui/react-label@2.1.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -3831,6 +4154,24 @@ snapshots: '@types/react': 19.2.7 '@types/react-dom': 19.2.3(@types/react@19.2.7) + '@radix-ui/react-menubar@1.1.16(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.7)(react@19.2.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + '@radix-ui/react-navigation-menu@1.2.14(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -3853,6 +4194,42 @@ snapshots: '@types/react': 19.2.7 '@types/react-dom': 19.2.3(@types/react@19.2.7) + '@radix-ui/react-one-time-password-field@0.1.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.2.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + + '@radix-ui/react-password-toggle-field@0.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.7)(react@19.2.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + '@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -3932,6 +4309,16 @@ snapshots: '@types/react': 19.2.7 '@types/react-dom': 19.2.3(@types/react@19.2.7) + '@radix-ui/react-progress@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + '@radix-ui/react-progress@1.1.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@radix-ui/react-context': 1.1.3(@types/react@19.2.7)(react@19.2.3) @@ -4023,6 +4410,15 @@ snapshots: '@types/react': 19.2.7 '@types/react-dom': 19.2.3(@types/react@19.2.7) + '@radix-ui/react-separator@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + '@radix-ui/react-separator@1.1.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -4032,6 +4428,25 @@ snapshots: '@types/react': 19.2.7 '@types/react-dom': 19.2.3(@types/react@19.2.7) + '@radix-ui/react-slider@1.3.6(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.7)(react@19.2.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + '@radix-ui/react-slot@1.2.3(@types/react@19.2.7)(react@19.2.3)': dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.3) @@ -4077,6 +4492,26 @@ snapshots: '@types/react': 19.2.7 '@types/react-dom': 19.2.3(@types/react@19.2.7) + '@radix-ui/react-toast@1.2.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + '@radix-ui/react-toggle-group@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -4103,6 +4538,21 @@ snapshots: '@types/react': 19.2.7 '@types/react-dom': 19.2.3(@types/react@19.2.7) + '@radix-ui/react-toolbar@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-separator': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-toggle-group': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + '@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -4628,6 +5078,10 @@ snapshots: dependencies: fill-range: 7.1.1 + browser-image-compression@2.0.2: + dependencies: + uzip: 0.20201231.0 + browserslist@4.28.1: dependencies: baseline-browser-mapping: 2.9.11 @@ -4794,6 +5248,8 @@ snapshots: dependencies: esutils: 2.0.3 + driver.js@1.4.0: {} + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -5746,6 +6202,69 @@ snapshots: quickselect@3.0.0: {} + radix-ui@1.4.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-accessible-icon': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-accordion': 1.2.12(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-alert-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-aspect-ratio': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-avatar': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-checkbox': 1.3.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-context-menu': 2.2.16(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-dropdown-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-form': 0.1.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-hover-card': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-label': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-menubar': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-navigation-menu': 1.2.14(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-one-time-password-field': 0.1.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-password-toggle-field': 0.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-popover': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-progress': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-radio-group': 1.3.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-scroll-area': 1.2.10(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-select': 2.2.6(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-separator': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-slider': 1.3.6(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-switch': 1.2.6(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-toast': 1.2.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-toggle': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-toggle-group': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-toolbar': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-tooltip': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.7)(react@19.2.3) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + react-day-picker@9.13.0(react@19.2.3): dependencies: '@date-fns/tz': 1.4.1 @@ -6236,6 +6755,8 @@ snapshots: dependencies: react: 19.2.3 + uzip@0.20201231.0: {} + vaul@1.1.2(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) diff --git a/src/app/(auth)/errors/forbidden/components/forbidden-error.tsx b/src/app/(auth)/errors/forbidden/components/forbidden-error.tsx index 4c3a762..e634412 100644 --- a/src/app/(auth)/errors/forbidden/components/forbidden-error.tsx +++ b/src/app/(auth)/errors/forbidden/components/forbidden-error.tsx @@ -21,7 +21,7 @@ export function ForbiddenError() {

Forbidden

Access to this resource is forbidden. You don't have the necessary permissions to view this page.

- + diff --git a/src/app/(auth)/errors/internal-server-error/components/internal-server-error.tsx b/src/app/(auth)/errors/internal-server-error/components/internal-server-error.tsx index 586b997..3da8aeb 100644 --- a/src/app/(auth)/errors/internal-server-error/components/internal-server-error.tsx +++ b/src/app/(auth)/errors/internal-server-error/components/internal-server-error.tsx @@ -21,7 +21,7 @@ export function InternalServerError() {

Internal Server Error

Something went wrong on our end. We're working to fix the issue. Please try again later.

- + diff --git a/src/app/(auth)/errors/not-found/components/not-found-error.tsx b/src/app/(auth)/errors/not-found/components/not-found-error.tsx index ea24da4..32d6d81 100644 --- a/src/app/(auth)/errors/not-found/components/not-found-error.tsx +++ b/src/app/(auth)/errors/not-found/components/not-found-error.tsx @@ -21,7 +21,7 @@ export function NotFoundError() {

Page Not Found

The page you are looking for doesn't exist or has been moved to another location.

- + diff --git a/src/app/(auth)/errors/unauthorized/components/unauthorized-error.tsx b/src/app/(auth)/errors/unauthorized/components/unauthorized-error.tsx index 35c1c5a..6992b85 100644 --- a/src/app/(auth)/errors/unauthorized/components/unauthorized-error.tsx +++ b/src/app/(auth)/errors/unauthorized/components/unauthorized-error.tsx @@ -21,7 +21,7 @@ export function UnauthorizedError() {

Unauthorized

You don't have permission to access this resource. Please sign in or contact your administrator.

- + diff --git a/src/app/(auth)/errors/under-maintenance/components/under-maintenance-error.tsx b/src/app/(auth)/errors/under-maintenance/components/under-maintenance-error.tsx index c446728..f5122d1 100644 --- a/src/app/(auth)/errors/under-maintenance/components/under-maintenance-error.tsx +++ b/src/app/(auth)/errors/under-maintenance/components/under-maintenance-error.tsx @@ -21,7 +21,7 @@ export function UnderMaintenanceError() {

Under Maintenance

The service is currently unavailable. Please try again later.

- + diff --git a/src/app/(auth)/forgot-password/components/forgot-password-form-1.tsx b/src/app/(auth)/forgot-password/components/forgot-password-form-1.tsx index 7f3ff9e..075d8ee 100644 --- a/src/app/(auth)/forgot-password/components/forgot-password-form-1.tsx +++ b/src/app/(auth)/forgot-password/components/forgot-password-form-1.tsx @@ -2,7 +2,7 @@ import Link from "next/link"; import { useActionState } from "react"; -import { ArrowLeft, Loader2, MailCheck } from "lucide-react"; +import { ArrowLeft, CircleNotch, EnvelopeOpen } from '@/lib/icons'; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; @@ -28,7 +28,7 @@ export function ForgotPasswordForm1({ className, ...props }: React.ComponentProp {state.ok ? (
- +

Bağlantı emailinize gönderildi. Gelen kutusunu kontrol edin. @@ -64,7 +64,7 @@ export function ForgotPasswordForm1({ className, ...props }: React.ComponentProp

diff --git a/src/app/(auth)/sign-in-3/components/login-form-3.tsx b/src/app/(auth)/sign-in-3/components/login-form-3.tsx index 2109737..daf71fa 100644 --- a/src/app/(auth)/sign-in-3/components/login-form-3.tsx +++ b/src/app/(auth)/sign-in-3/components/login-form-3.tsx @@ -71,7 +71,7 @@ export function LoginForm3({ fill="currentColor" /> - Login with Apple + Login with AppleLogo
@@ -98,7 +98,7 @@ export function SignupForm1({

- İşletmenizi büyütecek tek araç. + Emlak ofisinizi büyütecek tek araç.

Hesap oluşturduktan sonra çalışma alanınızı kuruyor, ekibinizi davet ediyor ve hemen kullanmaya başlıyorsunuz.

    -
  • • Müşteri & hizmet yönetimi
  • -
  • • Görev ve takvim
  • -
  • • Finans ve fatura
  • +
  • • Portföy ve müşteri yönetimi
  • +
  • • Otomatik ilan-müşteri eşleştirme
  • +
  • • Komisyon ve finans takibi
-
KovakSoft tarafından
+
Kovak Yazılım ve Medya LTD. ŞTİ.
); diff --git a/src/app/(dashboard)/academy/page.tsx b/src/app/(dashboard)/academy/page.tsx new file mode 100644 index 0000000..33f0bb9 --- /dev/null +++ b/src/app/(dashboard)/academy/page.tsx @@ -0,0 +1,23 @@ +import { GraduationCap } from '@/lib/icons'; +import { AcademyClient } from "@/components/academy/academy-client"; + +export const metadata = { title: "Akademi | KovakEmlak" }; + +export default function AcademyPage() { + return ( +
+
+
+ +
+
+

Akademi

+

+ Her modülün turunu başlatarak sistemi adım adım öğrenin. +

+
+
+ +
+ ); +} diff --git a/src/app/(dashboard)/customers/[id]/page.tsx b/src/app/(dashboard)/customers/[id]/page.tsx new file mode 100644 index 0000000..cbc555f --- /dev/null +++ b/src/app/(dashboard)/customers/[id]/page.tsx @@ -0,0 +1,301 @@ +export const dynamic = "force-dynamic"; + +import { notFound } from "next/navigation"; +import Link from "next/link"; +import { ArrowLeft, Phone, Envelope, CalendarCheck, Tag } from '@/lib/icons'; +import { Query } from "node-appwrite"; + +import { requireTenant } from "@/lib/appwrite/tenant-guard"; +import { getCustomer } from "@/lib/appwrite/customer-queries"; +import { + DATABASE_ID, TABLES, + CUSTOMER_TYPE_LABELS, CUSTOMER_STAGE_LABELS, CUSTOMER_SOURCE_LABELS, + ACTIVITY_TYPE_LABELS, PROPERTY_TYPE_LABELS, LISTING_TYPE_LABELS, + type Activity, type CustomerSearch, type PropertyMatch, type Property, +} from "@/lib/appwrite/schema"; +import { createAdminClient } from "@/lib/appwrite/server"; +import { Badge } from "@/components/ui/badge"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; + +interface Props { + params: Promise<{ id: string }>; +} + +const STAGE_COLORS: Record = { + ilk_temas: "bg-slate-100 text-slate-700", + aktif_arama: "bg-blue-100 text-blue-700", + teklif: "bg-amber-100 text-amber-700", + sozlesme: "bg-purple-100 text-purple-700", + kapandi: "bg-emerald-100 text-emerald-700", +}; + +export default async function CustomerDetailPage({ params }: Props) { + const { id } = await params; + const ctx = await requireTenant(); + const { tablesDB } = createAdminClient(); + + const customer = await getCustomer(id, ctx.tenantId); + if (!customer) notFound(); + + const [activitiesRes, searchesRes, matchesRes] = await Promise.all([ + tablesDB.listRows({ + databaseId: DATABASE_ID, + tableId: TABLES.activities, + queries: [ + Query.equal("customerId", id), + Query.equal("tenantId", ctx.tenantId), + Query.orderDesc("$createdAt"), + Query.limit(20), + ], + }), + tablesDB.listRows({ + databaseId: DATABASE_ID, + tableId: TABLES.customerSearches, + queries: [ + Query.equal("customerId", id), + Query.equal("tenantId", ctx.tenantId), + Query.limit(20), + ], + }), + tablesDB.listRows({ + databaseId: DATABASE_ID, + tableId: TABLES.propertyMatches, + queries: [ + Query.equal("customerId", id), + Query.equal("tenantId", ctx.tenantId), + Query.orderDesc("score"), + Query.limit(20), + ], + }), + ]); + + const activities = JSON.parse(JSON.stringify(activitiesRes.rows)) as Activity[]; + const searches = JSON.parse(JSON.stringify(searchesRes.rows)) as CustomerSearch[]; + const matches = JSON.parse(JSON.stringify(matchesRes.rows)) as PropertyMatch[]; + + // Fetch matched properties for display + const matchedPropertyIds = [...new Set(matches.map((m) => m.propertyId))]; + const propertiesMap: Record = {}; + if (matchedPropertyIds.length > 0) { + const propsRes = await tablesDB.listRows({ + databaseId: DATABASE_ID, + tableId: TABLES.properties, + queries: [ + Query.equal("$id", matchedPropertyIds.slice(0, 25)), + Query.limit(25), + ], + }); + for (const row of propsRes.rows) { + const p = row as unknown as Property; + propertiesMap[p.$id] = p; + } + } + + const stageKey = customer.stage ?? "ilk_temas"; + + function parseJsonList(json?: string | null): string[] { + if (!json) return []; + try { return JSON.parse(json) as string[]; } catch { return [json]; } + } + + return ( +
+ {/* Back */} +
+ + + Müşteriler + +
+ + {/* Header */} +
+
+

{customer.name}

+
+ {CUSTOMER_TYPE_LABELS[customer.type] ?? customer.type} + + {CUSTOMER_STAGE_LABELS[stageKey as keyof typeof CUSTOMER_STAGE_LABELS] ?? stageKey} + + {customer.source && ( + + + {CUSTOMER_SOURCE_LABELS[customer.source] ?? customer.source} + + )} +
+
+ + Düzenle → + +
+ +
+ {/* Left: info + searches + activities */} +
+ {/* Contact card */} + + + İletişim + + + {customer.phone && ( + + )} + {customer.email && ( + + )} + {customer.nextFollowUpDate && ( +
+ + Takip: {new Date(customer.nextFollowUpDate).toLocaleDateString("tr-TR", { day: "numeric", month: "long", year: "numeric" })} +
+ )} + {!customer.phone && !customer.email && !customer.nextFollowUpDate && ( +

İletişim bilgisi yok.

+ )} + {customer.notes && ( +
+

{customer.notes}

+
+ )} +
+
+ + {/* MagnifyingGlass criteria */} + {searches.length > 0 && ( + + + Arama Kriterleri ({searches.length}) + + + {searches.map((s) => ( +
+
+ + {s.listingType ? (s.listingType === "satilik" ? "Satılık" : "Kiralık") : "Tümü"} + {parseJsonList(s.propertyTypes).length > 0 && ` · ${parseJsonList(s.propertyTypes).join(", ")}`} + + + {s.isActive ? "Aktif" : "Pasif"} + +
+
+ {parseJsonList(s.roomCounts).length > 0 && ( + Oda: {parseJsonList(s.roomCounts).join(", ")} + )} + {(s.minPrice || s.maxPrice) && ( + Fiyat: {s.minPrice ? s.minPrice.toLocaleString("tr-TR") : "–"} – {s.maxPrice ? s.maxPrice.toLocaleString("tr-TR") : "–"} ₺ + )} + {(s.minM2 || s.maxM2) && ( + m²: {s.minM2 ?? "–"}–{s.maxM2 ?? "–"} + )} + {parseJsonList(s.cities).length > 0 && ( + Şehir: {parseJsonList(s.cities).join(", ")} + )} + {parseJsonList(s.districts).length > 0 && ( + İlçe: {parseJsonList(s.districts).join(", ")} + )} +
+ {s.notes &&

{s.notes}

} +
+ ))} +
+
+ )} + + {/* Activities */} + {activities.length > 0 && ( + + + Aktiviteler ({activities.length}) + + + {activities.map((a) => ( +
+
+ + {ACTIVITY_TYPE_LABELS[a.type] ?? a.type} + +
+
+

{a.title}

+ {a.description && ( +

{a.description}

+ )} +
+ + {new Date(a.$createdAt).toLocaleDateString("tr-TR")} + +
+ ))} +
+
+ )} +
+ + {/* Right: matches */} +
+ + + + Eşleşen İlanlar + {matches.length > 0 && ( + ({matches.length}) + )} + + + + {matches.length === 0 ? ( +

Henüz eşleşme yok.

+ ) : ( + matches.map((m) => { + const p = propertiesMap[m.propertyId]; + const score = m.score ?? 0; + const scoreColor = + score >= 80 ? "text-green-600" : score >= 60 ? "text-blue-600" : score >= 40 ? "text-amber-600" : "text-gray-400"; + return ( +
+
+

+ {p?.title ?? m.propertyId} +

+ {score} +
+ {p && ( +

+ {PROPERTY_TYPE_LABELS[p.propertyType] ?? p.propertyType} + {p.city ? ` · ${p.city}` : ""} + {p.price ? ` · ${p.price.toLocaleString("tr-TR")} ₺` : ""} +

+ )} +
+ + {m.notified ? "Bildirildi" : "Bekliyor"} + + {p && ( + + Detay → + + )} +
+
+ ); + }) + )} +
+
+
+
+
+ ); +} diff --git a/src/app/(dashboard)/dashboard-shell.tsx b/src/app/(dashboard)/dashboard-shell.tsx index b873a27..f11b4b7 100644 --- a/src/app/(dashboard)/dashboard-shell.tsx +++ b/src/app/(dashboard)/dashboard-shell.tsx @@ -1,6 +1,7 @@ "use client"; import React from "react"; +import { IconContext } from "@phosphor-icons/react"; import { AppSidebar } from "@/components/app-sidebar"; import { SiteHeader } from "@/components/site-header"; @@ -28,16 +29,19 @@ export function DashboardShell({ company, children, initialPrefs, + pendingMatchCount = 0, }: { user: ShellUser; company: ShellCompany; children: React.ReactNode; initialPrefs: ThemePrefs; + pendingMatchCount?: number; }) { const [themeCustomizerOpen, setThemeCustomizerOpen] = React.useState(false); const { config } = useSidebarConfig(); return ( + -
-
-
{children}
-
-
+
{children}
@@ -73,11 +74,7 @@ export function DashboardShell({ <> -
-
-
{children}
-
-
+
{children}
)} @@ -97,5 +95,6 @@ export function DashboardShell({ initialPrefs={initialPrefs} />
+
); } diff --git a/src/app/(dashboard)/dashboard/components/chart-area-interactive.tsx b/src/app/(dashboard)/dashboard/components/chart-area-interactive.tsx index 00b97a3..f71b816 100644 --- a/src/app/(dashboard)/dashboard/components/chart-area-interactive.tsx +++ b/src/app/(dashboard)/dashboard/components/chart-area-interactive.tsx @@ -1,291 +1,232 @@ -"use client" +"use client"; -import * as React from "react" -import { Area, AreaChart, CartesianGrid, XAxis } from "recharts" +import { useState, useMemo } from "react"; +import { + Line, LineChart, CartesianGrid, XAxis, YAxis, Pie, PieChart, Cell, +} from "recharts"; -import { useIsMobile } from "@/hooks/use-mobile" import { - Card, - CardAction, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/components/ui/card" + Card, CardContent, CardDescription, CardHeader, CardTitle, +} from "@/components/ui/card"; import { - type ChartConfig, - ChartContainer, - ChartTooltip, - ChartTooltipContent, -} from "@/components/ui/chart" -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select" -import { - ToggleGroup, - ToggleGroupItem, -} from "@/components/ui/toggle-group" + ChartContainer, ChartTooltip, type ChartConfig, +} from "@/components/ui/chart"; -export const description = "An interactive area chart" +type View = "trend" | "dagilim"; -const chartData = [ - { date: "2024-04-01", desktop: 222, mobile: 150 }, - { date: "2024-04-02", desktop: 97, mobile: 180 }, - { date: "2024-04-03", desktop: 167, mobile: 120 }, - { date: "2024-04-04", desktop: 242, mobile: 260 }, - { date: "2024-04-05", desktop: 373, mobile: 290 }, - { date: "2024-04-06", desktop: 301, mobile: 340 }, - { date: "2024-04-07", desktop: 245, mobile: 180 }, - { date: "2024-04-08", desktop: 409, mobile: 320 }, - { date: "2024-04-09", desktop: 59, mobile: 110 }, - { date: "2024-04-10", desktop: 261, mobile: 190 }, - { date: "2024-04-11", desktop: 327, mobile: 350 }, - { date: "2024-04-12", desktop: 292, mobile: 210 }, - { date: "2024-04-13", desktop: 342, mobile: 380 }, - { date: "2024-04-14", desktop: 137, mobile: 220 }, - { date: "2024-04-15", desktop: 120, mobile: 170 }, - { date: "2024-04-16", desktop: 138, mobile: 190 }, - { date: "2024-04-17", desktop: 446, mobile: 360 }, - { date: "2024-04-18", desktop: 364, mobile: 410 }, - { date: "2024-04-19", desktop: 243, mobile: 180 }, - { date: "2024-04-20", desktop: 89, mobile: 150 }, - { date: "2024-04-21", desktop: 137, mobile: 200 }, - { date: "2024-04-22", desktop: 224, mobile: 170 }, - { date: "2024-04-23", desktop: 138, mobile: 230 }, - { date: "2024-04-24", desktop: 387, mobile: 290 }, - { date: "2024-04-25", desktop: 215, mobile: 250 }, - { date: "2024-04-26", desktop: 75, mobile: 130 }, - { date: "2024-04-27", desktop: 383, mobile: 420 }, - { date: "2024-04-28", desktop: 122, mobile: 180 }, - { date: "2024-04-29", desktop: 315, mobile: 240 }, - { date: "2024-04-30", desktop: 454, mobile: 380 }, - { date: "2024-05-01", desktop: 165, mobile: 220 }, - { date: "2024-05-02", desktop: 293, mobile: 310 }, - { date: "2024-05-03", desktop: 247, mobile: 190 }, - { date: "2024-05-04", desktop: 385, mobile: 420 }, - { date: "2024-05-05", desktop: 481, mobile: 390 }, - { date: "2024-05-06", desktop: 498, mobile: 520 }, - { date: "2024-05-07", desktop: 388, mobile: 300 }, - { date: "2024-05-08", desktop: 149, mobile: 210 }, - { date: "2024-05-09", desktop: 227, mobile: 180 }, - { date: "2024-05-10", desktop: 293, mobile: 330 }, - { date: "2024-05-11", desktop: 335, mobile: 270 }, - { date: "2024-05-12", desktop: 197, mobile: 240 }, - { date: "2024-05-13", desktop: 197, mobile: 160 }, - { date: "2024-05-14", desktop: 448, mobile: 490 }, - { date: "2024-05-15", desktop: 473, mobile: 380 }, - { date: "2024-05-16", desktop: 338, mobile: 400 }, - { date: "2024-05-17", desktop: 499, mobile: 420 }, - { date: "2024-05-18", desktop: 315, mobile: 350 }, - { date: "2024-05-19", desktop: 235, mobile: 180 }, - { date: "2024-05-20", desktop: 177, mobile: 230 }, - { date: "2024-05-21", desktop: 82, mobile: 140 }, - { date: "2024-05-22", desktop: 81, mobile: 120 }, - { date: "2024-05-23", desktop: 252, mobile: 290 }, - { date: "2024-05-24", desktop: 294, mobile: 220 }, - { date: "2024-05-25", desktop: 201, mobile: 250 }, - { date: "2024-05-26", desktop: 213, mobile: 170 }, - { date: "2024-05-27", desktop: 420, mobile: 460 }, - { date: "2024-05-28", desktop: 233, mobile: 190 }, - { date: "2024-05-29", desktop: 78, mobile: 130 }, - { date: "2024-05-30", desktop: 340, mobile: 280 }, - { date: "2024-05-31", desktop: 178, mobile: 230 }, - { date: "2024-06-01", desktop: 178, mobile: 200 }, - { date: "2024-06-02", desktop: 470, mobile: 410 }, - { date: "2024-06-03", desktop: 103, mobile: 160 }, - { date: "2024-06-04", desktop: 439, mobile: 380 }, - { date: "2024-06-05", desktop: 88, mobile: 140 }, - { date: "2024-06-06", desktop: 294, mobile: 250 }, - { date: "2024-06-07", desktop: 323, mobile: 370 }, - { date: "2024-06-08", desktop: 385, mobile: 320 }, - { date: "2024-06-09", desktop: 438, mobile: 480 }, - { date: "2024-06-10", desktop: 155, mobile: 200 }, - { date: "2024-06-11", desktop: 92, mobile: 150 }, - { date: "2024-06-12", desktop: 492, mobile: 420 }, - { date: "2024-06-13", desktop: 81, mobile: 130 }, - { date: "2024-06-14", desktop: 426, mobile: 380 }, - { date: "2024-06-15", desktop: 307, mobile: 350 }, - { date: "2024-06-16", desktop: 371, mobile: 310 }, - { date: "2024-06-17", desktop: 475, mobile: 520 }, - { date: "2024-06-18", desktop: 107, mobile: 170 }, - { date: "2024-06-19", desktop: 341, mobile: 290 }, - { date: "2024-06-20", desktop: 408, mobile: 450 }, - { date: "2024-06-21", desktop: 169, mobile: 210 }, - { date: "2024-06-22", desktop: 317, mobile: 270 }, - { date: "2024-06-23", desktop: 480, mobile: 530 }, - { date: "2024-06-24", desktop: 132, mobile: 180 }, - { date: "2024-06-25", desktop: 141, mobile: 190 }, - { date: "2024-06-26", desktop: 434, mobile: 380 }, - { date: "2024-06-27", desktop: 448, mobile: 490 }, - { date: "2024-06-28", desktop: 149, mobile: 200 }, - { date: "2024-06-29", desktop: 103, mobile: 160 }, - { date: "2024-06-30", desktop: 446, mobile: 400 }, -] +const PIE_COLORS = [ + "hsl(221, 83%, 53%)", + "hsl(142, 71%, 45%)", + "hsl(38, 92%, 50%)", + "hsl(280, 68%, 58%)", + "hsl(10, 80%, 55%)", + "hsl(200, 65%, 50%)", +]; const chartConfig = { - visitors: { - label: "Visitors", - }, - desktop: { - label: "Desktop", - color: "var(--primary)", - }, - mobile: { - label: "Mobile", - color: "var(--primary)", - }, -} satisfies ChartConfig + ilanSayisi: { label: "İlan", color: "hsl(221, 83%, 53%)" }, + musteriSayisi: { label: "Müşteri", color: "hsl(142, 71%, 45%)" }, + aktiviteSayisi: { label: "Aktivite", color: "hsl(38, 92%, 50%)" }, +} satisfies ChartConfig; -export function ChartAreaInteractive() { - const isMobile = useIsMobile() - const [timeRange, setTimeRange] = React.useState("90d") +const SERIES = [ + { dataKey: "ilanSayisi", label: "İlan", color: "hsl(221, 83%, 53%)" }, + { dataKey: "musteriSayisi", label: "Müşteri", color: "hsl(142, 71%, 45%)" }, + { dataKey: "aktiviteSayisi", label: "Aktivite", color: "hsl(38, 92%, 50%)" }, +] as const; - React.useEffect(() => { - if (isMobile) { - setTimeRange("7d") - } - }, [isMobile]) +export function ChartAreaInteractive({ + ilanTrend, musteriTrend, aktiviteTrend, portfoyDagilim, +}: { + ilanTrend: { ay: string; ilanSayisi: number }[]; + musteriTrend: { ay: string; musteriSayisi: number }[]; + aktiviteTrend: { ay: string; aktiviteSayisi: number }[]; + portfoyDagilim: { tip: string; label: string; sayi: number }[]; +}) { + const [view, setView] = useState("trend"); - const filteredData = chartData.filter((item) => { - const date = new Date(item.date) - const referenceDate = new Date("2024-06-30") - let daysToSubtract = 90 - if (timeRange === "30d") { - daysToSubtract = 30 - } else if (timeRange === "7d") { - daysToSubtract = 7 - } - const startDate = new Date(referenceDate) - startDate.setDate(startDate.getDate() - daysToSubtract) - return date >= startDate - }) + const trendData = useMemo(() => + ilanTrend.map((item, i) => ({ + ay: item.ay, + ilanSayisi: item.ilanSayisi, + musteriSayisi: musteriTrend[i]?.musteriSayisi ?? 0, + aktiviteSayisi: aktiviteTrend[i]?.aktiviteSayisi ?? 0, + })), + [ilanTrend, musteriTrend, aktiviteTrend] + ); + + const dagilimTotal = portfoyDagilim.reduce((s, d) => s + d.sayi, 0); return ( - - - Total Visitors - - - Total for the last 3 months - - Last 3 months - - - - Last 3 months - Last 30 days - Last 7 days - - - + /* + * Mobil: kart doğal yüksekliğinde (overflow: hidden her iki eksen). + * Desktop: lg:h-full ile grid item'ı tamamen doldurur (items-stretch ile eşit yükseklik). + */ + + +
+
+ Portföy Analitiği + + {view === "trend" + ? "Son 6 ay — ilan, müşteri ve aktivite" + : "Aktif portföy dağılımı"} + +
+
+ {(["trend", "dagilim"] as View[]).map((v, i) => ( + + ))} +
+
+ {view === "trend" && ( +
+ {SERIES.map((s) => ( + + + {s.label} + + ))} +
+ )}
- - - - - - + {view === "trend" ? ( + /* + * h-[180px]: mobilde sabit yükseklik → Recharts collapse etmez. + * lg:h-full: desktop'ta CardContent'in tamamını doldurur. + */ + + + + + + { + if (!active || !payload?.length) return null; + return ( +
+

{label}

+ {payload.map((p) => { + const s = SERIES.find((x) => x.dataKey === p.dataKey); + return ( +
+ + {s?.label} + {p.value} +
+ ); + })} +
+ ); + }} + /> + {SERIES.map((s) => ( + - -
- - - - -
- - { - const date = new Date(value) - return date.toLocaleDateString("en-US", { - month: "short", - day: "numeric", - }) - }} - /> - { - return new Date(value as string | number | Date).toLocaleDateString("en-US", { - month: "short", - day: "numeric", - }) - }} - indicator="dot" - /> - } - /> - - -
-
+ ))} + + + ) : ( +
+ {portfoyDagilim.length === 0 ? ( +

+ Henüz aktif ilan yok +

+ ) : ( + <> +
+ + + {portfoyDagilim.map((_, i) => ( + + ))} + + { + if (!active || !payload?.[0]) return null; + const d = payload[0].payload as { label: string; sayi: number }; + const pct = dagilimTotal > 0 ? Math.round((d.sayi / dagilimTotal) * 100) : 0; + return ( +
+

{d.label}

+

{d.sayi} ilan · %{pct}

+
+ ); + }} + /> +
+
+
+ {portfoyDagilim.map((d, i) => { + const pct = dagilimTotal > 0 ? Math.round((d.sayi / dagilimTotal) * 100) : 0; + return ( +
+
+ + {d.label} + {d.sayi} + %{pct} +
+
+
+
+
+ ); + })} +
+ + )} +
+ )} - ) + ); } diff --git a/src/app/(dashboard)/dashboard/components/dashboard-carousel.tsx b/src/app/(dashboard)/dashboard/components/dashboard-carousel.tsx new file mode 100644 index 0000000..acd9dcd --- /dev/null +++ b/src/app/(dashboard)/dashboard/components/dashboard-carousel.tsx @@ -0,0 +1,53 @@ +"use client"; + +import { useRef, useState, useCallback } from "react"; + +export function DashboardCarousel({ + children, + className, + count, +}: { + children: React.ReactNode; + className: string; + count: number; +}) { + const scrollRef = useRef(null); + const [active, setActive] = useState(0); + + const onScroll = useCallback(() => { + const el = scrollRef.current; + if (!el) return; + const total = el.scrollWidth - el.clientWidth; + if (total <= 0) return; + setActive(Math.round((el.scrollLeft / total) * (count - 1))); + }, [count]); + + const scrollTo = (i: number) => { + const el = scrollRef.current; + if (!el) return; + const total = el.scrollWidth - el.clientWidth; + el.scrollTo({ left: (total / (count - 1)) * i, behavior: "smooth" }); + }; + + return ( +
+
+ {children} +
+
+ {Array.from({ length: count }, (_, i) => ( +
+
+ ); +} diff --git a/src/app/(dashboard)/dashboard/components/data-table.tsx b/src/app/(dashboard)/dashboard/components/data-table.tsx index c18a38f..6b7e116 100644 --- a/src/app/(dashboard)/dashboard/components/data-table.tsx +++ b/src/app/(dashboard)/dashboard/components/data-table.tsx @@ -21,19 +21,19 @@ import { } from "@dnd-kit/sortable" import { CSS } from "@dnd-kit/utilities" import { - ChevronDown, - ChevronLeft, - ChevronRight, - ChevronsLeft, - ChevronsRight, - CircleCheckBig, - EllipsisVertical, - GripVertical, - Columns2, - Loader, + CaretDown, + CaretLeft, + CaretRight, + CaretDoubleLeft, + CaretDoubleRight, + CheckCircle, + DotsThreeVertical, + DotsSixVertical, + Columns, + CircleNotch, Plus, - TrendingUp, -} from "lucide-react" + TrendUp, +} from '@/lib/icons' import { type ColumnDef, type ColumnFiltersState, @@ -121,7 +121,7 @@ function DragHandle({ id }: { id: number }) { size="icon" className="text-muted-foreground size-7 hover:bg-transparent cursor-move" > - + Drag to reorder ) @@ -184,9 +184,9 @@ const columns: ColumnDef>[] = [ cell: ({ row }) => ( {row.original.status === "Done" ? ( - + ) : ( - + )} {row.original.status} @@ -286,7 +286,7 @@ const columns: ColumnDef>[] = [ className="data-[state=open]:bg-muted text-muted-foreground flex size-8 cursor-pointer" size="icon" > - + Open menu @@ -629,7 +629,7 @@ export function DataTable({ disabled={!currentTable.getCanPreviousPage()} > Go to first page - +
@@ -705,10 +705,10 @@ export function DataTable({ @@ -838,7 +838,7 @@ export function DataTable({ disabled={!table.getCanPreviousPage()} > Go to first page - +
@@ -992,7 +992,7 @@ function TableCellViewer({ item }: { item: z.infer }) {
Trending up by 5.2% this month{" "} - +
Showing total visitors for the last 6 months. This is just diff --git a/src/app/(dashboard)/dashboard/components/follow-up-widget.tsx b/src/app/(dashboard)/dashboard/components/follow-up-widget.tsx new file mode 100644 index 0000000..30bcda8 --- /dev/null +++ b/src/app/(dashboard)/dashboard/components/follow-up-widget.tsx @@ -0,0 +1,78 @@ +import { Phone, Envelope, CalendarCheck } from '@/lib/icons'; + +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { CUSTOMER_STAGE_LABELS, CUSTOMER_TYPE_LABELS, type Customer } from "@/lib/appwrite/schema"; + +function isOverdue(date: string) { + const today = new Date(); + today.setHours(0, 0, 0, 0); + return new Date(date) < today; +} + +export function FollowUpWidget({ customers }: { customers: Customer[] }) { + const empty = customers.length === 0; + + return ( + + + + + Bugünkü Takipler + {!empty && ( + {customers.length} + )} + + + + {empty ? ( +

+ Bekleyen takip yok +

+ ) : ( +
    + {customers.map((c) => { + const overdue = c.nextFollowUpDate ? isOverdue(c.nextFollowUpDate) : false; + return ( +
  • +
    +

    {c.name}

    +

    + {CUSTOMER_TYPE_LABELS[c.type] ?? c.type} + {c.stage ? ` · ${CUSTOMER_STAGE_LABELS[c.stage] ?? c.stage}` : ""} +

    +
    +
    + {overdue && ( + + Gecikti + + )} + {c.phone && ( + + + + )} + {c.email && ( + + + + )} +
    +
  • + ); + })} +
+ )} +
+
+ ); +} diff --git a/src/app/(dashboard)/dashboard/components/quick-actions.tsx b/src/app/(dashboard)/dashboard/components/quick-actions.tsx index 712556e..e90e1c8 100644 --- a/src/app/(dashboard)/dashboard/components/quick-actions.tsx +++ b/src/app/(dashboard)/dashboard/components/quick-actions.tsx @@ -1,5 +1,5 @@ import Link from "next/link"; -import { Calendar, FilePlus, Receipt, UserPlus } from "lucide-react"; +import { Calendar, FilePlus, Receipt, UserPlus } from '@/lib/icons'; import { Button } from "@/components/ui/button"; diff --git a/src/app/(dashboard)/dashboard/components/recent-activities.tsx b/src/app/(dashboard)/dashboard/components/recent-activities.tsx new file mode 100644 index 0000000..e423578 --- /dev/null +++ b/src/app/(dashboard)/dashboard/components/recent-activities.tsx @@ -0,0 +1,63 @@ +import { formatDistanceToNow } from "date-fns"; +import { tr } from "date-fns/locale"; +import { ChatCircle, FileText, Eye, Phone, Note } from '@/lib/icons'; + +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import type { Activity } from "@/lib/appwrite/schema"; + +const typeConfig: Record = { + gorusme: { label: "Görüşme", icon: ChatCircle, variant: "default" }, + teklif: { label: "Teklif", icon: FileText, variant: "secondary" }, + ziyaret: { label: "Ziyaret", icon: Eye, variant: "outline" }, + arama: { label: "Arama", icon: Phone, variant: "outline" }, + not: { label: "Not", icon: Note, variant: "outline" }, +}; + +export function RecentActivities({ activities }: { activities: Activity[] }) { + return ( + + + Son Aktiviteler + + {/* Mobil: içerik sayfayı scroll ettirir. Desktop: kart içinde kalır. */} + + {activities.length === 0 ? ( +

+ Henüz aktivite yok +

+ ) : ( +
    + {activities.map((a) => { + const cfg = typeConfig[a.type] ?? typeConfig.not; + const Icon = cfg.icon; + return ( +
  • + + + +
    +
    +

    {a.title}

    + + {cfg.label} + +
    + {a.description && ( +

    + {a.description} +

    + )} +

    + {formatDistanceToNow(new Date(a.$createdAt), { addSuffix: true, locale: tr })} +

    +
    +
  • + ); + })} +
+ )} +
+
+ ); +} diff --git a/src/app/(dashboard)/dashboard/components/recent-properties.tsx b/src/app/(dashboard)/dashboard/components/recent-properties.tsx new file mode 100644 index 0000000..0724944 --- /dev/null +++ b/src/app/(dashboard)/dashboard/components/recent-properties.tsx @@ -0,0 +1,77 @@ +import Link from "next/link"; +import { formatDistanceToNow } from "date-fns"; +import { tr } from "date-fns/locale"; + +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import type { Property } from "@/lib/appwrite/schema"; + +const statusDot: Record = { + aktif: { color: "bg-emerald-500", label: "Aktif" }, + pasif: { color: "bg-zinc-400", label: "Pasif" }, + satildi: { color: "bg-blue-500", label: "Satıldı" }, + kiralandit: { color: "bg-orange-400", label: "Kiralandı" }, +}; + +const listingLabel: Record = { satilik: "Satılık", kiralik: "Kiralık" }; +const typeLabel: Record = { + daire: "Daire", villa: "Villa", arsa: "Arsa", + dukkan: "Dükkan", ofis: "Ofis", depo: "Depo", +}; + +function formatPrice(price: number, currency = "TRY") { + return new Intl.NumberFormat("tr-TR", { + style: "currency", currency, maximumFractionDigits: 0, + }).format(price); +} + +export function RecentProperties({ properties }: { properties: Property[] }) { + return ( + + + Son Eklenen İlanlar + + + {properties.length === 0 ? ( +

+ Henüz ilan yok +

+ ) : ( +
    + {properties.map((p) => { + const dot = statusDot[p.status] ?? { color: "bg-zinc-400", label: p.status }; + return ( +
  • + + +
    +

    {p.title}

    +

    + {typeLabel[p.propertyType] ?? p.propertyType} · {listingLabel[p.listingType] ?? p.listingType} + {p.city ? ` · ${p.city}` : ""} + {p.roomCount ? ` · ${p.roomCount}` : ""} +

    +
    +
    +

    + {formatPrice(p.price, p.currency)} +

    + + {formatDistanceToNow(new Date(p.$createdAt), { addSuffix: true, locale: tr })} + +
    + +
  • + ); + })} +
+ )} +
+
+ ); +} diff --git a/src/app/(dashboard)/dashboard/components/section-cards.tsx b/src/app/(dashboard)/dashboard/components/section-cards.tsx index ca05f99..fd8bc07 100644 --- a/src/app/(dashboard)/dashboard/components/section-cards.tsx +++ b/src/app/(dashboard)/dashboard/components/section-cards.tsx @@ -1,102 +1,58 @@ -import { TrendingDown, TrendingUp } from "lucide-react" +import { Buildings, Users, Lightning, TrendUp } from '@/lib/icons'; -import { Badge } from "@/components/ui/badge" -import { - Card, - CardAction, - CardDescription, - CardFooter, - CardHeader, - CardTitle, -} from "@/components/ui/card" +import type { DashboardStats } from "@/lib/appwrite/dashboard-queries"; + +export function SectionCards({ stats }: { stats: DashboardStats }) { + const items = [ + { + label: "Aktif İlan", + value: stats.aktifIlanlar, + icon: Buildings, + sub: [ + `${stats.satilikAktif} sat`, + `${stats.kiralikAktif} kir`, + ...(stats.rezerveIlanlar > 0 ? [`${stats.rezerveIlanlar} rsv`] : []), + ].join(" · "), + }, + { + label: "Müşteri", + value: stats.toplamMusteri, + icon: Users, + sub: `${stats.aliciMusteri} alıcı · ${stats.kiraciMusteri} kiracı · ${stats.yatirimciMusteri} yat`, + }, + { + label: "Bekleyen Eşleşme", + value: stats.bekleyenEslesmeler, + icon: Lightning, + sub: "iletilmemiş bildirim", + accent: stats.bekleyenEslesmeler > 0, + }, + { + label: "Bu Ay Eklenen", + value: stats.buAyIlanlar, + icon: TrendUp, + sub: "yeni ilan", + }, + ]; -export function SectionCards() { return ( -
- - - Total Revenue - - $1,250.00 - - - - - +12.5% - - - - -
- Trending up this month +
+ {items.map((item) => ( +
+
+
-
- Visitors for the last 6 months +
+

{item.value}

+

{item.label}

+

{item.sub}

- - - - - New Customers - - 1,234 - - - - - -20% - - - - -
- Down 20% this period -
-
- Acquisition needs attention -
-
-
- - - Active Accounts - - 45,678 - - - - - +12.5% - - - - -
- Strong user retention -
-
Engagement exceed targets
-
-
- - - Growth Rate - - 4.5% - - - - - +4.5% - - - - -
- Steady performance increase -
-
Meets growth projections
-
-
+
+ ))}
- ) + ); } diff --git a/src/app/(dashboard)/dashboard/page.tsx b/src/app/(dashboard)/dashboard/page.tsx index 4ae67b7..a3bdd60 100644 --- a/src/app/(dashboard)/dashboard/page.tsx +++ b/src/app/(dashboard)/dashboard/page.tsx @@ -1,40 +1,100 @@ +export const dynamic = "force-dynamic"; + import { redirect } from "next/navigation"; +import Link from "next/link"; +import { Buildings, UserPlus, GitMerge, Plus } from '@/lib/icons'; import { getActiveContext } from "@/lib/appwrite/active-context"; +import { getDashboardStats } from "@/lib/appwrite/dashboard-queries"; +import { Button } from "@/components/ui/button"; +import { SectionCards } from "./components/section-cards"; +import { ChartAreaInteractive } from "./components/chart-area-interactive"; +import { RecentActivities } from "./components/recent-activities"; +import { RecentProperties } from "./components/recent-properties"; +import { FollowUpWidget } from "./components/follow-up-widget"; export default async function DashboardPage() { const ctx = await getActiveContext(); if (!ctx) redirect("/onboarding"); + const stats = await getDashboardStats(ctx.tenantId); + const firstName = ctx.user.name?.split(" ")[0] ?? ""; const officeName = ctx.settings?.officeName ?? "Çalışma alanı"; return ( -
-
-

{officeName}

-

- {firstName ? `Hoş geldiniz, ${firstName}` : "Genel Bakış"} -

-

- Portföyünüzü ve müşteri aktivitelerini buradan takip edin. -

+ // overflow-x-hidden: Recharts SVG ve diğer absolute-positioned elementlerin + // yatay taşmasını keser; tooltip'ler kart içinde render olduğu için etkilenmez. +
+ + {/* Başlık */} +
+
+

{officeName}

+

+ {firstName ? `Hoş geldiniz, ${firstName}` : "Genel Bakış"} +

+
+
+ + + + +
-
-
-

Aktif İlanlar

-

+ {/* İstatistik şeridi */} +
+ +
+ + {/* Grafik + Son Aktiviteler + Mobil: tek kolon, alt alta. + Desktop (lg): 3+2 kolon, eşit yükseklik (items-stretch). */} +
+
+
-
-

Müşteriler

-

-
-
-

Bekleyen Eşleşmeler

-

+
+
+ + {/* Son İlanlar + Bugünkü Takipler */} +
+
+ +
+
+ +
+
+
); } diff --git a/src/app/(dashboard)/finance/page.tsx b/src/app/(dashboard)/finance/page.tsx new file mode 100644 index 0000000..cc50a23 --- /dev/null +++ b/src/app/(dashboard)/finance/page.tsx @@ -0,0 +1,45 @@ +import { requireTenant } from "@/lib/appwrite/tenant-guard"; +import { createAdminClient } from "@/lib/appwrite/server"; +import { DATABASE_ID, TABLES } from "@/lib/appwrite/schema"; +import { listCustomers } from "@/lib/appwrite/customer-queries"; +import { Query } from "node-appwrite"; +import { FinanceClient } from "@/components/finance/finance-client"; +import type { Deal } from "@/lib/appwrite/schema"; + +export default async function FinancePage() { + const ctx = await requireTenant(); + const { tablesDB } = createAdminClient(); + + const dealQueries = [ + Query.equal("tenantId", ctx.tenantId), + Query.orderDesc("$createdAt"), + Query.limit(200), + ]; + + if (ctx.role === "member") { + dealQueries.push(Query.equal("agentId", ctx.user.id)); + } + + const [dealsResult, customers] = await Promise.all([ + tablesDB.listRows({ + databaseId: DATABASE_ID, + tableId: TABLES.deals, + queries: dealQueries, + }), + listCustomers(ctx.tenantId), + ]); + + const deals = JSON.parse(JSON.stringify(dealsResult.rows)) as Deal[]; + + return ( +
+ +
+ ); +} diff --git a/src/app/(dashboard)/layout.tsx b/src/app/(dashboard)/layout.tsx index cbb67fc..35e1f84 100644 --- a/src/app/(dashboard)/layout.tsx +++ b/src/app/(dashboard)/layout.tsx @@ -1,8 +1,10 @@ import { redirect } from "next/navigation"; +import { Query } from "node-appwrite"; import { getActiveContext } from "@/lib/appwrite/active-context"; import { getLogoUrl } from "@/lib/appwrite/storage"; -import { createSessionClient } from "@/lib/appwrite/server"; +import { createAdminClient, createSessionClient } from "@/lib/appwrite/server"; +import { DATABASE_ID, TABLES } from "@/lib/appwrite/schema"; import type { ThemePrefs } from "@/lib/appwrite/theme-prefs-actions"; import { DashboardShell } from "./dashboard-shell"; @@ -14,6 +16,17 @@ export default async function DashboardLayout({ const ctx = await getActiveContext(); if (!ctx) redirect("/onboarding"); + let pendingMatchCount = 0; + try { + const { tablesDB } = createAdminClient(); + const res = await tablesDB.listRows({ + databaseId: DATABASE_ID, + tableId: TABLES.propertyMatches, + queries: [Query.equal("tenantId", ctx.tenantId), Query.equal("notified", false), Query.limit(1)], + }); + pendingMatchCount = res.total; + } catch { /* non-critical */ } + let themePrefs: ThemePrefs = {}; try { const { account } = await createSessionClient(); @@ -37,7 +50,7 @@ export default async function DashboardLayout({ }; return ( - + {children} ); diff --git a/src/app/(dashboard)/properties/[id]/page.tsx b/src/app/(dashboard)/properties/[id]/page.tsx index 7a64c81..87f6f62 100644 --- a/src/app/(dashboard)/properties/[id]/page.tsx +++ b/src/app/(dashboard)/properties/[id]/page.tsx @@ -1,8 +1,6 @@ export const dynamic = "force-dynamic"; import { notFound } from "next/navigation"; -import Link from "next/link"; -import { ArrowLeft } from "lucide-react"; import { Query } from "node-appwrite"; import { requireTenant } from "@/lib/appwrite/tenant-guard"; @@ -10,18 +8,13 @@ import { listCustomers } from "@/lib/appwrite/customer-queries"; import { DATABASE_ID, TABLES, - PROPERTY_TYPE_LABELS, - LISTING_TYPE_LABELS, - PROPERTY_STATUS_LABELS, - ACTIVITY_TYPE_LABELS, type Property, type PropertyMatch, type Activity, } from "@/lib/appwrite/schema"; import { createAdminClient } from "@/lib/appwrite/server"; -import { getPropertyImagePreviewUrl, parseImageIds } from "@/lib/appwrite/storage-utils"; -import { Badge } from "@/components/ui/badge"; -import { PropertyMapView } from "@/components/map/property-map-view"; +import { parseImageIds } from "@/lib/appwrite/storage-utils"; +import { PropertyDetailClient } from "@/components/properties/property-detail-client"; interface Props { params: Promise<{ id: string }>; @@ -71,216 +64,13 @@ export default async function PropertyDetailPage({ params }: Props) { const customerMap = Object.fromEntries(customers.map((c) => [c.$id, c.name])); const imageIds = parseImageIds(property.imageIds); - const statusColor: Record = { - aktif: "bg-green-100 text-green-700", - pasif: "bg-gray-100 text-gray-600", - satildi: "bg-orange-100 text-orange-700", - kiralandit: "bg-blue-100 text-blue-700", - }; - return ( -
- {/* Header */} -
- - - İlanlar - -
- -
-
-

{property.title}

-

- {[property.neighborhood, property.district, property.city].filter(Boolean).join(", ")} -

-
- - {PROPERTY_STATUS_LABELS[property.status] ?? property.status} - -
- - {/* Photo gallery */} - {imageIds.length > 0 && ( -
-
- {imageIds.map((fileId, i) => ( -
2 ? "col-span-2 row-span-2" : ""}`} - > - {/* eslint-disable-next-line @next/next/no-img-element */} - {`${property.title} 2 ? "480px" : "240px" }} - /> -
- ))} -
-
- )} - -
- {/* Price */} -
-
-
- - {property.price.toLocaleString("tr-TR")} - - {property.currency ?? "TRY"} -
- -
- - - {property.roomCount && } - {property.netM2 && } - {property.grossM2 && } - {property.floor != null && } - {property.totalFloors != null && ( - - )} - {property.buildingAge != null && ( - - )} -
- - {property.address && ( -
- Adres: - {property.address} -
- )} - - {property.mapLat != null && property.mapLng != null && ( - - Google Maps'te aç ↗ - - )} -
- - {property.description && ( -
-

Açıklama

-

- {property.description} -

-
- )} - - {property.mapLat != null && property.mapLng != null && ( -
-

Konum

- -
- )} - - {/* Activities */} - {activities.length > 0 && ( -
-

Aktiviteler

-
- {activities.map((a) => ( -
- - {ACTIVITY_TYPE_LABELS[a.type] ?? a.type} - -
-

{a.title}

- {a.description && ( -

{a.description}

- )} -
- - {new Date(a.$createdAt).toLocaleDateString("tr-TR")} - -
- ))} -
-
- )} -
- - {/* Matches sidebar */} -
-
-

- İlgili Müşteriler - {matches.length > 0 && ( - ({matches.length}) - )} -

- {matches.length === 0 ? ( -

Eşleşme yok.

- ) : ( -
- {matches.map((m) => ( -
- - {customerMap[m.customerId] ?? m.customerId} - - -
- ))} -
- )} -
-
-
-
- ); -} - -function Detail({ label, value }: { label: string; value: string }) { - return ( -
- {label}: - {value} -
- ); -} - -function ScoreBadge({ score }: { score?: number | null }) { - const s = score ?? 0; - const color = - s >= 80 - ? "bg-green-100 text-green-700" - : s >= 60 - ? "bg-blue-100 text-blue-700" - : s >= 40 - ? "bg-yellow-100 text-yellow-700" - : "bg-gray-100 text-gray-500"; - return ( - - {s} - + ); } diff --git a/src/app/(dashboard)/settings/account/components/email-form.tsx b/src/app/(dashboard)/settings/account/components/email-form.tsx index 6b33bc8..ab2633c 100644 --- a/src/app/(dashboard)/settings/account/components/email-form.tsx +++ b/src/app/(dashboard)/settings/account/components/email-form.tsx @@ -1,7 +1,7 @@ "use client"; import { useActionState, useEffect, useRef } from "react"; -import { Loader2, Save } from "lucide-react"; +import { CircleNotch, FloppyDisk } from '@/lib/icons'; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; @@ -65,12 +65,12 @@ export function EmailForm({ currentEmail }: { currentEmail: string }) {
diff --git a/src/app/(dashboard)/settings/billing/components/current-plan-card.tsx b/src/app/(dashboard)/settings/billing/components/current-plan-card.tsx index 462bc43..9784826 100644 --- a/src/app/(dashboard)/settings/billing/components/current-plan-card.tsx +++ b/src/app/(dashboard)/settings/billing/components/current-plan-card.tsx @@ -1,12 +1,12 @@ "use client"; -import { Crown, Zap } from "lucide-react"; +import { Crown, Lightning } from '@/lib/icons'; import { Badge } from "@/components/ui/badge"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Progress } from "@/components/ui/progress"; import type { PlanUsage } from "@/lib/appwrite/plan-limits"; import type { TenantPlan } from "@/lib/appwrite/schema"; -import { RESOURCE_LABELS } from "@/lib/appwrite/plan-limits"; +import { RESOURCE_LABELS } from "@/lib/appwrite/plan-limits-shared"; const LIMIT_LABELS: Record = { properties: "İlan", @@ -36,7 +36,7 @@ export function CurrentPlanCard({ variant={isPro ? "default" : "secondary"} className="gap-1" > - {isPro ? : } + {isPro ? : } {isPro ? "Pro" : "Ücretsiz"}
diff --git a/src/app/(dashboard)/settings/billing/components/upgrade-section.tsx b/src/app/(dashboard)/settings/billing/components/upgrade-section.tsx index af320d7..1e1d0a6 100644 --- a/src/app/(dashboard)/settings/billing/components/upgrade-section.tsx +++ b/src/app/(dashboard)/settings/billing/components/upgrade-section.tsx @@ -2,7 +2,7 @@ import { useRef } from "react"; import { toast } from "sonner"; -import { Crown, Check, Loader2 } from "lucide-react"; +import { Crown, Check, CircleNotch } from '@/lib/icons'; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; diff --git a/src/app/(dashboard)/settings/billing/page.tsx b/src/app/(dashboard)/settings/billing/page.tsx index f6de6de..5fe49ba 100644 --- a/src/app/(dashboard)/settings/billing/page.tsx +++ b/src/app/(dashboard)/settings/billing/page.tsx @@ -1,6 +1,6 @@ import type { Metadata } from "next"; import { redirect } from "next/navigation"; -import { CheckCircle2, XCircle } from "lucide-react"; +import { CheckCircle, XCircle } from '@/lib/icons'; import { requireTenant } from "@/lib/appwrite/tenant-guard"; import { getEffectivePlan, getPlanUsage } from "@/lib/appwrite/plan-limits"; @@ -44,7 +44,7 @@ export default async function BillingPage({ {upgraded && (
- + Pro plana başarıyla geçtiniz. İyi kullanımlar!
)} diff --git a/src/app/(dashboard)/settings/connections/page.tsx b/src/app/(dashboard)/settings/connections/page.tsx index 8454f8c..26d0cd9 100644 --- a/src/app/(dashboard)/settings/connections/page.tsx +++ b/src/app/(dashboard)/settings/connections/page.tsx @@ -4,7 +4,7 @@ import { Button } from "@/components/ui/button" import { Badge } from "@/components/ui/badge" import { Separator } from "@/components/ui/separator" import { Switch } from "@/components/ui/switch" -import { Github, Slack, Twitter, Zap, Globe, Database, Apple, Chrome, Facebook, Instagram, Dribbble } from "lucide-react" +import { GithubLogo, SlackLogo, TwitterLogo, Lightning, Globe, Database, AppleLogo, Browser, FacebookLogo, InstagramLogo, DribbbleLogo } from '@/lib/icons' import { useState } from "react" export default function ConnectionSettings() { // Controlled state for switches @@ -37,9 +37,9 @@ export default function ConnectionSettings() {
- +
-
Apple
+
AppleLogo
Calendar and contacts
@@ -52,7 +52,7 @@ export default function ConnectionSettings() {
- +
Google
Calendar and contacts
@@ -67,9 +67,9 @@ export default function ConnectionSettings() {
- +
-
Github
+
GithubLogo
Manage your Git repositories
@@ -82,9 +82,9 @@ export default function ConnectionSettings() {
- +
-
Slack
+
SlackLogo
Communication
@@ -109,13 +109,13 @@ export default function ConnectionSettings() {
- +
- Facebook + FacebookLogo Not Connected
-
Share updates on Facebook
+
Share updates on FacebookLogo
@@ -240,7 +240,7 @@ export function MembersTable({ onClick={handleRemove} disabled={busy !== null} > - {busy ? : } + {busy ? : } Çıkar @@ -269,7 +269,7 @@ export function MembersTable({ onClick={handleLeave} disabled={busy !== null} > - {busy ? : } + {busy ? : } Ayrıl diff --git a/src/app/(dashboard)/settings/members/components/pending-invites-table.tsx b/src/app/(dashboard)/settings/members/components/pending-invites-table.tsx index 06090d9..a8c7ce9 100644 --- a/src/app/(dashboard)/settings/members/components/pending-invites-table.tsx +++ b/src/app/(dashboard)/settings/members/components/pending-invites-table.tsx @@ -1,7 +1,7 @@ "use client"; import { useTransition, useState } from "react"; -import { Check, Copy, Loader2, X } from "lucide-react"; +import { Check, Copy, CircleNotch, X } from '@/lib/icons'; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; @@ -119,7 +119,7 @@ export function PendingInvitesTable({ onClick={() => cancel(inv.id)} > {busy === inv.id ? ( - + ) : ( )} diff --git a/src/app/(dashboard)/settings/notifications/page.tsx b/src/app/(dashboard)/settings/notifications/page.tsx index dbd40a3..958f270 100644 --- a/src/app/(dashboard)/settings/notifications/page.tsx +++ b/src/app/(dashboard)/settings/notifications/page.tsx @@ -17,7 +17,7 @@ import { Checkbox } from "@/components/ui/checkbox" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { Separator } from "@/components/ui/separator" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" -import { Bell, Mail, MessageSquare } from "lucide-react" +import { Bell, Envelope, ChatCircle } from '@/lib/icons' const notificationsFormSchema = z.object({ emailSecurity: z.boolean(), @@ -595,7 +595,7 @@ export default function NotificationSettings() { render={({ field }) => (
- +
Email
Receive notifications via email
@@ -639,7 +639,7 @@ export default function NotificationSettings() { render={({ field }) => (
- +
SMS
Receive notifications via SMS
@@ -659,7 +659,7 @@ export default function NotificationSettings() {
- +
diff --git a/src/app/(dashboard)/settings/user/page.tsx b/src/app/(dashboard)/settings/user/page.tsx index 8b7fae3..0435f97 100644 --- a/src/app/(dashboard)/settings/user/page.tsx +++ b/src/app/(dashboard)/settings/user/page.tsx @@ -17,7 +17,7 @@ import { import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { Textarea } from "@/components/ui/textarea" -import { Upload } from "lucide-react" +import { Upload } from '@/lib/icons' import { useRef, useState } from "react" import { Separator } from "@/components/ui/separator" import { Logo } from "@/components/logo" @@ -95,7 +95,7 @@ export default function UserSettingsPage() {
- Profile Settings + Profile GearSix Update your personal information and preferences @@ -346,7 +346,7 @@ export default function UserSettingsPage() { {/* Action Buttons */}
diff --git a/src/app/(dashboard)/settings/workspace/components/workspace-form.tsx b/src/app/(dashboard)/settings/workspace/components/workspace-form.tsx index c9cc010..c770687 100644 --- a/src/app/(dashboard)/settings/workspace/components/workspace-form.tsx +++ b/src/app/(dashboard)/settings/workspace/components/workspace-form.tsx @@ -1,7 +1,7 @@ "use client"; import { useActionState, useEffect } from "react"; -import { Building2, Loader2, Save } from "lucide-react"; +import { Buildings, CircleNotch, FloppyDisk } from '@/lib/icons'; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; @@ -43,7 +43,7 @@ export function WorkspaceSettingsForm({ - + Ofis Bilgileri Müşterilere ve sunumlarda gösterilecek ofis bilgileri. @@ -117,12 +117,12 @@ export function WorkspaceSettingsForm({ diff --git a/src/app/landing/components/blog-section.tsx b/src/app/landing/components/blog-section.tsx index ae53eb3..e04d183 100644 --- a/src/app/landing/components/blog-section.tsx +++ b/src/app/landing/components/blog-section.tsx @@ -1,7 +1,7 @@ "use client" import Image from 'next/image' -import { ArrowRight } from 'lucide-react' +import { ArrowRight } from '@/lib/icons' import { Card, CardContent } from '@/components/ui/card' import { Badge } from '@/components/ui/badge' diff --git a/src/app/landing/components/contact-section.tsx b/src/app/landing/components/contact-section.tsx index c606fa2..d46c993 100644 --- a/src/app/landing/components/contact-section.tsx +++ b/src/app/landing/components/contact-section.tsx @@ -16,7 +16,7 @@ import { FormLabel, FormMessage, } from "@/components/ui/form" -import { Mail, MessageCircle, Github, BookOpen } from 'lucide-react' +import { Envelope, ChatCircle, GithubLogo, BookOpen } from '@/lib/icons' const contactFormSchema = z.object({ firstName: z.string().min(2, { @@ -74,7 +74,7 @@ export function ContactSection() { - + Discord Community @@ -93,7 +93,7 @@ export function ContactSection() { - + GitHub Issues @@ -134,7 +134,7 @@ export function ContactSection() { - + Send us a message diff --git a/src/app/landing/components/cta-section.tsx b/src/app/landing/components/cta-section.tsx index 9c931e6..08f97ea 100644 --- a/src/app/landing/components/cta-section.tsx +++ b/src/app/landing/components/cta-section.tsx @@ -1,6 +1,6 @@ "use client" -import { ArrowRight, TrendingUp, Package, Github } from 'lucide-react' +import { ArrowRight, TrendUp, Package, GithubLogo } from '@/lib/icons' import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' import { Separator } from '@/components/ui/separator' @@ -15,14 +15,14 @@ export function CTASection() { {/* Badge and Stats */}
- + Productivity Suite
- 150+ Blocks + 150+ SquaresFour 25K+ Downloads @@ -62,7 +62,7 @@ export function CTASection() {
@@ -240,7 +240,7 @@ export function LandingThemeCustomizer({ open, onOpenChange }: LandingThemeCusto
@@ -373,7 +373,7 @@ export function LandingThemeCustomizer({ open, onOpenChange }: LandingThemeCusto className="w-full cursor-pointer" onClick={() => window.open('https://tweakcn.com/editor/theme', '_blank')} > - + Open Tweakcn
@@ -400,7 +400,7 @@ export function LandingThemeCustomizerTrigger({ onClick }: { onClick: () => void "fixed top-1/2 -translate-y-1/2 h-12 w-12 rounded-full shadow-lg z-50 bg-primary hover:bg-primary/90 text-primary-foreground cursor-pointer right-4" )} > - + ) } diff --git a/src/app/landing/components/logo-carousel.tsx b/src/app/landing/components/logo-carousel.tsx index d065de8..932149e 100644 --- a/src/app/landing/components/logo-carousel.tsx +++ b/src/app/landing/components/logo-carousel.tsx @@ -52,7 +52,7 @@ const techCompanies = [ { name: 'Dropbox', id: 'dropbox' }, { name: 'Stripe', id: 'stripe' }, { name: 'Google', id: 'google' }, - { name: 'Apple', id: 'apple' }, + { name: 'AppleLogo', id: 'apple' }, { name: 'Meta', id: 'meta' }, { name: 'Tesla', id: 'tesla' }, { name: 'Salesforce', id: 'salesforce' }, diff --git a/src/app/landing/components/navbar.tsx b/src/app/landing/components/navbar.tsx index bb5a957..cb38f66 100644 --- a/src/app/landing/components/navbar.tsx +++ b/src/app/landing/components/navbar.tsx @@ -2,7 +2,7 @@ import { useState } from 'react' import Link from 'next/link' -import { Menu, Github, LayoutDashboard, ChevronDown, X, Moon, Sun } from 'lucide-react' +import { List, GithubLogo, SquaresFour, CaretDown, X, Moon, Sun } from '@/lib/icons' import { Button } from '@/components/ui/button' import { NavigationMenu, @@ -30,7 +30,7 @@ import { ModeToggle } from '@/components/mode-toggle' import { useTheme } from '@/hooks/use-theme' const navigationItems = [ - { name: 'Home', href: '#hero' }, + { name: 'House', href: '#hero' }, { name: 'Features', href: '#features' }, { name: 'Solutions', href: '#features', hasMegaMenu: true }, { name: 'Team', href: '#team' }, @@ -42,7 +42,7 @@ const navigationItems = [ // Solutions menu items for mobile const solutionsItems = [ { title: 'Browse Products' }, - { name: 'Free Blocks', href: '#free-blocks' }, + { name: 'Free SquaresFour', href: '#free-blocks' }, { name: 'Premium Templates', href: '#premium-templates' }, { name: 'Admin Dashboards', href: '#admin-dashboards' }, { name: 'Landing Pages', href: '#landing-pages' }, @@ -128,12 +128,12 @@ export function LandingNavbar() { @@ -145,11 +145,11 @@ export function LandingNavbar() {
- {/* Mobile Menu */} + {/* Mobile List */} @@ -174,7 +174,7 @@ export function LandingNavbar() { diff --git a/src/app/landing/components/pricing-section.tsx b/src/app/landing/components/pricing-section.tsx index 856d25e..c39aa28 100644 --- a/src/app/landing/components/pricing-section.tsx +++ b/src/app/landing/components/pricing-section.tsx @@ -1,6 +1,6 @@ "use client" -import { Check } from 'lucide-react' +import { Check } from '@/lib/icons' import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group' @@ -101,7 +101,7 @@ export function PricingSection() {

- Save 20% On Annual Billing + FloppyDisk 20% On Annual Billing

diff --git a/src/app/landing/components/stats-section.tsx b/src/app/landing/components/stats-section.tsx index 7f18911..63e3df4 100644 --- a/src/app/landing/components/stats-section.tsx +++ b/src/app/landing/components/stats-section.tsx @@ -5,7 +5,7 @@ import { Download, Users, Star -} from 'lucide-react' +} from '@/lib/icons' import { Card, CardContent } from '@/components/ui/card' import { DotPattern } from '@/components/dot-pattern' diff --git a/src/app/landing/components/team-section.tsx b/src/app/landing/components/team-section.tsx index 7c01382..9721220 100644 --- a/src/app/landing/components/team-section.tsx +++ b/src/app/landing/components/team-section.tsx @@ -5,7 +5,7 @@ import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' import { Card, CardContent } from '@/components/ui/card' import { CardDecorator } from '@/components/ui/card-decorator' -import { Github, Linkedin, Globe } from 'lucide-react' +import { GithubLogo, LinkedinLogo, Globe } from '@/lib/icons' const team = [ @@ -91,7 +91,7 @@ const team = [ id: 7, name: 'James Anderson', role: 'UX Researcher', - description: 'Lead user research for Slack. Contractor for Netflix and Udacity.', + description: 'Lead user research for SlackLogo. Contractor for Netflix and Udacity.', image: 'https://images.unsplash.com/photo-1566492031773-4f4e44671d66?q=60&w=150&auto=format&fit=crop', fallback: 'JA', social: { @@ -181,7 +181,7 @@ export function TeamSection() { rel="noopener noreferrer" aria-label={`${member.name} LinkedIn`} > - + +
+ )} +
+ ); +} diff --git a/src/components/academy/academy-sidebar-badge.tsx b/src/components/academy/academy-sidebar-badge.tsx new file mode 100644 index 0000000..174379e --- /dev/null +++ b/src/components/academy/academy-sidebar-badge.tsx @@ -0,0 +1,50 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { GraduationCap } from '@/lib/icons'; +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { cn } from "@/lib/utils"; +import { ACADEMY_MODULES } from "@/lib/academy/tours"; +import { getCompletedModules } from "@/lib/academy/progress"; + +export function AcademySidebarBadge() { + const [percent, setPercent] = useState(0); + const pathname = usePathname(); + const isActive = pathname === "/academy"; + + useEffect(() => { + function update() { + const completed = getCompletedModules(); + setPercent(Math.round((completed.length / ACADEMY_MODULES.length) * 100)); + } + update(); + window.addEventListener("focus", update); + return () => window.removeEventListener("focus", update); + }, []); + + if (percent === 100) return null; + + return ( + + + Akademi + + %{percent} + + + ); +} diff --git a/src/components/academy/academy-tour-button.tsx b/src/components/academy/academy-tour-button.tsx new file mode 100644 index 0000000..da5f249 --- /dev/null +++ b/src/components/academy/academy-tour-button.tsx @@ -0,0 +1,98 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { PlayCircle } from '@/lib/icons'; +import { Button } from "@/components/ui/button"; +import type { AcademyModule } from "@/lib/academy/tours"; +import { markModuleComplete } from "@/lib/academy/progress"; + +interface AcademyTourButtonProps { + module: AcademyModule; + onComplete?: () => void; + variant?: "default" | "outline" | "ghost"; + size?: "default" | "sm"; +} + +export function AcademyTourButton({ + module, + onComplete, + variant = "outline", + size = "sm", +}: AcademyTourButtonProps) { + const router = useRouter(); + + async function startTour() { + // @ts-expect-error - CSS loaded dynamically + await import("driver.js/dist/driver.css"); + const { driver } = await import("driver.js"); + + router.push(module.url); + await new Promise((r) => setTimeout(r, 900)); + + let driverObj: ReturnType; + + driverObj = driver({ + showProgress: true, + progressText: "{{current}} / {{total}}", + nextBtnText: "İleri →", + prevBtnText: "← Geri", + doneBtnText: "Tamamla ✓", + smoothScroll: true, + onDestroyStarted: () => { + driverObj.destroy(); + }, + onDestroyed: () => { + // Close any open form + if (module.closeEvent) { + window.dispatchEvent(new CustomEvent(module.closeEvent)); + } + markModuleComplete(module.id); + onComplete?.(); + }, + onNextClick: (_el, _step, { state }) => { + const nextIndex = (state.activeIndex ?? 0) + 1; + if (nextIndex >= module.steps.length) { + driverObj.destroy(); + return; + } + + const nextStep = module.steps[nextIndex]; + + // Dispatch a window event before this step (e.g. open a form) + if (nextStep.triggerEvent) { + window.dispatchEvent(new CustomEvent(nextStep.triggerEvent)); + setTimeout(() => driverObj.moveNext(), nextStep.triggerDelay ?? 700); + return; + } + + // Click a DOM element before this step (e.g. navigate form wizard) + if (nextStep.clickBefore) { + const target = document.querySelector(nextStep.clickBefore); + target?.click(); + setTimeout(() => driverObj.moveNext(), nextStep.clickDelay ?? 350); + return; + } + + driverObj.moveNext(); + }, + steps: module.steps.map((s) => ({ + element: s.element, + popover: { + title: s.title, + description: s.description, + side: s.side ?? "bottom", + align: "start", + }, + })), + }); + + driverObj.drive(); + } + + return ( + + ); +} diff --git a/src/components/activities/activities-client.tsx b/src/components/activities/activities-client.tsx index 73b351b..b76bc7c 100644 --- a/src/components/activities/activities-client.tsx +++ b/src/components/activities/activities-client.tsx @@ -1,7 +1,8 @@ "use client"; -import { useState } from "react"; -import { MoreHorizontal, Plus, Pencil, Trash2, CheckCircle } from "lucide-react"; +import { useState, useEffect } from "react"; +import { useRouter } from "next/navigation"; +import { DotsThree, Plus, PencilSimple, Trash, CheckCircle, List, CalendarDots } from '@/lib/icons'; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; @@ -17,9 +18,13 @@ import { deleteActivityAction, } from "@/lib/appwrite/activity-actions"; import { ActivityFormSheet } from "./activity-form-sheet"; +import { ActivityCalendar } from "./activity-calendar"; +import { DeleteConfirmDialog } from "@/components/ui/delete-confirm-dialog"; import type { Activity, Customer, Property } from "@/lib/appwrite/schema"; import { ACTIVITY_TYPE_LABELS } from "@/lib/appwrite/schema"; +type ViewMode = "list" | "calendar"; + interface ActivitiesClientProps { initialActivities: Activity[]; customers: Customer[]; @@ -31,9 +36,12 @@ export function ActivitiesClient({ customers, properties, }: ActivitiesClientProps) { + const router = useRouter(); const [activities, setActivities] = useState(initialActivities); const [sheetOpen, setSheetOpen] = useState(false); const [editing, setEditing] = useState(null); + const [deleteTarget, setDeleteTarget] = useState(null); + const [viewMode, setViewMode] = useState("list"); function customerName(id?: string | null) { if (!id) return "—"; @@ -45,15 +53,19 @@ export function ActivitiesClient({ return properties.find((p) => p.$id === id)?.title ?? "—"; } - function openCreate() { - setEditing(null); - setSheetOpen(true); - } + useEffect(() => { + const open = () => { setEditing(null); setSheetOpen(true); }; + const close = () => setSheetOpen(false); + window.addEventListener("kovak:open-form-activities", open); + window.addEventListener("kovak:close-form-activities", close); + return () => { + window.removeEventListener("kovak:open-form-activities", open); + window.removeEventListener("kovak:close-form-activities", close); + }; + }, []); - function openEdit(a: Activity) { - setEditing(a); - setSheetOpen(true); - } + function openCreate() { setEditing(null); setSheetOpen(true); } + function openEdit(a: Activity) { setEditing(a); setSheetOpen(true); } async function handleComplete(a: Activity) { const result = await completeActivityAction(a.$id); @@ -67,11 +79,12 @@ export function ActivitiesClient({ } } - async function handleDelete(a: Activity) { - if (!confirm("Bu aktivite silinsin mi?")) return; - const result = await deleteActivityAction(a.$id); + async function doDelete() { + if (!deleteTarget) return; + const result = await deleteActivityAction(deleteTarget.$id); if (result.ok) { - setActivities((prev) => prev.filter((x) => x.$id !== a.$id)); + setActivities((prev) => prev.filter((x) => x.$id !== deleteTarget.$id)); + setDeleteTarget(null); toast.success("Aktivite silindi."); } else { toast.error(result.error ?? "Silinemedi."); @@ -80,15 +93,57 @@ export function ActivitiesClient({ return (
-
+

Aktiviteler

- +
+ {/* View toggle */} +
+ + +
+ +
-
+ {/* Calendar view */} + {viewMode === "calendar" && ( + + )} + + {/* List view */} + {viewMode === "list" && ( +
@@ -133,7 +188,7 @@ export function ActivitiesClient({ @@ -144,14 +199,14 @@ export function ActivitiesClient({ )} openEdit(a)}> - + Düzenle handleDelete(a)} + onClick={() => setDeleteTarget(a)} className="text-destructive focus:text-destructive" > - + Sil @@ -162,6 +217,7 @@ export function ActivitiesClient({
+ )} router.refresh()} + /> + { if (!v) setDeleteTarget(null); }} + title="Bu aktivite silinsin mi?" + description="Bu aktivite kalıcı olarak silinecek ve geri alınamaz." + onConfirm={doDelete} />
); diff --git a/src/components/activities/activity-calendar.tsx b/src/components/activities/activity-calendar.tsx new file mode 100644 index 0000000..5285532 --- /dev/null +++ b/src/components/activities/activity-calendar.tsx @@ -0,0 +1,320 @@ +"use client"; + +import { useState, useMemo } from "react"; +import { CaretLeft, CaretRight, CheckCircle, PencilSimple, Clock, User, Buildings } from '@/lib/icons'; +import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; +import type { Activity, Customer, Property } from "@/lib/appwrite/schema"; +import { ACTIVITY_TYPE_LABELS } from "@/lib/appwrite/schema"; + +const DAYS = ["Pzt", "Sal", "Çar", "Per", "Cum", "Cmt", "Paz"]; +const MONTHS = [ + "Ocak", "Şubat", "Mart", "Nisan", "Mayıs", "Haziran", + "Temmuz", "Ağustos", "Eylül", "Ekim", "Kasım", "Aralık", +]; + +const TYPE_COLORS: Record = { + gorusme: "bg-blue-500", + teklif: "bg-amber-500", + ziyaret: "bg-emerald-500", + arama: "bg-purple-500", + not: "bg-gray-400", +}; + +const TYPE_BADGE_COLORS: Record = { + gorusme: "bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-300", + teklif: "bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-300", + ziyaret: "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300", + arama: "bg-purple-100 text-purple-700 dark:bg-purple-900/40 dark:text-purple-300", + not: "bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-300", +}; + +function toDateKey(date: Date): string { + return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`; +} + +function activityDateKey(a: Activity): string | null { + if (!a.dueDate) return null; + const d = new Date(a.dueDate); + if (isNaN(d.getTime())) return null; + return toDateKey(d); +} + +interface Props { + activities: Activity[]; + customers: Customer[]; + properties: Property[]; + onEdit: (a: Activity) => void; + onComplete: (a: Activity) => void; +} + +export function ActivityCalendar({ activities, customers, properties, onEdit, onComplete }: Props) { + const today = new Date(); + const [year, setYear] = useState(today.getFullYear()); + const [month, setMonth] = useState(today.getMonth()); + const [selectedKey, setSelectedKey] = useState(toDateKey(today)); + + function prevMonth() { + if (month === 0) { setMonth(11); setYear(y => y - 1); } + else setMonth(m => m - 1); + } + function nextMonth() { + if (month === 11) { setMonth(0); setYear(y => y + 1); } + else setMonth(m => m + 1); + } + + // Index activities by date key + const byDate = useMemo(() => { + const map: Record = {}; + for (const a of activities) { + const key = activityDateKey(a); + if (!key) continue; + if (!map[key]) map[key] = []; + map[key].push(a); + } + return map; + }, [activities]); + + // Build calendar grid + const cells = useMemo(() => { + const firstDay = new Date(year, month, 1); + // Monday-based: 0=Mon … 6=Sun + let startOffset = firstDay.getDay() - 1; + if (startOffset < 0) startOffset = 6; + + const daysInMonth = new Date(year, month + 1, 0).getDate(); + const totalCells = Math.ceil((startOffset + daysInMonth) / 7) * 7; + + const result: Array<{ date: Date | null; key: string | null }> = []; + for (let i = 0; i < totalCells; i++) { + const dayNum = i - startOffset + 1; + if (dayNum < 1 || dayNum > daysInMonth) { + result.push({ date: null, key: null }); + } else { + const d = new Date(year, month, dayNum); + result.push({ date: d, key: toDateKey(d) }); + } + } + return result; + }, [year, month]); + + const selectedActivities = selectedKey ? (byDate[selectedKey] ?? []) : []; + const selectedDate = selectedKey ? new Date(selectedKey + "T12:00:00") : null; + + function customerName(id?: string | null) { + if (!id) return null; + return customers.find((c) => c.$id === id)?.name ?? null; + } + function propertyTitle(id?: string | null) { + if (!id) return null; + return properties.find((p) => p.$id === id)?.title ?? null; + } + + const todayKey = toDateKey(today); + + return ( +
+ {/* === Calendar Grid === */} +
+ {/* Month nav */} +
+ + + {MONTHS[month]} {year} + + +
+ + {/* Day headers */} +
+ {DAYS.map((d) => ( +
+ {d} +
+ ))} +
+ + {/* Day cells */} +
+ {cells.map((cell, i) => { + if (!cell.date || !cell.key) { + return
; + } + const cellActivities = byDate[cell.key] ?? []; + const isToday = cell.key === todayKey; + const isSelected = cell.key === selectedKey; + const visible = cellActivities.slice(0, 3); + const overflow = cellActivities.length - 3; + + return ( + + ); + })} +
+ + {/* Legend */} +
+ {Object.entries(ACTIVITY_TYPE_LABELS).map(([key, label]) => ( +
+ + {label} +
+ ))} +
+
+ + {/* === Day Panel === */} +
+
+ {/* Panel header */} +
+ {selectedDate ? ( +
+

+ {selectedDate.toLocaleDateString("tr-TR", { weekday: "long", day: "numeric", month: "long" })} +

+

+ {selectedActivities.length === 0 + ? "Bu gün için aktivite yok" + : `${selectedActivities.length} aktivite`} +

+
+ ) : ( +

Gün seçin

+ )} +
+ + {/* Activity list */} +
+ {selectedActivities.length === 0 ? ( +
+ + Aktivite bulunmuyor +
+ ) : ( +
+ {selectedActivities.map((a) => ( + + ))} +
+ )} +
+
+
+
+ ); +} + +interface CardProps { + activity: Activity; + customerName: string | null; + propertyTitle: string | null; + typeBadgeColor: string; + onEdit: (a: Activity) => void; + onComplete: (a: Activity) => void; +} + +function ActivityCard({ activity: a, customerName, propertyTitle, typeBadgeColor, onEdit, onComplete }: CardProps) { + return ( +
+
+ + {ACTIVITY_TYPE_LABELS[a.type] ?? a.type} + + {a.completedAt ? ( + Tamam + ) : ( + Açık + )} +
+ +

{a.title}

+ + {a.description && ( +

{a.description}

+ )} + +
+ {customerName && ( +
+ + {customerName} +
+ )} + {propertyTitle && ( +
+ + {propertyTitle} +
+ )} +
+ +
+ {!a.completedAt && ( + + )} + +
+
+ ); +} diff --git a/src/components/activities/activity-form-sheet.tsx b/src/components/activities/activity-form-sheet.tsx index 26b63ab..a6fb674 100644 --- a/src/components/activities/activity-form-sheet.tsx +++ b/src/components/activities/activity-form-sheet.tsx @@ -1,20 +1,12 @@ "use client"; import { useActionState, useEffect } from "react"; -import { Loader2 } from "lucide-react"; import { toast } from "sonner"; -import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; -import { - Sheet, - SheetContent, - SheetFooter, - SheetHeader, - SheetTitle, -} from "@/components/ui/sheet"; +import { ResponsiveSheet, FormWizard } from "@/components/ui/responsive-sheet"; import { createActivityAction, updateActivityAction } from "@/lib/appwrite/activity-actions"; import type { Activity, Customer, Property } from "@/lib/appwrite/schema"; @@ -30,18 +22,8 @@ interface ActivityFormSheetProps { onSuccess?: () => void; } -export function ActivityFormSheet({ - open, - onOpenChange, - activity, - customers, - properties, - onSuccess, -}: ActivityFormSheetProps) { - const action = activity - ? updateActivityAction.bind(null, activity.$id) - : createActivityAction; - +export function ActivityFormSheet({ open, onOpenChange, activity, customers, properties, onSuccess }: ActivityFormSheetProps) { + const action = activity ? updateActivityAction.bind(null, activity.$id) : createActivityAction; const [state, formAction, isPending] = useActionState(action, INITIAL); useEffect(() => { @@ -56,86 +38,82 @@ export function ActivityFormSheet({ const fe = state.fieldErrors ?? {}; - return ( - - - - {activity ? "Aktiviteyi Düzenle" : "Yeni Aktivite"} - - - -
- - + const steps = [ + { + label: "Aktivite", + content: ( + <> +
+
+ + +
+
+ + + {fe.title &&

{fe.title[0]}

} +
-
- - - {fe.title &&

{fe.title[0]}

} + +
- + + ), + }, + { + label: "Bağlantı", + content: ( + <>
- {customers.map((c) => ( ))}
-
- {properties.map((p) => ( ))}
+ + ), + }, + { + label: "Detay", + content: ( +
+ +