diff --git a/apps/admin/vite.config.ts b/apps/admin/vite.config.ts
index a6a6aa7..791fd63 100644
--- a/apps/admin/vite.config.ts
+++ b/apps/admin/vite.config.ts
@@ -2,7 +2,9 @@ import react from '@vitejs/plugin-react';
import { defineConfig } from 'vite';
// В разработке API проксируется — Mini App и API на одном origin (удобно и для туннеля https)
+// ADMIN_BASE=/old/ — в продакшене старая админка живёт по адресу admin.barevclub.com/old/
export default defineConfig({
+ base: process.env.ADMIN_BASE ?? '/',
plugins: [react()],
server: {
host: true,
diff --git a/apps/worker/src/index.ts b/apps/worker/src/index.ts
index dd96446..10902f3 100644
--- a/apps/worker/src/index.ts
+++ b/apps/worker/src/index.ts
@@ -102,11 +102,12 @@ async function runBroadcast(id: string) {
}
/** Новая админка, если уже на сервере; иначе — раздел переписки в старой */
-const BACKOFFICE_URL = process.env.BACKOFFICE_URL ?? '';
+// новая админка живёт на admin.* (старая — admin.*/old/), отдельный адрес не обязателен
+const BACKOFFICE_URL = process.env.BACKOFFICE_URL || ADMIN_URL;
const chatButton = (threadId: string | null, peer: string): InlineButton | null =>
BACKOFFICE_URL.startsWith('https://') && threadId
? { text: adminT('bot.admin.open'), url: `${BACKOFFICE_URL}/#/chats/${threadId}` }
- : adminButton(`/concierge?${peer.startsWith('c:') ? 'client' : 'provider'}=${peer.slice(2)}`);
+ : adminButton('/chats');
const who = (u: { firstName: string | null; lastName?: string | null; username: string | null }) =>
`${escapeHtml(`${u.firstName ?? ''} ${u.lastName ?? ''}`.trim())}${u.username ? ` (@${escapeHtml(u.username)})` : ''}`;
@@ -395,7 +396,7 @@ async function handle(event: DomainEvent) {
const pkg = lead.package ? adminT(`landing.quiz.package_${lead.package}`) : '—';
await toAdmins(
`🧳 ${adminT('bot.admin.newLead', { number: lead.number })}\n${escapeHtml(lead.name)} · ${escapeHtml(lead.contact)}\n${when} · ${pkg}${lead.adminNote ? `\n${escapeHtml(lead.adminNote)}` : ''}`,
- compactRows([[adminButton('/leads')]]),
+ compactRows([[adminButton('/moves')]]),
);
return;
}
@@ -405,7 +406,7 @@ async function handle(event: DomainEvent) {
const what = r.kind === 'SUBSCRIPTION' ? adminT('admin.requests.subscription', { plan: r.plan === 'ELITE' ? 'Barev Elite' : 'Barev+' }) : adminT(`services.${r.serviceCode}.name`);
await toAdmins(
`⭐ ${adminT('bot.admin.newRequest', { number: r.number })}\n${who(r.client.user)}\n${escapeHtml(what)}${r.comment ? `\n${escapeHtml(r.comment)}` : ''}`,
- compactRows([[{ text: adminT('bot.admin.reply'), callback_data: `ar:c:${r.clientId}` }, adminButton('/app-requests')]]),
+ compactRows([[{ text: adminT('bot.admin.reply'), callback_data: `ar:c:${r.clientId}` }, adminButton('/requests')]]),
);
return;
}
diff --git a/infra/prod/Dockerfile b/infra/prod/Dockerfile
index 3f919a6..4d45d7a 100644
--- a/infra/prod/Dockerfile
+++ b/infra/prod/Dockerfile
@@ -1,6 +1,7 @@
# Barev Club — образы для продакшена.
# app — Node: API + воркер + боты (один процесс) и лендинг Next.js (тот же образ)
# web — nginx: статика Mini App и админки + прокси /trpc и /files на API
+# admin.* — новая админка (apps/backoffice), admin.*/old/ — старая (apps/admin)
FROM node:22-alpine AS deps
RUN corepack enable
WORKDIR /repo
@@ -15,13 +16,15 @@ ARG CLIENT_BOT_USERNAME=barevclub_bot
ENV VITE_LANDING_URL=$LANDING_URL NEXT_PUBLIC_API_URL=$API_PUBLIC_URL CLIENT_BOT_USERNAME=$CLIENT_BOT_USERNAME NEXT_TELEMETRY_DISABLED=1
RUN pnpm --filter @barev/miniapp-client build \
&& pnpm --filter @barev/miniapp-pro build \
- && pnpm --filter @barev/admin build \
+ && ADMIN_BASE=/old/ pnpm --filter @barev/admin build \
+ && VITE_OLD_ADMIN_URL=/old/ pnpm --filter @barev/backoffice build \
&& pnpm --filter @barev/landing build
FROM nginx:alpine AS web
COPY --from=build /repo/apps/miniapp-client/dist /srv/client
COPY --from=build /repo/apps/miniapp-pro/dist /srv/pro
-COPY --from=build /repo/apps/admin/dist /srv/admin
+COPY --from=build /repo/apps/backoffice/dist /srv/admin
+COPY --from=build /repo/apps/admin/dist /srv/admin/old
COPY infra/prod/nginx.conf /etc/nginx/conf.d/default.conf
FROM node:22-alpine AS app
diff --git a/infra/prod/env.example b/infra/prod/env.example
index cfa7d51..8ba6dc4 100644
--- a/infra/prod/env.example
+++ b/infra/prod/env.example
@@ -16,8 +16,8 @@ LANDING_URL=https://barevclub.com
CLIENT_MINIAPP_URL=https://app.barevclub.com
PRO_MINIAPP_URL=https://pro.barevclub.com
ADMIN_URL=https://admin.barevclub.com
-# новая админка — когда выложим: тогда «Открыть» в уведомлениях о сообщениях ведёт в её чаты
-# BACKOFFICE_URL=https://team.barevclub.com
+# новая админка — на ADMIN_URL (старая — ADMIN_URL/old/); BACKOFFICE_URL нужен, только если она на другом адресе
+# BACKOFFICE_URL=
# серверный адрес API для лендинга (внутри docker-сети)
API_URL=http://barev-app:4000
API_PORT=4000
diff --git a/infra/prod/nginx.conf b/infra/prod/nginx.conf
index 22a1433..86a5f84 100644
--- a/infra/prod/nginx.conf
+++ b/infra/prod/nginx.conf
@@ -75,6 +75,16 @@ server {
add_header Cache-Control "no-cache";
}
+ # старая админка: admin.barevclub.com/old/ — своя index.html, тоже без кеша
+ location ^~ /old/ {
+ root /srv/$barev_site;
+ try_files $uri $uri/ /old/index.html;
+ location ~ \.html$ {
+ root /srv/$barev_site;
+ add_header Cache-Control "no-cache";
+ }
+ }
+
# статика с хешами в имени — кешируем надолго
location /assets/ {
root /srv/$barev_site;