feat(miniapp): refine UI and add utility bill management

- Fix collapsible padding and button spacing
- Add subtotal to balance card
- Add utility bill management for admins
- Fix lints and type checks across the monorepo
- Implement rejectPendingHouseholdMember in repository and service
This commit is contained in:
2026-03-13 05:52:34 +04:00
parent 25c4928ca9
commit 94a5904f54
58 changed files with 5400 additions and 7006 deletions

View File

@@ -132,6 +132,9 @@ function createRepositoryStub() {
members.set(member.telegramUserId, member)
return member
},
rejectPendingHouseholdMember: async (input) => {
return pendingMembers.delete(input.telegramUserId)
},
updateHouseholdDefaultLocale: async (_householdId, locale) => ({
...household,
defaultLocale: locale

View File

@@ -8,6 +8,7 @@ import type {
HouseholdTelegramChatRecord,
HouseholdTopicBindingRecord
} from '@household/ports'
import type { SupportedLocale } from '@household/domain'
import { createHouseholdOnboardingService } from './household-onboarding-service'
@@ -141,7 +142,10 @@ function createRepositoryStub() {
isAdmin: input.isAdmin === true
}
},
async updateHouseholdDefaultLocale(_householdId, locale) {
async rejectPendingHouseholdMember(input) {
return pendingMembers.delete(input.telegramUserId)
},
async updateHouseholdDefaultLocale(_householdId: string, locale: SupportedLocale) {
return {
...household,
defaultLocale: locale

View File

@@ -8,6 +8,7 @@ import type {
HouseholdTelegramChatRecord,
HouseholdTopicBindingRecord
} from '@household/ports'
import type { SupportedLocale } from '@household/domain'
import { createHouseholdSetupService } from './household-setup-service'
@@ -228,7 +229,11 @@ function createRepositoryStub() {
members.set(key, member)
return member
},
async updateHouseholdDefaultLocale(householdId, locale) {
async rejectPendingHouseholdMember(input) {
const key = `${input.householdId}:${input.telegramUserId}`
return pendingMembers.delete(key)
},
async updateHouseholdDefaultLocale(householdId: string, locale: SupportedLocale) {
const household =
[...households.values()].find((entry) => entry.householdId === householdId) ?? null
if (!household) {

View File

@@ -27,7 +27,10 @@ function createRepository(): HouseholdConfigurationRepository {
}
return {
registerTelegramHouseholdChat: async () => ({ status: 'existing', household }),
registerTelegramHouseholdChat: async () => ({
status: 'existing',
household
}),
getTelegramHouseholdChat: async () => household,
getHouseholdChatByHouseholdId: async () => household,
bindHouseholdTopic: async (input) => ({
@@ -66,6 +69,7 @@ function createRepository(): HouseholdConfigurationRepository {
listHouseholdMembersByTelegramUserId: async () => [member],
listPendingHouseholdMembers: async () => [],
approvePendingHouseholdMember: async () => member,
rejectPendingHouseholdMember: async () => false,
updateHouseholdDefaultLocale: async (_householdId, locale) => ({
...household,
defaultLocale: locale

View File

@@ -121,6 +121,7 @@ function repository(): HouseholdConfigurationRepository {
isAdmin: false
}
: null,
rejectPendingHouseholdMember: async (input) => input.telegramUserId === '123456',
updateHouseholdDefaultLocale: async (_householdId, locale) => ({
householdId: 'household-1',
householdName: 'Kojori House',

View File

@@ -110,6 +110,19 @@ export interface MiniAppAdminService {
reason: 'not_admin' | 'pending_not_found'
}
>
rejectPendingMember(input: {
householdId: string
actorIsAdmin: boolean
pendingTelegramUserId: string
}): Promise<
| {
status: 'rejected_member'
}
| {
status: 'rejected'
reason: 'not_admin' | 'pending_not_found'
}
>
promoteMemberToAdmin(input: {
householdId: string
actorIsAdmin: boolean
@@ -536,6 +549,31 @@ export function createMiniAppAdminService(
}
},
async rejectPendingMember(input) {
if (!input.actorIsAdmin) {
return {
status: 'rejected',
reason: 'not_admin'
}
}
const success = await repository.rejectPendingHouseholdMember({
householdId: input.householdId,
telegramUserId: input.pendingTelegramUserId
})
if (!success) {
return {
status: 'rejected',
reason: 'pending_not_found'
}
}
return {
status: 'rejected_member'
}
},
async promoteMemberToAdmin(input) {
if (!input.actorIsAdmin) {
return {