mirror of
https://github.com/whekin/household-bot.git
synced 2026-03-31 15:54:03 +00:00
feat(miniapp): add member rent weight controls
This commit is contained in:
@@ -13,6 +13,7 @@ import {
|
||||
joinMiniAppHousehold,
|
||||
openMiniAppBillingCycle,
|
||||
promoteMiniAppMember,
|
||||
updateMiniAppMemberRentWeight,
|
||||
type MiniAppAdminCycleState,
|
||||
type MiniAppAdminSettingsPayload,
|
||||
updateMiniAppLocalePreference,
|
||||
@@ -143,6 +144,8 @@ function App() {
|
||||
const [joining, setJoining] = createSignal(false)
|
||||
const [approvingTelegramUserId, setApprovingTelegramUserId] = createSignal<string | null>(null)
|
||||
const [promotingMemberId, setPromotingMemberId] = createSignal<string | null>(null)
|
||||
const [savingRentWeightMemberId, setSavingRentWeightMemberId] = createSignal<string | null>(null)
|
||||
const [rentWeightDrafts, setRentWeightDrafts] = createSignal<Record<string, string>>({})
|
||||
const [savingMemberLocale, setSavingMemberLocale] = createSignal(false)
|
||||
const [savingHouseholdLocale, setSavingHouseholdLocale] = createSignal(false)
|
||||
const [savingBillingSettings, setSavingBillingSettings] = createSignal(false)
|
||||
@@ -212,6 +215,11 @@ function App() {
|
||||
try {
|
||||
const payload = await fetchMiniAppAdminSettings(initData)
|
||||
setAdminSettings(payload)
|
||||
setRentWeightDrafts(
|
||||
Object.fromEntries(
|
||||
payload.members.map((member) => [member.id, String(member.rentShareWeight)])
|
||||
)
|
||||
)
|
||||
setCycleForm((current) => ({
|
||||
...current,
|
||||
utilityCategorySlug:
|
||||
@@ -721,11 +729,50 @@ function App() {
|
||||
}
|
||||
: current
|
||||
)
|
||||
setRentWeightDrafts((current) => ({
|
||||
...current,
|
||||
[member.id]: String(member.rentShareWeight)
|
||||
}))
|
||||
} finally {
|
||||
setPromotingMemberId(null)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSaveRentWeight(memberId: string) {
|
||||
const initData = webApp?.initData?.trim()
|
||||
const currentReady = readySession()
|
||||
const nextWeight = Number(rentWeightDrafts()[memberId] ?? '')
|
||||
if (
|
||||
!initData ||
|
||||
currentReady?.mode !== 'live' ||
|
||||
!currentReady.member.isAdmin ||
|
||||
!Number.isInteger(nextWeight) ||
|
||||
nextWeight <= 0
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
setSavingRentWeightMemberId(memberId)
|
||||
|
||||
try {
|
||||
const member = await updateMiniAppMemberRentWeight(initData, memberId, nextWeight)
|
||||
setAdminSettings((current) =>
|
||||
current
|
||||
? {
|
||||
...current,
|
||||
members: current.members.map((item) => (item.id === member.id ? member : item))
|
||||
}
|
||||
: current
|
||||
)
|
||||
setRentWeightDrafts((current) => ({
|
||||
...current,
|
||||
[member.id]: String(member.rentShareWeight)
|
||||
}))
|
||||
} finally {
|
||||
setSavingRentWeightMemberId(null)
|
||||
}
|
||||
}
|
||||
|
||||
const renderPanel = () => {
|
||||
switch (activeNav()) {
|
||||
case 'balances':
|
||||
@@ -1223,6 +1270,32 @@ function App() {
|
||||
<strong>{member.displayName}</strong>
|
||||
<span>{member.isAdmin ? copy().adminTag : copy().residentTag}</span>
|
||||
</header>
|
||||
<label class="settings-field settings-field--wide">
|
||||
<span>{copy().rentWeightLabel}</span>
|
||||
<input
|
||||
inputmode="numeric"
|
||||
value={rentWeightDrafts()[member.id] ?? String(member.rentShareWeight)}
|
||||
onInput={(event) =>
|
||||
setRentWeightDrafts((current) => ({
|
||||
...current,
|
||||
[member.id]: event.currentTarget.value
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
<button
|
||||
class="ghost-button"
|
||||
type="button"
|
||||
disabled={
|
||||
savingRentWeightMemberId() === member.id ||
|
||||
Number(rentWeightDrafts()[member.id] ?? member.rentShareWeight) <= 0
|
||||
}
|
||||
onClick={() => void handleSaveRentWeight(member.id)}
|
||||
>
|
||||
{savingRentWeightMemberId() === member.id
|
||||
? copy().savingRentWeight
|
||||
: copy().saveRentWeightAction}
|
||||
</button>
|
||||
{!member.isAdmin ? (
|
||||
<button
|
||||
class="ghost-button"
|
||||
|
||||
@@ -88,6 +88,9 @@ export const dictionary = {
|
||||
savingCategory: 'Saving…',
|
||||
adminsTitle: 'Admins',
|
||||
adminsBody: 'Promote trusted household members so they can manage billing and approvals.',
|
||||
rentWeightLabel: 'Rent weight',
|
||||
saveRentWeightAction: 'Save rent weight',
|
||||
savingRentWeight: 'Saving weight…',
|
||||
promoteAdminAction: 'Promote to admin',
|
||||
promotingAdmin: 'Promoting…',
|
||||
residentHouseTitle: 'Household access',
|
||||
@@ -192,6 +195,9 @@ export const dictionary = {
|
||||
adminsTitle: 'Админы',
|
||||
adminsBody:
|
||||
'Повышай доверенных участников, чтобы они могли управлять биллингом и подтверждениями.',
|
||||
rentWeightLabel: 'Вес аренды',
|
||||
saveRentWeightAction: 'Сохранить вес аренды',
|
||||
savingRentWeight: 'Сохраняем вес…',
|
||||
promoteAdminAction: 'Сделать админом',
|
||||
promotingAdmin: 'Повышаем…',
|
||||
residentHouseTitle: 'Доступ к household',
|
||||
|
||||
@@ -37,6 +37,7 @@ export interface MiniAppPendingMember {
|
||||
export interface MiniAppMember {
|
||||
id: string
|
||||
displayName: string
|
||||
rentShareWeight: number
|
||||
isAdmin: boolean
|
||||
}
|
||||
|
||||
@@ -452,6 +453,37 @@ export async function promoteMiniAppMember(
|
||||
return payload.member
|
||||
}
|
||||
|
||||
export async function updateMiniAppMemberRentWeight(
|
||||
initData: string,
|
||||
memberId: string,
|
||||
rentShareWeight: number
|
||||
): Promise<MiniAppMember> {
|
||||
const response = await fetch(`${apiBaseUrl()}/api/miniapp/admin/members/rent-weight`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'content-type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
initData,
|
||||
memberId,
|
||||
rentShareWeight
|
||||
})
|
||||
})
|
||||
|
||||
const payload = (await response.json()) as {
|
||||
ok: boolean
|
||||
authorized?: boolean
|
||||
member?: MiniAppMember
|
||||
error?: string
|
||||
}
|
||||
|
||||
if (!response.ok || !payload.member) {
|
||||
throw new Error(payload.error ?? 'Failed to update member rent weight')
|
||||
}
|
||||
|
||||
return payload.member
|
||||
}
|
||||
|
||||
export async function fetchMiniAppBillingCycle(initData: string): Promise<MiniAppAdminCycleState> {
|
||||
const response = await fetch(`${apiBaseUrl()}/api/miniapp/admin/billing-cycle`, {
|
||||
method: 'POST',
|
||||
|
||||
Reference in New Issue
Block a user