feat(miniapp): add pending member admin approval

This commit is contained in:
2026-03-09 06:29:23 +04:00
parent 7c602900ee
commit d5872ede57
11 changed files with 881 additions and 3 deletions

View File

@@ -2,10 +2,13 @@ import { Match, Switch, createMemo, createSignal, onMount, type JSX } from 'soli
import { dictionary, type Locale } from './i18n'
import {
approveMiniAppPendingMember,
fetchMiniAppDashboard,
fetchMiniAppPendingMembers,
fetchMiniAppSession,
joinMiniAppHousehold,
type MiniAppDashboard
type MiniAppDashboard,
type MiniAppPendingMember
} from './miniapp-api'
import { getTelegramWebApp } from './telegram-webapp'
@@ -106,7 +109,9 @@ function App() {
})
const [activeNav, setActiveNav] = createSignal<NavigationKey>('home')
const [dashboard, setDashboard] = createSignal<MiniAppDashboard | null>(null)
const [pendingMembers, setPendingMembers] = createSignal<readonly MiniAppPendingMember[]>([])
const [joining, setJoining] = createSignal(false)
const [approvingTelegramUserId, setApprovingTelegramUserId] = createSignal<string | null>(null)
const copy = createMemo(() => dictionary[locale()])
const onboardingSession = createMemo(() => {
@@ -135,6 +140,18 @@ function App() {
}
}
async function loadPendingMembers(initData: string) {
try {
setPendingMembers(await fetchMiniAppPendingMembers(initData))
} catch (error) {
if (import.meta.env.DEV) {
console.warn('Failed to load pending mini app members', error)
}
setPendingMembers([])
}
}
async function bootstrap() {
setLocale(detectLocale())
@@ -183,6 +200,9 @@ function App() {
})
await loadDashboard(initData)
if (payload.member.isAdmin) {
await loadPendingMembers(initData)
}
} catch {
if (import.meta.env.DEV) {
setSession(demoSession)
@@ -229,6 +249,14 @@ function App() {
}
]
})
setPendingMembers([
{
telegramUserId: '555777',
displayName: 'Mia',
username: 'mia',
languageCode: 'ru'
}
])
return
}
@@ -263,6 +291,9 @@ function App() {
telegramUser: payload.telegramUser
})
await loadDashboard(initData)
if (payload.member.isAdmin) {
await loadPendingMembers(initData)
}
return
}
@@ -290,6 +321,24 @@ function App() {
}
}
async function handleApprovePendingMember(pendingTelegramUserId: string) {
const initData = webApp?.initData?.trim()
if (!initData || approvingTelegramUserId()) {
return
}
setApprovingTelegramUserId(pendingTelegramUserId)
try {
await approveMiniAppPendingMember(initData, pendingTelegramUserId)
setPendingMembers((current) =>
current.filter((member) => member.telegramUserId !== pendingTelegramUserId)
)
} finally {
setApprovingTelegramUserId(null)
}
}
const renderPanel = () => {
switch (activeNav()) {
case 'balances':
@@ -345,7 +394,47 @@ function App() {
</div>
)
case 'house':
return copy().houseEmpty
return readySession()?.member.isAdmin ? (
<div class="balance-list">
<article class="balance-item">
<header>
<strong>{copy().pendingMembersTitle}</strong>
</header>
<p>{copy().pendingMembersBody}</p>
</article>
{pendingMembers().length === 0 ? (
<article class="balance-item">
<p>{copy().pendingMembersEmpty}</p>
</article>
) : (
pendingMembers().map((member) => (
<article class="balance-item">
<header>
<strong>{member.displayName}</strong>
<span>{member.telegramUserId}</span>
</header>
<p>
{member.username
? copy().pendingMemberHandle.replace('{username}', member.username)
: (member.languageCode ?? 'Telegram')}
</p>
<button
class="ghost-button"
type="button"
disabled={approvingTelegramUserId() === member.telegramUserId}
onClick={() => void handleApprovePendingMember(member.telegramUserId)}
>
{approvingTelegramUserId() === member.telegramUserId
? copy().approvingMember
: copy().approveMemberAction}
</button>
</article>
))
)}
</div>
) : (
copy().houseEmpty
)
default:
return (
<ShowDashboard
@@ -516,7 +605,7 @@ function App() {
<article class="panel panel--wide">
<p class="eyebrow">{copy().summaryTitle}</p>
<h3>{readySession()?.member.displayName}</h3>
<p>{renderPanel()}</p>
<div>{renderPanel()}</div>
</article>
<article class="panel">