add business_owner

This commit is contained in:
2026-07-15 19:14:52 +03:30
parent c02ff06e2c
commit 0d4158db4e
12 changed files with 267 additions and 108 deletions
+8 -15
View File
@@ -1,11 +1,11 @@
<template> <template>
<RouterLink <RouterLink
:to="{ name: 'app-business-detail', params: { id: business.id } }" :to="{ name: 'app-business-detail', params: { id: business.id } }"
class="group flex h-full flex-col rounded-2xl border border-slate-200 bg-white p-6 text-center shadow-sm transition hover:-translate-y-0.5 hover:border-brand-500 hover:shadow-md dark:border-border dark:bg-surface" class="group flex h-full flex-col rounded-xl border border-slate-200 bg-white p-4 text-center shadow-sm transition hover:-translate-y-0.5 hover:border-brand-500 hover:shadow-md dark:border-border dark:bg-surface"
:class="featured ? 'border-brand-500 ring-1 ring-brand-500/20' : ''" :class="featured ? 'border-brand-500 ring-1 ring-brand-500/20' : ''"
> >
<div <div
class="mx-auto mb-4 flex h-16 w-16 items-center justify-center overflow-hidden rounded-full bg-brand-500/10 text-brand-600 transition group-hover:bg-brand-500/15 dark:text-brand-500" class="mx-auto mb-3 flex h-12 w-12 items-center justify-center overflow-hidden rounded-full bg-brand-500/10 text-brand-600 transition group-hover:bg-brand-500/15 dark:text-brand-500"
> >
<img <img
v-if="cover" v-if="cover"
@@ -14,42 +14,35 @@
class="h-full w-full object-cover" class="h-full w-full object-cover"
loading="lazy" loading="lazy"
/> />
<AppIcon v-else name="store" size="lg" /> <AppIcon v-else name="store" size="md" />
</div> </div>
<h3 class="text-base font-bold text-slate-800 dark:text-text">{{ business.title }}</h3> <h3 class="text-sm font-bold text-slate-800 dark:text-text">{{ business.title }}</h3>
<BusinessRatingBadge <BusinessRatingBadge
class="mx-auto mt-2" class="mx-auto mt-1.5"
:average-rating="business.averageRating" :average-rating="business.averageRating"
:review-count="business.reviewCount" :review-count="business.reviewCount"
/> />
<p <p
v-if="business.description" v-if="business.description"
class="mt-2 line-clamp-3 min-h-[4.5rem] text-sm leading-7 text-slate-600 dark:text-muted" class="mt-1.5 line-clamp-2 text-xs leading-6 text-slate-600 dark:text-muted"
> >
{{ business.description }} {{ business.description }}
</p> </p>
<p v-else class="mt-2 min-h-[4.5rem] text-sm text-slate-400 dark:text-muted">—</p>
<p v-if="business.category" class="mt-3 text-xs font-medium text-brand-600 dark:text-brand-500"> <p v-if="business.category" class="mt-2 text-[11px] font-medium text-brand-600 dark:text-brand-500">
{{ business.category.title }} {{ business.category.title }}
</p> </p>
<p <p
v-if="business.address" v-if="business.address"
class="mt-2 flex items-center justify-center gap-1 text-xs text-slate-500 dark:text-muted" class="mt-1.5 flex items-center justify-center gap-1 text-[11px] text-slate-500 dark:text-muted"
> >
<AppIcon name="map-pin" size="sm" class="shrink-0" /> <AppIcon name="map-pin" size="sm" class="shrink-0" />
<span class="line-clamp-1">{{ business.address }}</span> <span class="line-clamp-1">{{ business.address }}</span>
</p> </p>
<span
class="mt-auto pt-5 text-sm font-semibold text-brand-600 transition group-hover:text-brand-700 dark:text-brand-500"
>
مشاهده و رزرو
</span>
</RouterLink> </RouterLink>
</template> </template>
+1 -1
View File
@@ -17,7 +17,7 @@
v-else v-else
:class=" :class="
variant === 'featured' variant === 'featured'
? 'grid grid-cols-1 gap-6 md:grid-cols-2 xl:grid-cols-3' ? 'grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4'
: 'grid grid-cols-1 gap-2' : 'grid grid-cols-1 gap-2'
" "
> >
+5 -5
View File
@@ -72,7 +72,7 @@
<div class="relative shadow-[0_20px_50px_-12px_rgba(0,0,0,0.18)]"> <div class="relative shadow-[0_20px_50px_-12px_rgba(0,0,0,0.18)]">
<BusinessImageGallery <BusinessImageGallery
:images="singleBusiness?.images" :images="landingBusiness?.images"
:title="heroTitle" :title="heroTitle"
variant="hero" variant="hero"
frame-extra-class="landing-hero-blob bg-slate-200" frame-extra-class="landing-hero-blob bg-slate-200"
@@ -103,13 +103,13 @@ const emit = defineEmits<{
}>(); }>();
const tenant = useTenantStore(); const tenant = useTenantStore();
const { singleBusiness } = storeToRefs(tenant); const { landingBusiness } = storeToRefs(tenant);
const heroTitle = computed(() => singleBusiness.value?.title ?? ''); const heroTitle = computed(() => landingBusiness.value?.title ?? '');
const heroSubtitle = computed(() => singleBusiness.value?.subtitle?.trim() ?? ''); const heroSubtitle = computed(() => landingBusiness.value?.subtitle?.trim() ?? '');
const heroDescription = computed(() => singleBusiness.value?.description?.trim() ?? ''); const heroDescription = computed(() => landingBusiness.value?.description?.trim() ?? '');
const highlights: { label: string; icon: AppIconName }[] = [ const highlights: { label: string; icon: AppIconName }[] = [
{ label: 'رضایت مشتریان', icon: 'shield' }, { label: 'رضایت مشتریان', icon: 'shield' },
+1 -1
View File
@@ -71,7 +71,7 @@ const gridClass = computed(() => {
function serviceLink(item: LandingServiceItem) { function serviceLink(item: LandingServiceItem) {
if (!props.bookingEnabled) return '#services'; if (!props.bookingEnabled) return '#services';
return { return {
name: 'app-business-detail', name: 'app-business-book',
params: { id: item.businessId }, params: { id: item.businessId },
hash: 'services', hash: 'services',
}; };
@@ -42,7 +42,7 @@ const props = withDefaults(
const router = useRouter(); const router = useRouter();
const auth = useAuthStore(); const auth = useAuthStore();
const tenant = useTenantStore(); const tenant = useTenantStore();
const { singleBusinessId } = storeToRefs(tenant); const { landingBusinessId } = storeToRefs(tenant);
function scrollToServices() { function scrollToServices() {
const el = document.getElementById('services'); const el = document.getElementById('services');
@@ -55,7 +55,7 @@ function goToBooking() {
return; return;
} }
const id = singleBusinessId.value; const id = landingBusinessId.value;
if (!id) { if (!id) {
scrollToServices(); scrollToServices();
return; return;
@@ -18,7 +18,7 @@
class="overflow-hidden rounded-2xl bg-slate-100 shadow-[0_8px_30px_rgba(15,23,42,0.12)] dark:bg-surface-2 dark:shadow-black/30" class="overflow-hidden rounded-2xl bg-slate-100 shadow-[0_8px_30px_rgba(15,23,42,0.12)] dark:bg-surface-2 dark:shadow-black/30"
> >
<BusinessImageGallery <BusinessImageGallery
:images="singleBusiness?.images" :images="landingBusiness?.images"
:title="businessTitle" :title="businessTitle"
variant="card" variant="card"
/> />
@@ -132,11 +132,11 @@ const emit = defineEmits<{
}>(); }>();
const tenant = useTenantStore(); const tenant = useTenantStore();
const { singleBusiness } = storeToRefs(tenant); const { landingBusiness } = storeToRefs(tenant);
const businessTitle = computed(() => singleBusiness.value?.title ?? ''); const businessTitle = computed(() => landingBusiness.value?.title ?? '');
const businessSubtitle = computed(() => singleBusiness.value?.subtitle?.trim() ?? ''); const businessSubtitle = computed(() => landingBusiness.value?.subtitle?.trim() ?? '');
const aboutText = computed(() => singleBusiness.value?.description?.trim() ?? ''); const aboutText = computed(() => landingBusiness.value?.description?.trim() ?? '');
const quickActions: { const quickActions: {
id: string; id: string;
+6 -6
View File
@@ -80,23 +80,23 @@ import AppIcon from '@/components/ui/AppIcon.vue';
import { useTenantStore } from '@/stores/tenant'; import { useTenantStore } from '@/stores/tenant';
const tenant = useTenantStore(); const tenant = useTenantStore();
const { brand, singleBusiness, isSingleBusiness } = storeToRefs(tenant); const { brand, landingBusiness } = storeToRefs(tenant);
const contactPhone = computed(() => { const contactPhone = computed(() => {
if (isSingleBusiness.value && singleBusiness.value?.contactPhone) { if (landingBusiness.value?.contactPhone) {
return singleBusiness.value.contactPhone; return landingBusiness.value.contactPhone;
} }
return '021-12345678'; return '021-12345678';
}); });
const contactMobile = computed(() => { const contactMobile = computed(() => {
if (isSingleBusiness.value) return null; if (landingBusiness.value) return null;
return '0912-123-4567'; return '0912-123-4567';
}); });
const address = computed(() => { const address = computed(() => {
if (isSingleBusiness.value && singleBusiness.value?.address) { if (landingBusiness.value?.address) {
return singleBusiness.value.address; return landingBusiness.value.address;
} }
return 'تهران، خیابان ولیعصر، پلاک ۱۲۳'; return 'تهران، خیابان ولیعصر، پلاک ۱۲۳';
}); });
+9 -1
View File
@@ -30,6 +30,11 @@ const router = createRouter({
name: 'app-business-detail', name: 'app-business-detail',
component: () => import('@/views/app/AppBusinessDetailView.vue'), component: () => import('@/views/app/AppBusinessDetailView.vue'),
}, },
{
path: '/app/businesses/:id/book',
name: 'app-business-book',
component: () => import('@/views/app/AppBusinessBookingView.vue'),
},
{ {
path: '/app/bookings', path: '/app/bookings',
name: 'app-bookings', name: 'app-bookings',
@@ -86,7 +91,10 @@ router.beforeEach(async (to) => {
return auth.isBusinessOwner ? { name: 'app-owner' } : { name: 'app-home' }; return auth.isBusinessOwner ? { name: 'app-owner' } : { name: 'app-home' };
} }
if (to.name === 'app-business-detail' && auth.isBusinessOwner) { if (
(to.name === 'app-business-detail' || to.name === 'app-business-book') &&
auth.isBusinessOwner
) {
const { ensureLoaded, isOwnBusiness } = useOwnedBusiness(); const { ensureLoaded, isOwnBusiness } = useOwnedBusiness();
await ensureLoaded(); await ensureLoaded();
if (isOwnBusiness(String(to.params.id))) { if (isOwnBusiness(String(to.params.id))) {
+24 -10
View File
@@ -10,22 +10,28 @@ export const useTenantStore = defineStore('tenant', () => {
const loading = ref(false); const loading = ref(false);
const isSingleBusiness = ref(false); const isSingleBusiness = ref(false);
const singleBusiness = ref<Business | null>(null); const singleBusiness = ref<Business | null>(null);
/** Business landing opened from multi-tenant discovery (not platform-wide single-business mode). */
const focusedBusiness = ref<Business | null>(null);
const landingBusiness = computed(() =>
isSingleBusiness.value ? singleBusiness.value : focusedBusiness.value,
);
const landingBusinessId = computed(() => landingBusiness.value?.id ?? null);
const brand = computed<SiteBrand>(() => { const brand = computed<SiteBrand>(() => {
const base = isSingleBusiness.value ? NIVASH_BRAND : MONTIME_BRAND; const biz = landingBusiness.value;
if (biz) {
if (isSingleBusiness.value && singleBusiness.value) {
return { return {
...base, ...NIVASH_BRAND,
name: singleBusiness.value.title, name: biz.title,
shortName: singleBusiness.value.title, shortName: biz.title,
copyrightName: singleBusiness.value.title, copyrightName: biz.title,
aboutText: aboutText: biz.description?.trim() || NIVASH_BRAND.aboutText,
singleBusiness.value.description?.trim() || base.aboutText,
}; };
} }
return base; return MONTIME_BRAND;
}); });
const singleBusinessId = computed(() => singleBusiness.value?.id ?? null); const singleBusinessId = computed(() => singleBusiness.value?.id ?? null);
@@ -35,6 +41,10 @@ export const useTenantStore = defineStore('tenant', () => {
singleBusiness.value = business; singleBusiness.value = business;
} }
function setFocusedBusiness(business: Business | null) {
focusedBusiness.value = business;
}
async function bootstrap() { async function bootstrap() {
if (loaded.value || loading.value) return; if (loaded.value || loading.value) return;
loading.value = true; loading.value = true;
@@ -68,8 +78,12 @@ export const useTenantStore = defineStore('tenant', () => {
isSingleBusiness, isSingleBusiness,
singleBusiness, singleBusiness,
singleBusinessId, singleBusinessId,
focusedBusiness,
landingBusiness,
landingBusinessId,
brand, brand,
bootstrap, bootstrap,
syncSingleBusiness, syncSingleBusiness,
setFocusedBusiness,
}; };
}); });
+2 -2
View File
@@ -20,7 +20,7 @@ export function loginRouteWithRedirect(redirectPath: string) {
export function businessBookingPath(businessId: string, hash?: string) { export function businessBookingPath(businessId: string, hash?: string) {
const normalizedHash = hash?.replace(/^#/, ''); const normalizedHash = hash?.replace(/^#/, '');
const base = `/app/businesses/${businessId}`; const base = `/app/businesses/${businessId}/book`;
return normalizedHash ? `${base}#${normalizedHash}` : base; return normalizedHash ? `${base}#${normalizedHash}` : base;
} }
@@ -35,7 +35,7 @@ export function bookingDestination(
const normalizedHash = hash?.replace(/^#/, ''); const normalizedHash = hash?.replace(/^#/, '');
return { return {
name: 'app-business-detail' as const, name: 'app-business-book' as const,
params: { id: businessId }, params: { id: businessId },
...(normalizedHash ? { hash: normalizedHash } : {}), ...(normalizedHash ? { hash: normalizedHash } : {}),
}; };
+117
View File
@@ -0,0 +1,117 @@
<template>
<ResponsiveAppLayout>
<DetailPageSkeleton v-if="loading" />
<p
v-else-if="error"
class="rounded-xl border border-red-500/30 bg-red-500/10 px-4 py-3 text-sm text-red-600 dark:text-red-400"
>
{{ error }}
</p>
<section v-else-if="business" class="min-w-0 max-w-full space-y-6">
<div
v-if="!business.isActive"
class="rounded-xl border border-amber-500/30 bg-amber-500/10 px-4 py-3 text-sm text-amber-700 dark:text-amber-400"
>
این کسب‌وکار فعلاً غیرفعال است.
</div>
<div
v-else-if="isOwnBusinessView"
class="rounded-2xl border border-border bg-surface-2 p-6 text-center"
>
<p class="text-sm text-muted">
شما صاحب این کسب‌وکار هستید و نمی‌توانید برای خودتان نوبت رزرو کنید.
</p>
<RouterLink
:to="{ name: 'app-owner' }"
class="mt-4 inline-flex items-center justify-center rounded-xl bg-brand-600 px-5 py-3 text-sm font-semibold text-white"
>
ورود به پنل مدیریت
</RouterLink>
</div>
<DesktopBookingFlow
v-else
:business="business"
:services="services"
:loading-services="loadingServices"
:services-error="servicesError"
:home-to="homeRoute"
@login="goToLoginForBooking"
@view-bookings="router.push({ name: 'app-bookings' })"
@go-home="router.push(homeRoute)"
/>
</section>
</ResponsiveAppLayout>
</template>
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue';
import { RouterLink, useRoute, useRouter } from 'vue-router';
import { fetchBusinessServices } from '@/api/business-services';
import { fetchBusiness } from '@/api/businesses';
import { ApiError } from '@/api/client';
import type { Business, BusinessService } from '@/api/types';
import DesktopBookingFlow from '@/components/booking/DesktopBookingFlow.vue';
import DetailPageSkeleton from '@/components/ui/DetailPageSkeleton.vue';
import ResponsiveAppLayout from '@/layouts/ResponsiveAppLayout.vue';
import { useOwnedBusiness } from '@/composables/useOwnedBusiness';
import { loginRouteWithRedirect } from '@/utils/auth-redirect';
const route = useRoute();
const router = useRouter();
const { ensureLoaded, isOwnBusiness } = useOwnedBusiness();
const business = ref<Business | null>(null);
const services = ref<BusinessService[]>([]);
const loading = ref(true);
const loadingServices = ref(false);
const error = ref('');
const servicesError = ref('');
const homeRoute = computed(() => ({
name: 'app-business-detail' as const,
params: { id: String(route.params.id) },
}));
const isOwnBusinessView = computed(() =>
business.value ? isOwnBusiness(business.value.id) : false,
);
async function loadBusiness() {
loading.value = true;
error.value = '';
try {
business.value = await fetchBusiness(String(route.params.id));
} catch (e) {
error.value = e instanceof ApiError ? e.message : 'بارگذاری کسب‌وکار ناموفق بود.';
} finally {
loading.value = false;
}
}
async function loadServices() {
if (!business.value) return;
loadingServices.value = true;
servicesError.value = '';
try {
services.value = await fetchBusinessServices(business.value.id, { activeOnly: true });
} catch (e) {
servicesError.value = e instanceof ApiError ? e.message : 'بارگذاری خدمات ناموفق بود.';
} finally {
loadingServices.value = false;
}
}
function goToLoginForBooking() {
void router.push(loginRouteWithRedirect(route.fullPath));
}
onMounted(async () => {
await ensureLoaded();
await loadBusiness();
if (!isOwnBusinessView.value) {
await loadServices();
}
});
</script>
+87 -60
View File
@@ -1,114 +1,141 @@
<template> <template>
<ResponsiveAppLayout> <div
<DetailPageSkeleton v-if="loading" /> v-if="loading"
class="flex min-h-dvh items-center justify-center bg-white dark:bg-[#0d1117]"
>
<BusinessCardSkeleton class="w-full max-w-5xl px-8" />
</div>
<div v-else-if="error" class="mx-auto max-w-lg px-6 py-16">
<p <p
v-else-if="error"
class="rounded-xl border border-red-500/30 bg-red-500/10 px-4 py-3 text-sm text-red-600 dark:text-red-400" class="rounded-xl border border-red-500/30 bg-red-500/10 px-4 py-3 text-sm text-red-600 dark:text-red-400"
> >
{{ error }} {{ error }}
</p> </p>
<section v-else-if="business" class="min-w-0 max-w-full space-y-6"> </div>
<div
v-if="!business.isActive"
class="rounded-xl border border-amber-500/30 bg-amber-500/10 px-4 py-3 text-sm text-amber-700 dark:text-amber-400"
>
این کسب‌وکار فعلاً غیرفعال است.
</div>
<div <DesktopLandingPage
v-else-if="isOwnBusinessView" v-else-if="business && isDesktop"
class="rounded-2xl border border-border bg-surface-2 p-6 text-center" :services="landingServices"
> :loading-services="loadingServices"
<p class="text-sm text-muted"> :services-error="servicesError"
شما صاحب این کسب‌وکار هستید و نمی‌توانید برای خودتان نوبت رزرو کنید. :booking-enabled="bookingEnabled"
</p> />
<RouterLink
:to="{ name: 'app-owner' }"
class="mt-4 inline-flex items-center justify-center rounded-xl bg-brand-600 px-5 py-3 text-sm font-semibold text-white"
>
ورود به پنل مدیریت
</RouterLink>
</div>
<DesktopBookingFlow <WebAppLayout v-else-if="business">
v-else <div
:business="business" v-if="!business.isActive"
:services="services" class="mb-4 rounded-xl border border-amber-500/30 bg-amber-500/10 px-4 py-3 text-sm text-amber-700 dark:text-amber-400"
:loading-services="loadingServices" >
:services-error="servicesError" این کسب‌وکار فعلاً غیرفعال است.
:home-to="homeRoute" </div>
@login="goToLoginForBooking" <MobileBusinessLanding
@view-bookings="router.push({ name: 'app-bookings' })" :services="landingServices"
@go-home="router.push(homeRoute)" :loading-services="loadingServices"
/> :services-error="servicesError"
</section> :booking-enabled="bookingEnabled"
</ResponsiveAppLayout> @book="goToBooking"
/>
</WebAppLayout>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, onMounted, ref } from 'vue'; import { useMediaQuery } from '@vueuse/core';
import { RouterLink, useRoute, useRouter } from 'vue-router'; import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';
import { fetchBusinessServices } from '@/api/business-services'; import { useRoute, useRouter } from 'vue-router';
import { fetchBusiness } from '@/api/businesses'; import { fetchBusiness } from '@/api/businesses';
import { ApiError } from '@/api/client'; import { ApiError } from '@/api/client';
import type { Business, BusinessService } from '@/api/types'; import type { Business } from '@/api/types';
import DesktopBookingFlow from '@/components/booking/DesktopBookingFlow.vue'; import MobileBusinessLanding from '@/components/landing/MobileBusinessLanding.vue';
import DetailPageSkeleton from '@/components/ui/DetailPageSkeleton.vue'; import DesktopLandingPage from '@/components/landing/DesktopLandingPage.vue';
import ResponsiveAppLayout from '@/layouts/ResponsiveAppLayout.vue'; import BusinessCardSkeleton from '@/components/ui/BusinessCardSkeleton.vue';
import WebAppLayout from '@/layouts/WebAppLayout.vue';
import { useOwnedBusiness } from '@/composables/useOwnedBusiness'; import { useOwnedBusiness } from '@/composables/useOwnedBusiness';
import { loginRouteWithRedirect } from '@/utils/auth-redirect'; import { useAuthStore } from '@/stores/auth';
import { useTenantStore } from '@/stores/tenant';
import type { LandingServiceItem } from '@/types/landing';
import { fetchLandingServices } from '@/utils/landing-services';
import { bookingDestination } from '@/utils/auth-redirect';
const isDesktop = useMediaQuery('(min-width: 1024px)');
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
const auth = useAuthStore();
const tenant = useTenantStore();
const { ensureLoaded, isOwnBusiness } = useOwnedBusiness(); const { ensureLoaded, isOwnBusiness } = useOwnedBusiness();
const business = ref<Business | null>(null); const business = ref<Business | null>(null);
const services = ref<BusinessService[]>([]); const landingServices = ref<LandingServiceItem[]>([]);
const loading = ref(true); const loading = ref(true);
const loadingServices = ref(false); const loadingServices = ref(false);
const error = ref(''); const error = ref('');
const servicesError = ref(''); const servicesError = ref('');
const homeRoute = computed(() => ({ name: 'app-home' })); const bookingEnabled = computed(() => {
if (!business.value) return false;
const isOwnBusinessView = computed(() => if (!business.value.isActive) return false;
business.value ? isOwnBusiness(business.value.id) : false, return !isOwnBusiness(business.value.id);
); });
async function loadBusiness() { async function loadBusiness() {
loading.value = true; loading.value = true;
error.value = ''; error.value = '';
try { try {
business.value = await fetchBusiness(String(route.params.id)); const id = String(route.params.id);
business.value = await fetchBusiness(id);
tenant.setFocusedBusiness(business.value);
} catch (e) { } catch (e) {
business.value = null;
tenant.setFocusedBusiness(null);
error.value = e instanceof ApiError ? e.message : 'بارگذاری کسب‌وکار ناموفق بود.'; error.value = e instanceof ApiError ? e.message : 'بارگذاری کسب‌وکار ناموفق بود.';
} finally { } finally {
loading.value = false; loading.value = false;
} }
} }
async function loadServices() { async function loadLandingServices() {
if (!business.value) return; if (!business.value) return;
loadingServices.value = true; loadingServices.value = true;
servicesError.value = ''; servicesError.value = '';
try { try {
services.value = await fetchBusinessServices(business.value.id, { activeOnly: true }); landingServices.value = await fetchLandingServices(business.value.id);
} catch (e) { } catch (e) {
servicesError.value = e instanceof ApiError ? e.message : 'بارگذاری خدمات ناموفق بود.'; landingServices.value = [];
servicesError.value =
e instanceof ApiError ? e.message : 'بارگذاری خدمات ناموفق بود.';
} finally { } finally {
loadingServices.value = false; loadingServices.value = false;
} }
} }
function goToLoginForBooking() { function goToBooking() {
void router.push(loginRouteWithRedirect(route.fullPath)); if (!business.value || !bookingEnabled.value) return;
void router.push(bookingDestination(business.value.id, auth.isLoggedIn));
} }
watch(
() => route.params.id,
async () => {
await loadBusiness();
if (business.value) {
await loadLandingServices();
}
},
);
onMounted(async () => { onMounted(async () => {
await ensureLoaded(); if (auth.isLoggedIn) {
await auth.loadUser();
await ensureLoaded();
}
await tenant.bootstrap();
await loadBusiness(); await loadBusiness();
if (!isOwnBusinessView.value) { if (business.value) {
await loadServices(); await loadLandingServices();
} }
}); });
onBeforeUnmount(() => {
tenant.setFocusedBusiness(null);
});
</script> </script>