diff --git a/app/components/AppFooter.vue b/app/components/AppFooter.vue index fd52e68..8b064d0 100644 --- a/app/components/AppFooter.vue +++ b/app/components/AppFooter.vue @@ -7,9 +7,9 @@ const props = withDefaults(defineProps<{ const { toLocalized } = useLocalizedNavigation() const footerLinks = [ - { label: 'Главная', to: '/' }, - { label: 'Заказы', to: '/clientarea/orders' }, - { label: 'Кабинет', to: '/clientarea/profile' }, + { label: 'Home', to: '/' }, + { label: 'Orders', to: '/clientarea/orders' }, + { label: 'Profile', to: '/clientarea/profile' }, ] const socialLinks = [ @@ -37,9 +37,9 @@ const currentYear = new Date().getFullYear() : 'border-white/20 bg-[rgba(11,24,42,0.26)] [backdrop-filter:blur(6px)]'" >
-

Готовы к следующей поставке?

+

Ready for your next shipment?

- Получайте предложения в реальном времени, сравнивайте условия и запускайте закупку прямо в Optovia. + Get live offers, compare terms, and launch procurement directly in Optovia.

@@ -75,7 +75,7 @@ const currentYear = new Date().getFullYear()
-

© {{ currentYear }} Optovia. Все права защищены.

+

© {{ currentYear }} Optovia. All rights reserved.

diff --git a/app/components/AppHeader.vue b/app/components/AppHeader.vue index 1e47093..b7206c0 100644 --- a/app/components/AppHeader.vue +++ b/app/components/AppHeader.vue @@ -18,16 +18,22 @@ const emit = defineEmits<{ }>() const route = useRoute() +const { t } = useI18n() const auth = useAuth() const { basePath, isBasePathActive, navigateToLocalized, toLocalized } = useLocalizedNavigation() const { draft: calcDraft, hydrateFromQuery, buildQuery: buildCalcQuery } = useCalcSearchDraft() const isLandingPage = computed(() => basePath.value === '/') -const isAuthPage = computed(() => basePath.value === '/auth' || route.path === '/sign-in' || basePath.value === '/callback') +const isAuthPage = computed(() => + basePath.value === '/auth' + || basePath.value === '/sign-in' + || basePath.value === '/sign-out' + || basePath.value === '/callback' +) const isCalcPage = computed(() => basePath.value.startsWith('/catalog')) const isManagerStagePage = computed(() => basePath.value.startsWith('/manager')) const isDarkHeaderScene = computed(() => isLandingPage.value || isManagerStagePage.value) const isAuthenticated = computed(() => props.isAuthenticated || auth.isAuthenticated.value) -const profileLabel = computed(() => props.profileLabel || (auth.user.value?.id ?? 'Профиль')) +const profileLabel = computed(() => props.profileLabel || (auth.user.value?.id ?? t('ui.profile'))) const showLogistics = computed(() => props.showLogistics || Boolean((auth.user.value as any)?.isAdmin)) const landingSearchScrollY = ref(0) const landingSearchTopStart = ref(450) @@ -71,9 +77,16 @@ const showAdminDock = computed(() => Boolean(showLogistics.value) && !isAuthPage // Fullscreen menu const isMenuOpen = ref(false) const menuLinks = computed(() => { - return [ - { label: 'Мои заказы', to: '/clientarea/orders', icon: 'orders' }, + const links: Array<{ label: string; to: string; icon: string }> = [ + { label: 'Optovia', to: '/', icon: 'map' }, + { label: t('ui.calculate'), to: '/catalog', icon: 'map' }, ] + + if (isAuthenticated.value) { + links.push({ label: t('ui.profile'), to: '/clientarea/profile', icon: 'referral' }) + } + + return links }) watch(() => route.fullPath, () => { diff --git a/app/composables/useAuth.ts b/app/composables/useAuth.ts index c31fa0e..163929d 100644 --- a/app/composables/useAuth.ts +++ b/app/composables/useAuth.ts @@ -48,12 +48,20 @@ export const useAuth = () => { } const signIn = async (_redirectUri?: string) => { + if (import.meta.client) { + window.location.assign('/sign-in') + return + } await navigateTo('/sign-in', { external: true }) } const login = signIn const signOut = async (_logoutRedirectUri?: string) => { + if (import.meta.client) { + window.location.assign('/sign-out') + return + } await navigateTo('/sign-out', { external: true }) } diff --git a/app/middleware/auth-oidc.ts b/app/middleware/auth-oidc.ts index 84f11c1..d7e2d23 100644 --- a/app/middleware/auth-oidc.ts +++ b/app/middleware/auth-oidc.ts @@ -1,11 +1,13 @@ export default defineNuxtRouteMiddleware(async (to) => { + const basePath = stripLocalePrefix(to.path, ['ru', 'en']) + // Skip auth routes handled by @logto/nuxt - if (to.path === '/sign-in' || to.path === '/sign-out' || to.path === '/callback') { + if (basePath === '/sign-in' || basePath === '/sign-out' || basePath === '/callback') { return } // Skip public auth paths - if (to.path.startsWith('/auth/')) { + if (basePath.startsWith('/auth/')) { return } diff --git a/app/pages/index.vue b/app/pages/index.vue index 630e8bb..18d1f76 100644 --- a/app/pages/index.vue +++ b/app/pages/index.vue @@ -141,7 +141,7 @@ const testimonials = computed(() => isEn.value const leadTestimonial = computed(() => testimonials.value[0] ?? null) const sideTestimonials = computed(() => testimonials.value.slice(1)) -const ctaTitle = computed(() => isEn.value ? 'Scale your sourcing flow with Optovia' : 'Масштабируйте закупочный поток вместе с Optovia') +const ctaTitle = computed(() => isEn.value ? 'Scale your procurement flow with Optovia' : 'Масштабируйте поток закупок вместе с Optovia') const ctaText = computed(() => isEn.value ? 'Move from fragmented tools to one coherent workflow.' : 'Перейдите от разрозненных инструментов к единому рабочему контуру.') diff --git a/server/middleware/00-logto.ts b/server/middleware/00-logto.ts index 7a8c533..7972337 100644 --- a/server/middleware/00-logto.ts +++ b/server/middleware/00-logto.ts @@ -23,6 +23,28 @@ const RESOURCES = [ 'https://billing.optovia.ru' ] +const LOCALE_CODES = ['ru', 'en'] as const + +function resolveLocalizedPath(pathname: string) { + const trimmed = pathname === '/' ? '/' : pathname.replace(/\/+$/, '') || '/' + const segments = trimmed.split('/').filter(Boolean) + const firstSegment = segments[0] + + if (firstSegment && LOCALE_CODES.includes(firstSegment as (typeof LOCALE_CODES)[number])) { + const normalized = `/${segments.slice(1).join('/')}` || '/' + const normalizedPath = normalized === '//' ? '/' : normalized + return { + localePrefix: `/${firstSegment}`, + normalizedPath: normalizedPath || '/', + } + } + + return { + localePrefix: '', + normalizedPath: trimmed, + } +} + const createSessionWrapper = (event: H3Event) => { const storage = useStorage('logto') let currentSessionId = '' @@ -60,16 +82,19 @@ const createSessionWrapper = (event: H3Event) => { } export default defineEventHandler(async (event) => { - const endpoint = process.env.NUXT_LOGTO_ENDPOINT || 'https://auth.optovia.ru' - const appId = process.env.NUXT_LOGTO_APP_ID || '' - const appSecret = process.env.NUXT_LOGTO_APP_SECRET || '' + const endpoint = process.env.NUXT_LOGTO_ENDPOINT || process.env.LOGTO_ENDPOINT || 'https://auth.optovia.ru' + const appId = process.env.NUXT_LOGTO_APP_ID || process.env.LOGTO_APP_ID || process.env.LOGTO_CLIENT_ID || '' + const appSecret = process.env.NUXT_LOGTO_APP_SECRET || process.env.LOGTO_APP_SECRET || process.env.LOGTO_CLIENT_SECRET || '' + const url = getRequestURL(event) + const { localePrefix, normalizedPath } = resolveLocalizedPath(url.pathname) if (!appId || !appSecret) { + if (normalizedPath === '/sign-in' || normalizedPath === '/sign-out' || normalizedPath === '/callback') { + await sendRedirect(event, localePrefix || '/', 302) + } return } - const url = getRequestURL(event) - if (getCookie(event, LEGACY_COOKIE_NAME)) { setCookie(event, LEGACY_COOKIE_NAME, '', { path: '/', maxAge: 0 }) } @@ -113,21 +138,23 @@ export default defineEventHandler(async (event) => { } ) - if (url.pathname === '/sign-in') { + if (normalizedPath === '/sign-in') { + const callbackPath = `${localePrefix}/callback` await logto.signIn({ - redirectUri: new URL('/callback', url).href + redirectUri: new URL(callbackPath || '/callback', url).href }) return } - if (url.pathname === '/sign-out') { - await logto.signOut(new URL('/', url).href) + if (normalizedPath === '/sign-out') { + const homePath = localePrefix || '/' + await logto.signOut(new URL(homePath, url).href) return } - if (url.pathname === '/callback') { + if (normalizedPath === '/callback') { await logto.handleSignInCallback(url.href) - await sendRedirect(event, '/', 302) + await sendRedirect(event, localePrefix || '/', 302) return }