From befd53dc5baab28d189485e5afc52fda2c476ef0 Mon Sep 17 00:00:00 2001 From: Mahdi Date: Thu, 16 Jul 2026 16:50:11 +0330 Subject: [PATCH] Performing logins, mobile cards, and other tasks --- src/api/types.ts | 2 + src/components/booking/BookingReviewHost.vue | 16 ++- src/components/business/BusinessCard.vue | 42 ++++---- src/components/business/CategoryFilter.vue | 40 +++++++- src/components/home/HomeBusinessCard.vue | 68 +++++++------ src/components/home/HomeBusinessList.vue | 40 +++++--- src/components/home/HomeDiscoveryContent.vue | 12 ++- src/components/home/HomeHeroSection.vue | 10 +- .../owner/BusinessHighlightPicker.vue | 73 ++++++++++++++ src/components/ui/AppIcon.vue | 2 + src/components/ui/AppSelect.vue | 2 + src/composables/useAppNav.ts | 59 +++++------ src/composables/useOwnedBusiness.ts | 27 ++++- src/constants/business-highlights.ts | 66 +++++++++++++ src/layouts/MobileLoginLayout.vue | 98 +++++++++++++++++++ src/layouts/ResponsiveAuthLayout.vue | 9 +- src/types/icons.ts | 3 +- src/views/app/AppBookingsView.vue | 2 +- src/views/app/AppHomeView.vue | 2 +- src/views/app/AppOwnerView.vue | 9 +- src/views/app/AppRegisterBusinessView.vue | 31 +++++- src/views/auth/LoginView.vue | 6 +- 22 files changed, 499 insertions(+), 120 deletions(-) create mode 100644 src/components/owner/BusinessHighlightPicker.vue create mode 100644 src/constants/business-highlights.ts create mode 100644 src/layouts/MobileLoginLayout.vue diff --git a/src/api/types.ts b/src/api/types.ts index bdfe92a..bff7dde 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -45,6 +45,7 @@ export type Business = { promoDiscountMode?: 'first_booking' | 'always'; logoLightUrl?: string | null; logoDarkUrl?: string | null; + heroHighlightKeys?: string[] | null; averageRating?: number | null; reviewCount?: number; createdAt?: string; @@ -69,6 +70,7 @@ export type UpdateBusinessPayload = { promoDiscountPercent?: number | null; promoDiscountCode?: string | null; promoDiscountMode?: 'first_booking' | 'always'; + heroHighlightKeys?: string[] | null; }; export type ServiceTemplate = { diff --git a/src/components/booking/BookingReviewHost.vue b/src/components/booking/BookingReviewHost.vue index b018a31..8591046 100644 --- a/src/components/booking/BookingReviewHost.vue +++ b/src/components/booking/BookingReviewHost.vue @@ -126,7 +126,12 @@ const tipSubmitting = ref(false); const tipAmountToman = computed(() => parsePriceInput(tipAmountInput.value)); async function loadPending() { - if (!auth.isLoggedIn || auth.isBusinessOwner) return; + if (!auth.isLoggedIn) return; + if (auth.token && !auth.user) { + await auth.loadUser(); + } + // Business owners get 403 from this customer-only endpoint. + if (auth.isBusinessOwner || !auth.user) return; try { pending.value = await fetchPendingReviews(); if (!activeReview.value && pending.value.length) { @@ -241,9 +246,14 @@ async function submitTip() { } watch( - () => auth.isLoggedIn, - (loggedIn) => { + () => [auth.isLoggedIn, auth.user?.role] as const, + ([loggedIn]) => { if (loggedIn) void loadPending(); + else { + pending.value = []; + activeReview.value = null; + reviewOpen.value = false; + } }, ); diff --git a/src/components/business/BusinessCard.vue b/src/components/business/BusinessCard.vue index e3cc1a4..0ec55a9 100644 --- a/src/components/business/BusinessCard.vue +++ b/src/components/business/BusinessCard.vue @@ -4,33 +4,33 @@ class="flex items-center gap-3 rounded-xl border border-border bg-surface-2 p-2.5 transition hover:border-brand-500/40 hover:shadow-sm" >
-
-
-

{{ business.title }}

+
+
+

{{ business.title }}

- + {{ business.category.title }} + +
+

+ {{ summary }} +

+

- {{ business.category.title }} - + + {{ business.address }} +

-

- {{ business.description }} -

-

- - {{ business.address }} -

-
(); const cover = computed(() => businessCoverUrl(props.business.images)); + +const summary = computed(() => props.business.subtitle?.trim() || ''); diff --git a/src/components/business/CategoryFilter.vue b/src/components/business/CategoryFilter.vue index 47ba45e..8ba8dba 100644 --- a/src/components/business/CategoryFilter.vue +++ b/src/components/business/CategoryFilter.vue @@ -1,5 +1,32 @@ diff --git a/src/components/home/HomeDiscoveryContent.vue b/src/components/home/HomeDiscoveryContent.vue index aa6b3ab..4468fb9 100644 --- a/src/components/home/HomeDiscoveryContent.vue +++ b/src/components/home/HomeDiscoveryContent.vue @@ -117,10 +117,20 @@ watch(selectedCategoryId, () => { void loadBusinesses(); }); +watch( + () => [auth.isLoggedIn, auth.user?.id, auth.user?.role] as const, + async () => { + if (auth.isLoggedIn) { + await auth.loadUser(); + await ensureLoaded(true); + } + }, +); + onMounted(async () => { if (auth.isLoggedIn) { await auth.loadUser(); - await ensureLoaded(); + await ensureLoaded(true); } await loadCategories(); await loadBusinesses(); diff --git a/src/components/home/HomeHeroSection.vue b/src/components/home/HomeHeroSection.vue index 77dd1ed..c280bff 100644 --- a/src/components/home/HomeHeroSection.vue +++ b/src/components/home/HomeHeroSection.vue @@ -88,7 +88,7 @@ import { storeToRefs } from 'pinia'; import BusinessImageGallery from '@/components/business/BusinessImageGallery.vue'; import AppIcon from '@/components/ui/AppIcon.vue'; import { useTenantStore } from '@/stores/tenant'; -import type { AppIconName } from '@/types/icons'; +import { resolveBusinessHighlights } from '@/constants/business-highlights'; const props = withDefaults( defineProps<{ @@ -111,9 +111,7 @@ const heroSubtitle = computed(() => landingBusiness.value?.subtitle?.trim() ?? ' const heroDescription = computed(() => landingBusiness.value?.description?.trim() ?? ''); -const highlights: { label: string; icon: AppIconName }[] = [ - { label: 'رضایت مشتریان', icon: 'shield' }, - { label: 'تجهیزات مدرن', icon: 'settings' }, - { label: 'مواد شوینده با کیفیت', icon: 'droplet' }, -]; +const highlights = computed(() => + resolveBusinessHighlights(landingBusiness.value?.heroHighlightKeys), +); diff --git a/src/components/owner/BusinessHighlightPicker.vue b/src/components/owner/BusinessHighlightPicker.vue new file mode 100644 index 0000000..9dc27be --- /dev/null +++ b/src/components/owner/BusinessHighlightPicker.vue @@ -0,0 +1,73 @@ + + + diff --git a/src/components/ui/AppIcon.vue b/src/components/ui/AppIcon.vue index a8c40ba..a52ca66 100644 --- a/src/components/ui/AppIcon.vue +++ b/src/components/ui/AppIcon.vue @@ -21,6 +21,7 @@ import { computed } from 'vue'; import { Gift, Info, + LogOut, Armchair, ArrowRight, BadgePercent, @@ -111,6 +112,7 @@ const iconMap: Record = { zap: Zap, gift: Gift, info: Info, + 'log-out': LogOut, }; const props = withDefaults( diff --git a/src/components/ui/AppSelect.vue b/src/components/ui/AppSelect.vue index 968e088..5a3b674 100644 --- a/src/components/ui/AppSelect.vue +++ b/src/components/ui/AppSelect.vue @@ -6,6 +6,7 @@