From 31683564315ed77ba26e65d45cc769b9c1b5c535 Mon Sep 17 00:00:00 2001 From: whekin Date: Tue, 10 Mar 2026 17:18:53 +0400 Subject: [PATCH] feat(miniapp): show topic routing in admin settings --- apps/bot/src/miniapp-admin.test.ts | 17 +++++++- apps/bot/src/miniapp-admin.ts | 1 + apps/miniapp/src/App.tsx | 40 +++++++++++++++++++ apps/miniapp/src/i18n.ts | 18 +++++++++ apps/miniapp/src/miniapp-api.ts | 10 +++++ .../src/miniapp-admin-service.test.ts | 19 ++++++++- .../application/src/miniapp-admin-service.ts | 10 +++-- 7 files changed, 109 insertions(+), 6 deletions(-) diff --git a/apps/bot/src/miniapp-admin.test.ts b/apps/bot/src/miniapp-admin.test.ts index 7d004d0..e5860c4 100644 --- a/apps/bot/src/miniapp-admin.test.ts +++ b/apps/bot/src/miniapp-admin.test.ts @@ -41,7 +41,14 @@ function onboardingRepository(): HouseholdConfigurationRepository { }) satisfies HouseholdTopicBindingRecord, getHouseholdTopicBinding: async () => null, findHouseholdTopicByTelegramContext: async () => null, - listHouseholdTopicBindings: async () => [], + listHouseholdTopicBindings: async () => [ + { + householdId: household.householdId, + role: 'purchase', + telegramThreadId: '2', + topicName: 'Общие покупки' + } + ], listReminderTargets: async () => [], upsertHouseholdJoinToken: async (input) => ({ householdId: household.householdId, @@ -381,6 +388,14 @@ describe('createMiniAppSettingsHandler', () => { utilitiesReminderDay: 3, timezone: 'Asia/Tbilisi' }, + topics: [ + { + householdId: 'household-1', + role: 'purchase', + telegramThreadId: '2', + topicName: 'Общие покупки' + } + ], categories: [], members: [ { diff --git a/apps/bot/src/miniapp-admin.ts b/apps/bot/src/miniapp-admin.ts index 6cbba4f..be7f368 100644 --- a/apps/bot/src/miniapp-admin.ts +++ b/apps/bot/src/miniapp-admin.ts @@ -384,6 +384,7 @@ export function createMiniAppSettingsHandler(options: { ok: true, authorized: true, settings: serializeBillingSettings(result.settings), + topics: result.topics, categories: result.categories, members: result.members }, diff --git a/apps/miniapp/src/App.tsx b/apps/miniapp/src/App.tsx index 595c41c..0edd8b5 100644 --- a/apps/miniapp/src/App.tsx +++ b/apps/miniapp/src/App.tsx @@ -272,6 +272,19 @@ function App() { : copy().paymentLedgerRent } + function topicRoleLabel(role: 'purchase' | 'feedback' | 'reminders' | 'payments'): string { + switch (role) { + case 'purchase': + return copy().topicPurchase + case 'feedback': + return copy().topicFeedback + case 'reminders': + return copy().topicReminders + case 'payments': + return copy().topicPayments + } + } + async function loadDashboard(initData: string) { try { setDashboard(await fetchMiniAppDashboard(initData)) @@ -1393,6 +1406,33 @@ function App() { + +
+
+ {copy().topicBindingsTitle} + {String(adminSettings()?.topics.length ?? 0)}/4 +
+

{copy().topicBindingsBody}

+
+ {(['purchase', 'feedback', 'reminders', 'payments'] as const).map((role) => { + const binding = adminSettings()?.topics.find((topic) => topic.role === role) + + return ( +
+
+ {topicRoleLabel(role)} + {binding ? copy().topicBound : copy().topicUnbound} +
+

+ {binding + ? `${binding.topicName ?? `Topic #${binding.telegramThreadId}`} · #${binding.telegramThreadId}` + : copy().topicUnbound} +

+
+ ) + })} +
+
diff --git a/apps/miniapp/src/i18n.ts b/apps/miniapp/src/i18n.ts index ba0cebf..949be07 100644 --- a/apps/miniapp/src/i18n.ts +++ b/apps/miniapp/src/i18n.ts @@ -71,6 +71,15 @@ export const dictionary = { latestActivityEmpty: 'Recent utility and purchase entries will appear here.', householdSettingsTitle: 'Household settings', householdSettingsBody: 'Control household defaults and approve roommates who requested access.', + topicBindingsTitle: 'Topic bindings', + topicBindingsBody: + 'Review which Telegram topics are currently connected for purchases, feedback, reminders, and payments.', + topicPurchase: 'Purchases', + topicFeedback: 'Feedback', + topicReminders: 'Reminders', + topicPayments: 'Payments', + topicBound: 'Bound', + topicUnbound: 'Unbound', billingSettingsTitle: 'Billing settings', settlementCurrency: 'Settlement currency', billingCycleTitle: 'Current billing cycle', @@ -194,6 +203,15 @@ export const dictionary = { latestActivityEmpty: 'Здесь появятся последние коммунальные платежи и покупки.', householdSettingsTitle: 'Настройки household', householdSettingsBody: 'Здесь можно менять язык household и подтверждать новых соседей.', + topicBindingsTitle: 'Привязанные топики', + topicBindingsBody: + 'Проверь, какие Telegram-топики сейчас подключены для покупок, обратной связи, напоминаний и оплат.', + topicPurchase: 'Покупки', + topicFeedback: 'Обратная связь', + topicReminders: 'Напоминания', + topicPayments: 'Оплаты', + topicBound: 'Привязан', + topicUnbound: 'Не привязан', billingSettingsTitle: 'Настройки биллинга', settlementCurrency: 'Валюта расчёта', billingCycleTitle: 'Текущий billing cycle', diff --git a/apps/miniapp/src/miniapp-api.ts b/apps/miniapp/src/miniapp-api.ts index 89c63ce..3f78ab4 100644 --- a/apps/miniapp/src/miniapp-api.ts +++ b/apps/miniapp/src/miniapp-api.ts @@ -64,6 +64,12 @@ export interface MiniAppUtilityCategory { isActive: boolean } +export interface MiniAppTopicBinding { + role: 'purchase' | 'feedback' | 'reminders' | 'payments' + telegramThreadId: string + topicName: string | null +} + export interface MiniAppDashboard { period: string currency: 'USD' | 'GEL' @@ -104,6 +110,7 @@ export interface MiniAppDashboard { export interface MiniAppAdminSettingsPayload { settings: MiniAppBillingSettings + topics: readonly MiniAppTopicBinding[] categories: readonly MiniAppUtilityCategory[] members: readonly MiniAppMember[] } @@ -349,6 +356,7 @@ export async function fetchMiniAppAdminSettings( ok: boolean authorized?: boolean settings?: MiniAppBillingSettings + topics?: MiniAppTopicBinding[] categories?: MiniAppUtilityCategory[] members?: MiniAppMember[] error?: string @@ -358,6 +366,7 @@ export async function fetchMiniAppAdminSettings( !response.ok || !payload.authorized || !payload.settings || + !payload.topics || !payload.categories || !payload.members ) { @@ -366,6 +375,7 @@ export async function fetchMiniAppAdminSettings( return { settings: payload.settings, + topics: payload.topics, categories: payload.categories, members: payload.members } diff --git a/packages/application/src/miniapp-admin-service.test.ts b/packages/application/src/miniapp-admin-service.test.ts index 37c9f28..281c486 100644 --- a/packages/application/src/miniapp-admin-service.test.ts +++ b/packages/application/src/miniapp-admin-service.test.ts @@ -27,7 +27,14 @@ function repository(): HouseholdConfigurationRepository { }), getHouseholdTopicBinding: async () => null, findHouseholdTopicByTelegramContext: async () => null, - listHouseholdTopicBindings: async () => [], + listHouseholdTopicBindings: async () => [ + { + householdId: 'household-1', + role: 'purchase', + telegramThreadId: '2', + topicName: 'Общие покупки' + } + ], listReminderTargets: async () => [], upsertHouseholdJoinToken: async () => ({ householdId: 'household-1', @@ -167,7 +174,7 @@ function repository(): HouseholdConfigurationRepository { } describe('createMiniAppAdminService', () => { - test('returns billing settings, utility categories, and members for admins', async () => { + test('returns billing settings, topic bindings, utility categories, and members for admins', async () => { const service = createMiniAppAdminService(repository()) const result = await service.getSettings({ @@ -188,6 +195,14 @@ describe('createMiniAppAdminService', () => { utilitiesReminderDay: 3, timezone: 'Asia/Tbilisi' }, + topics: [ + { + householdId: 'household-1', + role: 'purchase', + telegramThreadId: '2', + topicName: 'Общие покупки' + } + ], categories: [], members: [] }) diff --git a/packages/application/src/miniapp-admin-service.ts b/packages/application/src/miniapp-admin-service.ts index 51a9198..bfeea97 100644 --- a/packages/application/src/miniapp-admin-service.ts +++ b/packages/application/src/miniapp-admin-service.ts @@ -3,6 +3,7 @@ import type { HouseholdConfigurationRepository, HouseholdMemberRecord, HouseholdPendingMemberRecord, + HouseholdTopicBindingRecord, HouseholdUtilityCategoryRecord } from '@household/ports' import { Money, type CurrencyCode } from '@household/domain' @@ -27,6 +28,7 @@ export interface MiniAppAdminService { settings: HouseholdBillingSettingsRecord categories: readonly HouseholdUtilityCategoryRecord[] members: readonly HouseholdMemberRecord[] + topics: readonly HouseholdTopicBindingRecord[] } | { status: 'rejected' @@ -138,17 +140,19 @@ export function createMiniAppAdminService( } } - const [settings, categories, members] = await Promise.all([ + const [settings, categories, members, topics] = await Promise.all([ repository.getHouseholdBillingSettings(input.householdId), repository.listHouseholdUtilityCategories(input.householdId), - repository.listHouseholdMembers(input.householdId) + repository.listHouseholdMembers(input.householdId), + repository.listHouseholdTopicBindings(input.householdId) ]) return { status: 'ok', settings, categories, - members + members, + topics } },