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
}