From 4c19ee798d71294281153688edd4cec9c486774e Mon Sep 17 00:00:00 2001 From: whekin Date: Thu, 12 Mar 2026 11:30:11 +0400 Subject: [PATCH] feat(miniapp): add household general settings --- apps/bot/src/miniapp-admin.test.ts | 2 + apps/bot/src/miniapp-admin.ts | 14 +++ apps/bot/src/miniapp-auth.ts | 1 + apps/miniapp/src/App.tsx | 46 +++++-- apps/miniapp/src/demo/miniapp-demo.ts | 2 + apps/miniapp/src/i18n.ts | 9 ++ apps/miniapp/src/miniapp-api.ts | 17 ++- apps/miniapp/src/screens/house-screen.tsx | 112 ++++++++++++++---- .../src/household-config-repository.ts | 29 +++++ .../src/household-onboarding-service.test.ts | 1 + .../src/household-onboarding-service.ts | 26 +++- .../src/miniapp-admin-service.test.ts | 11 +- .../application/src/miniapp-admin-service.ts | 37 +++++- packages/ports/src/household-config.ts | 4 + 14 files changed, 268 insertions(+), 43 deletions(-) diff --git a/apps/bot/src/miniapp-admin.test.ts b/apps/bot/src/miniapp-admin.test.ts index c405d5e..5dd36d9 100644 --- a/apps/bot/src/miniapp-admin.test.ts +++ b/apps/bot/src/miniapp-admin.test.ts @@ -469,6 +469,7 @@ describe('createMiniAppSettingsHandler', () => { expect(await response.json()).toEqual({ ok: true, authorized: true, + householdName: 'Kojori House', settings: { householdId: 'household-1', settlementCurrency: 'GEL', @@ -570,6 +571,7 @@ describe('createMiniAppUpdateSettingsHandler', () => { expect(await response.json()).toEqual({ ok: true, authorized: true, + householdName: 'Kojori House', settings: { householdId: 'household-1', settlementCurrency: 'GEL', diff --git a/apps/bot/src/miniapp-admin.ts b/apps/bot/src/miniapp-admin.ts index 074cae6..edf104e 100644 --- a/apps/bot/src/miniapp-admin.ts +++ b/apps/bot/src/miniapp-admin.ts @@ -49,6 +49,7 @@ async function readApprovalPayload(request: Request): Promise<{ async function readSettingsUpdatePayload(request: Request): Promise<{ initData: string + householdName?: string settlementCurrency?: string paymentBalanceAdjustmentPolicy?: string rentAmountMajor?: string @@ -69,6 +70,7 @@ async function readSettingsUpdatePayload(request: Request): Promise<{ const text = await clonedRequest.text() let parsed: { + householdName?: string settlementCurrency?: string paymentBalanceAdjustmentPolicy?: string rentAmountMajor?: string @@ -99,6 +101,11 @@ async function readSettingsUpdatePayload(request: Request): Promise<{ return { initData: payload.initData, + ...(typeof parsed.householdName === 'string' + ? { + householdName: parsed.householdName + } + : {}), ...(typeof parsed.rentAmountMajor === 'string' ? { rentAmountMajor: parsed.rentAmountMajor @@ -545,6 +552,7 @@ export function createMiniAppSettingsHandler(options: { { ok: true, authorized: true, + householdName: result.householdName, settings: serializeBillingSettings(result.settings), assistantConfig: serializeAssistantConfig(result.assistantConfig), topics: result.topics, @@ -620,6 +628,11 @@ export function createMiniAppUpdateSettingsHandler(options: { const result = await options.miniAppAdminService.updateSettings({ householdId: session.member.householdId, actorIsAdmin: session.member.isAdmin, + ...(payload.householdName !== undefined + ? { + householdName: payload.householdName + } + : {}), ...(payload.settlementCurrency ? { settlementCurrency: payload.settlementCurrency @@ -675,6 +688,7 @@ export function createMiniAppUpdateSettingsHandler(options: { { ok: true, authorized: true, + householdName: result.householdName, settings: serializeBillingSettings(result.settings), assistantConfig: serializeAssistantConfig(result.assistantConfig) }, diff --git a/apps/bot/src/miniapp-auth.ts b/apps/bot/src/miniapp-auth.ts index 3f13c30..091a1b9 100644 --- a/apps/bot/src/miniapp-auth.ts +++ b/apps/bot/src/miniapp-auth.ts @@ -100,6 +100,7 @@ export interface MiniAppSessionResult { member?: { id: string householdId: string + householdName: string displayName: string status: 'active' | 'away' | 'left' isAdmin: boolean diff --git a/apps/miniapp/src/App.tsx b/apps/miniapp/src/App.tsx index e2a67eb..78368d4 100644 --- a/apps/miniapp/src/App.tsx +++ b/apps/miniapp/src/App.tsx @@ -90,6 +90,7 @@ type SessionState = mode: 'live' | 'demo' member: { id: string + householdName: string displayName: string status: 'active' | 'away' | 'left' isAdmin: boolean @@ -348,6 +349,7 @@ function App() { const [testingRolePreview, setTestingRolePreview] = createSignal(null) const [addingPayment, setAddingPayment] = createSignal(false) const [billingForm, setBillingForm] = createSignal({ + householdName: '', settlementCurrency: 'GEL' as 'USD' | 'GEL', paymentBalanceAdjustmentPolicy: 'utilities' as 'utilities' | 'rent' | 'separate', rentAmountMajor: '', @@ -762,6 +764,7 @@ function App() { '' })) setBillingForm({ + householdName: payload.householdName, settlementCurrency: payload.settings.settlementCurrency, paymentBalanceAdjustmentPolicy: payload.settings.paymentBalanceAdjustmentPolicy, rentAmountMajor: payload.settings.rentAmountMinor @@ -883,6 +886,7 @@ function App() { ) ) setBillingForm({ + householdName: demoAdminSettings.householdName, settlementCurrency: demoAdminSettings.settings.settlementCurrency, paymentBalanceAdjustmentPolicy: demoAdminSettings.settings.paymentBalanceAdjustmentPolicy, rentAmountMajor: demoAdminSettings.settings.rentAmountMinor @@ -1200,7 +1204,7 @@ function App() { setSavingBillingSettings(true) try { - const { settings, assistantConfig } = await updateMiniAppBillingSettings( + const { householdName, settings, assistantConfig } = await updateMiniAppBillingSettings( initData, billingForm() ) @@ -1208,11 +1212,27 @@ function App() { current ? { ...current, + householdName, settings, assistantConfig } : current ) + setBillingForm((current) => ({ + ...current, + householdName + })) + setSession((current) => + current.status === 'ready' + ? { + ...current, + member: { + ...current.member, + householdName + } + } + : current + ) setCycleForm((current) => ({ ...current, rentCurrency: settings.rentCurrency, @@ -1992,6 +2012,8 @@ function App() { locale={locale()} readyIsAdmin={effectiveIsAdmin()} householdDefaultLocale={readySession()?.member.householdDefaultLocale ?? 'en'} + householdName={readySession()?.member.householdName ?? billingForm().householdName} + profileDisplayName={readySession()?.member.displayName ?? displayNameDraft()} dashboard={dashboard()} adminSettings={adminSettings()} cycleState={cycleState()} @@ -2030,6 +2052,7 @@ function App() { resolvedMemberAbsencePolicy(memberId, status) } onChangeHouseholdLocale={handleHouseholdLocaleChange} + onOpenProfileEditor={() => setProfileEditorOpen(true)} onOpenCycleModal={() => setCycleRentOpen(true)} onCloseCycleModal={() => setCycleRentOpen(false)} onSaveCycleRent={handleSaveCycleRent} @@ -2061,6 +2084,12 @@ function App() { settlementCurrency: value })) } + onBillingHouseholdNameChange={(value) => + setBillingForm((current) => ({ + ...current, + householdName: value + })) + } onBillingAdjustmentPolicyChange={(value) => setBillingForm((current) => ({ ...current, @@ -2290,7 +2319,11 @@ function App() { - - -
{panel()}
diff --git a/apps/miniapp/src/demo/miniapp-demo.ts b/apps/miniapp/src/demo/miniapp-demo.ts index 91813c5..89bfa85 100644 --- a/apps/miniapp/src/demo/miniapp-demo.ts +++ b/apps/miniapp/src/demo/miniapp-demo.ts @@ -9,6 +9,7 @@ import type { export const demoMember: NonNullable = { id: 'demo-member', householdId: 'demo-household', + householdName: 'Kojori House', displayName: 'Stas', status: 'active', isAdmin: true, @@ -191,6 +192,7 @@ export const demoPendingMembers: readonly MiniAppPendingMember[] = [ ] export const demoAdminSettings: MiniAppAdminSettingsPayload = { + householdName: 'Kojori House', settings: { householdId: 'demo-household', settlementCurrency: 'GEL', diff --git a/apps/miniapp/src/i18n.ts b/apps/miniapp/src/i18n.ts index 805c71f..0268b0d 100644 --- a/apps/miniapp/src/i18n.ts +++ b/apps/miniapp/src/i18n.ts @@ -30,6 +30,10 @@ export const dictionary = { reload: 'Retry', language: 'Language', householdLanguage: 'Household language', + generalSettingsBody: + 'Household identity, default language, and personal profile controls live here.', + householdNameLabel: 'Household name', + householdNameHint: 'This appears in the mini app, join flow, and bot responses.', savingLanguage: 'Saving…', onLabel: 'On', offLabel: 'Off', @@ -37,6 +41,7 @@ export const dictionary = { balances: 'Balances', ledger: 'Ledger', house: 'House', + houseSectionGeneral: 'General', houseSectionBilling: 'Billing', houseSectionUtilities: 'Utilities', houseSectionMembers: 'Members', @@ -292,6 +297,9 @@ export const dictionary = { reload: 'Повторить', language: 'Язык', householdLanguage: 'Язык дома', + generalSettingsBody: 'Здесь живут имя дома, язык по умолчанию и доступ к личному профилю.', + householdNameLabel: 'Название дома', + householdNameHint: 'Показывается в mini app, при вступлении и в ответах бота.', savingLanguage: 'Сохраняем…', onLabel: 'Вкл', offLabel: 'Выкл', @@ -299,6 +307,7 @@ export const dictionary = { balances: 'Баланс', ledger: 'Леджер', house: 'Дом', + houseSectionGeneral: 'Общее', houseSectionBilling: 'Биллинг', houseSectionUtilities: 'Коммуналка', houseSectionMembers: 'Участники', diff --git a/apps/miniapp/src/miniapp-api.ts b/apps/miniapp/src/miniapp-api.ts index 30fff98..369e483 100644 --- a/apps/miniapp/src/miniapp-api.ts +++ b/apps/miniapp/src/miniapp-api.ts @@ -5,6 +5,7 @@ export interface MiniAppSession { member?: { id: string householdId: string + householdName: string displayName: string status: 'active' | 'away' | 'left' isAdmin: boolean @@ -138,6 +139,7 @@ export interface MiniAppDashboard { } export interface MiniAppAdminSettingsPayload { + householdName: string settings: MiniAppBillingSettings assistantConfig: MiniAppAssistantConfig topics: readonly MiniAppTopicBinding[] @@ -386,6 +388,7 @@ export async function fetchMiniAppAdminSettings( const payload = (await response.json()) as { ok: boolean authorized?: boolean + householdName?: string settings?: MiniAppBillingSettings assistantConfig?: MiniAppAssistantConfig topics?: MiniAppTopicBinding[] @@ -398,6 +401,7 @@ export async function fetchMiniAppAdminSettings( if ( !response.ok || !payload.authorized || + !payload.householdName || !payload.settings || !payload.assistantConfig || !payload.topics || @@ -409,6 +413,7 @@ export async function fetchMiniAppAdminSettings( } return { + householdName: payload.householdName, settings: payload.settings, assistantConfig: payload.assistantConfig, topics: payload.topics, @@ -423,6 +428,7 @@ export async function updateMiniAppBillingSettings( input: { settlementCurrency?: 'USD' | 'GEL' paymentBalanceAdjustmentPolicy?: 'utilities' | 'rent' | 'separate' + householdName?: string rentAmountMajor?: string rentCurrency: 'USD' | 'GEL' rentDueDay: number @@ -434,6 +440,7 @@ export async function updateMiniAppBillingSettings( assistantTone?: string } ): Promise<{ + householdName: string settings: MiniAppBillingSettings assistantConfig: MiniAppAssistantConfig }> { @@ -451,16 +458,24 @@ export async function updateMiniAppBillingSettings( const payload = (await response.json()) as { ok: boolean authorized?: boolean + householdName?: string settings?: MiniAppBillingSettings assistantConfig?: MiniAppAssistantConfig error?: string } - if (!response.ok || !payload.authorized || !payload.settings || !payload.assistantConfig) { + if ( + !response.ok || + !payload.authorized || + !payload.householdName || + !payload.settings || + !payload.assistantConfig + ) { throw new Error(payload.error ?? 'Failed to update billing settings') } return { + householdName: payload.householdName, settings: payload.settings, assistantConfig: payload.assistantConfig } diff --git a/apps/miniapp/src/screens/house-screen.tsx b/apps/miniapp/src/screens/house-screen.tsx index 2b22b43..b787c05 100644 --- a/apps/miniapp/src/screens/house-screen.tsx +++ b/apps/miniapp/src/screens/house-screen.tsx @@ -28,6 +28,7 @@ type UtilityBillDraft = { } type BillingForm = { + householdName: string settlementCurrency: 'USD' | 'GEL' paymentBalanceAdjustmentPolicy: 'utilities' | 'rent' | 'separate' rentAmountMajor: string @@ -55,6 +56,8 @@ type Props = { locale: 'en' | 'ru' readyIsAdmin: boolean householdDefaultLocale: 'en' | 'ru' + householdName: string + profileDisplayName: string dashboard: MiniAppDashboard | null adminSettings: MiniAppAdminSettingsPayload | null cycleState: MiniAppAdminCycleState | null @@ -101,6 +104,7 @@ type Props = { effectiveFromPeriod: string | null } onChangeHouseholdLocale: (locale: 'en' | 'ru') => Promise + onOpenProfileEditor: () => void onOpenCycleModal: () => void onCloseCycleModal: () => void onSaveCycleRent: () => Promise @@ -111,6 +115,7 @@ type Props = { onOpenBillingSettingsModal: () => void onCloseBillingSettingsModal: () => void onSaveBillingSettings: () => Promise + onBillingHouseholdNameChange: (value: string) => void onBillingSettlementCurrencyChange: (value: 'USD' | 'GEL') => void onBillingAdjustmentPolicyChange: (value: 'utilities' | 'rent' | 'separate') => void onBillingRentAmountChange: (value: string) => void @@ -213,15 +218,83 @@ export function HouseScreen(props: Props) { {props.copy.residentHouseTitle ?? ''}

{props.copy.residentHouseBody ?? ''}

+
+ +
} >
+ +
+
+
+
+ {props.copy.householdNameLabel ?? ''} + {props.householdName} +
+

{props.copy.householdNameHint ?? ''}

+
+ +
+
+ +
+
+ {props.copy.householdLanguage ?? ''} + {props.householdDefaultLocale.toUpperCase()} +
+
+ + +
+
+ +
+
+ {props.copy.manageProfileAction ?? ''} + {props.profileDisplayName} +
+

{props.copy.profileEditorBody ?? ''}

+
+ +
+
+
+
+
+
@@ -290,31 +363,6 @@ export function HouseScreen(props: Props) {
- -
-
- {props.copy.householdLanguage ?? ''} - {props.householdDefaultLocale.toUpperCase()} -
-
- - -
-
+ + + props.onBillingHouseholdNameChange(event.currentTarget.value) + } + /> +