refactor(miniapp): remove placeholder shell cards

This commit is contained in:
2026-03-09 17:12:21 +04:00
parent 16f9981fee
commit eb0143f132
3 changed files with 146 additions and 64 deletions

View File

@@ -107,6 +107,14 @@ function joinDeepLink(): string | null {
return `https://t.me/${context.botUsername}?start=join_${encodeURIComponent(context.joinToken)}` return `https://t.me/${context.botUsername}?start=join_${encodeURIComponent(context.joinToken)}`
} }
function dashboardMemberCount(dashboard: MiniAppDashboard | null): string {
return dashboard ? String(dashboard.members.length) : '—'
}
function dashboardLedgerCount(dashboard: MiniAppDashboard | null): string {
return dashboard ? String(dashboard.ledger.length) : '—'
}
function App() { function App() {
const [locale, setLocale] = createSignal<Locale>('en') const [locale, setLocale] = createSignal<Locale>('en')
const [session, setSession] = createSignal<SessionState>({ const [session, setSession] = createSignal<SessionState>({
@@ -479,6 +487,12 @@ function App() {
case 'house': case 'house':
return readySession()?.member.isAdmin ? ( return readySession()?.member.isAdmin ? (
<div class="balance-list"> <div class="balance-list">
<article class="balance-item">
<header>
<strong>{copy().householdSettingsTitle}</strong>
</header>
<p>{copy().householdSettingsBody}</p>
</article>
<article class="balance-item"> <article class="balance-item">
<header> <header>
<strong>{copy().householdLanguage}</strong> <strong>{copy().householdLanguage}</strong>
@@ -544,22 +558,75 @@ function App() {
)} )}
</div> </div>
) : ( ) : (
copy().houseEmpty <div class="balance-list">
<article class="balance-item">
<header>
<strong>{copy().residentHouseTitle}</strong>
</header>
<p>{copy().residentHouseBody}</p>
</article>
</div>
) )
default: default:
return ( return (
<div class="home-grid">
<article class="stat-card">
<span>{copy().totalDue}</span>
<strong>
{dashboard() ? `${dashboard()!.totalDueMajor} ${dashboard()!.currency}` : '—'}
</strong>
</article>
<article class="stat-card">
<span>{copy().membersCount}</span>
<strong>{dashboardMemberCount(dashboard())}</strong>
</article>
<article class="stat-card">
<span>{copy().ledgerEntries}</span>
<strong>{dashboardLedgerCount(dashboard())}</strong>
</article>
{readySession()?.member.isAdmin ? (
<article class="stat-card">
<span>{copy().pendingRequests}</span>
<strong>{String(pendingMembers().length)}</strong>
</article>
) : null}
<article class="balance-item">
<header>
<strong>{copy().overviewTitle}</strong>
</header>
<p>{copy().overviewBody}</p>
</article>
<article class="balance-item">
<header>
<strong>{copy().latestActivityTitle}</strong>
</header>
<ShowDashboard <ShowDashboard
dashboard={dashboard()} dashboard={dashboard()}
fallback={<p>{copy().summaryBody}</p>} fallback={<p>{copy().latestActivityEmpty}</p>}
render={(data) => ( render={(data) =>
<> data.ledger.length === 0 ? (
<p> <p>{copy().latestActivityEmpty}</p>
{copy().totalDue}: {data.totalDueMajor} {data.currency} ) : (
</p> <div class="ledger-list">
<p>{copy().summaryBody}</p> {data.ledger.slice(0, 3).map((entry) => (
</> <article class="ledger-item">
)} <header>
<strong>{entry.title}</strong>
<span>
{entry.amountMajor} {data.currency}
</span>
</header>
<p>{entry.actorDisplayName ?? 'Household'}</p>
</article>
))}
</div>
)
}
/> />
</article>
</div>
) )
} }
} }
@@ -601,7 +668,7 @@ function App() {
<Switch> <Switch>
<Match when={session().status === 'loading'}> <Match when={session().status === 'loading'}>
<section class="hero-card"> <section class="hero-card">
<span class="pill">{copy().navHint}</span> <span class="pill">{copy().loadingBadge}</span>
<h2>{copy().loadingTitle}</h2> <h2>{copy().loadingTitle}</h2>
<p>{copy().loadingBody}</p> <p>{copy().loadingBody}</p>
</section> </section>
@@ -609,7 +676,7 @@ function App() {
<Match when={session().status === 'blocked'}> <Match when={session().status === 'blocked'}>
<section class="hero-card"> <section class="hero-card">
<span class="pill">{copy().navHint}</span> <span class="pill">{copy().loadingBadge}</span>
<h2> <h2>
{blockedSession()?.reason === 'telegram_only' {blockedSession()?.reason === 'telegram_only'
? copy().telegramOnlyTitle ? copy().telegramOnlyTitle
@@ -628,7 +695,7 @@ function App() {
<Match when={session().status === 'onboarding'}> <Match when={session().status === 'onboarding'}>
<section class="hero-card"> <section class="hero-card">
<span class="pill">{copy().navHint}</span> <span class="pill">{copy().loadingBadge}</span>
<h2> <h2>
{onboardingSession()?.mode === 'pending' {onboardingSession()?.mode === 'pending'
? copy().pendingTitle ? copy().pendingTitle
@@ -681,7 +748,7 @@ function App() {
<section class="hero-card"> <section class="hero-card">
<div class="hero-card__meta"> <div class="hero-card__meta">
<span class="pill"> <span class="pill">
{readySession()?.mode === 'demo' ? copy().demoBadge : copy().navHint} {readySession()?.mode === 'demo' ? copy().demoBadge : copy().liveBadge}
</span> </span>
<span class="pill pill--muted"> <span class="pill pill--muted">
{readySession()?.member.isAdmin ? copy().adminTag : copy().residentTag} {readySession()?.member.isAdmin ? copy().adminTag : copy().residentTag}
@@ -692,7 +759,7 @@ function App() {
{copy().welcome},{' '} {copy().welcome},{' '}
{readySession()?.telegramUser.firstName ?? readySession()?.member.displayName} {readySession()?.telegramUser.firstName ?? readySession()?.member.displayName}
</h2> </h2>
<p>{copy().sectionBody}</p> <p>{copy().overviewBody}</p>
</section> </section>
<nav class="nav-grid"> <nav class="nav-grid">
@@ -716,25 +783,10 @@ function App() {
<section class="content-grid"> <section class="content-grid">
<article class="panel panel--wide"> <article class="panel panel--wide">
<p class="eyebrow">{copy().summaryTitle}</p> <p class="eyebrow">{copy().overviewTitle}</p>
<h3>{readySession()?.member.displayName}</h3> <h3>{readySession()?.member.displayName}</h3>
<div>{renderPanel()}</div> <div>{renderPanel()}</div>
</article> </article>
<article class="panel">
<p class="eyebrow">{copy().cardAccess}</p>
<p>{copy().cardAccessBody}</p>
</article>
<article class="panel">
<p class="eyebrow">{copy().cardLocale}</p>
<p>{copy().cardLocaleBody}</p>
</article>
<article class="panel">
<p class="eyebrow">{copy().cardNext}</p>
<p>{copy().cardNextBody}</p>
</article>
</section> </section>
</Match> </Match>
</Switch> </Switch>

View File

@@ -6,7 +6,9 @@ export const dictionary = {
appSubtitle: 'Shared home dashboard', appSubtitle: 'Shared home dashboard',
loadingTitle: 'Checking your household access', loadingTitle: 'Checking your household access',
loadingBody: 'Validating Telegram session and membership…', loadingBody: 'Validating Telegram session and membership…',
loadingBadge: 'Secure session',
demoBadge: 'Demo mode', demoBadge: 'Demo mode',
liveBadge: 'Live household',
joinTitle: 'Welcome to your household', joinTitle: 'Welcome to your household',
joinBody: joinBody:
'You are not a member of {household} yet. Send a join request and wait for admin approval.', 'You are not a member of {household} yet. Send a join request and wait for admin approval.',
@@ -33,28 +35,28 @@ export const dictionary = {
balances: 'Balances', balances: 'Balances',
ledger: 'Ledger', ledger: 'Ledger',
house: 'House', house: 'House',
navHint: 'Shell v1',
welcome: 'Welcome back', welcome: 'Welcome back',
adminTag: 'Admin', adminTag: 'Admin',
residentTag: 'Resident', residentTag: 'Resident',
summaryTitle: 'Current shell', overviewTitle: 'Current cycle',
summaryBody: overviewBody:
'Balances, ledger, and house wiki will land in the next tickets. This shell focuses on verified access, navigation, and mobile layout.', 'Use the sections below to review balances, ledger entries, and household access.',
totalDue: 'Total due', totalDue: 'Total due',
membersCount: 'Members',
ledgerEntries: 'Ledger entries',
pendingRequests: 'Pending requests',
shareRent: 'Rent', shareRent: 'Rent',
shareUtilities: 'Utilities', shareUtilities: 'Utilities',
shareOffset: 'Shared buys', shareOffset: 'Shared buys',
ledgerTitle: 'Included ledger', ledgerTitle: 'Included ledger',
emptyDashboard: 'No billing cycle is ready yet.', emptyDashboard: 'No billing cycle is ready yet.',
cardAccess: 'Access', latestActivityTitle: 'Latest activity',
cardAccessBody: 'Telegram identity verified and matched to a household member.', latestActivityEmpty: 'Recent utility and purchase entries will appear here.',
cardLocale: 'Locale', householdSettingsTitle: 'Household settings',
cardLocaleBody: 'Switch RU/EN immediately without reloading the shell.', householdSettingsBody: 'Control household defaults and approve roommates who requested access.',
cardNext: 'Next up', residentHouseTitle: 'Household access',
cardNextBody: 'Balances, ledger, and house pages will plug into this navigation.', residentHouseBody:
sectionTitle: 'Ready for the next features', 'Your admins manage household settings and approvals here. You can still switch your own language above.',
sectionBody:
'This layout is intentionally narrow and mobile-first so it behaves well inside the Telegram webview.',
pendingMembersTitle: 'Pending members', pendingMembersTitle: 'Pending members',
pendingMembersBody: pendingMembersBody:
'Approve roommates here after they request access from the group join flow.', 'Approve roommates here after they request access from the group join flow.',
@@ -64,14 +66,16 @@ export const dictionary = {
pendingMemberHandle: '@{username}', pendingMemberHandle: '@{username}',
balancesEmpty: 'Balances will appear here once the dashboard API lands.', balancesEmpty: 'Balances will appear here once the dashboard API lands.',
ledgerEmpty: 'Ledger entries will appear here after the finance view is connected.', ledgerEmpty: 'Ledger entries will appear here after the finance view is connected.',
houseEmpty: 'House rules, Wi-Fi info, and practical notes will live here.' houseEmpty: 'Household details will appear here.'
}, },
ru: { ru: {
appTitle: 'Kojori House', appTitle: 'Kojori House',
appSubtitle: 'Панель общего дома', appSubtitle: 'Панель общего дома',
loadingTitle: 'Проверяем доступ к дому', loadingTitle: 'Проверяем доступ к дому',
loadingBody: 'Проверяем Telegram-сессию и членство…', loadingBody: 'Проверяем Telegram-сессию и членство…',
loadingBadge: 'Защищённая сессия',
demoBadge: 'Демо режим', demoBadge: 'Демо режим',
liveBadge: 'Живой household',
joinTitle: 'Добро пожаловать домой', joinTitle: 'Добро пожаловать домой',
joinBody: joinBody:
'Ты пока не участник {household}. Отправь заявку на вступление и дождись подтверждения админа.', 'Ты пока не участник {household}. Отправь заявку на вступление и дождись подтверждения админа.',
@@ -98,28 +102,27 @@ export const dictionary = {
balances: 'Баланс', balances: 'Баланс',
ledger: 'Леджер', ledger: 'Леджер',
house: 'Дом', house: 'Дом',
navHint: 'Shell v1',
welcome: 'С возвращением', welcome: 'С возвращением',
adminTag: 'Админ', adminTag: 'Админ',
residentTag: 'Житель', residentTag: 'Житель',
summaryTitle: 'Текущая оболочка', overviewTitle: 'Текущий цикл',
summaryBody: overviewBody: 'Ниже можно посмотреть балансы, записи леджера и доступ к household.',
'Баланс, леджер и вики дома появятся в следующих тикетах. Сейчас приоритет — проверенный доступ, навигация и мобильный layout.',
totalDue: 'Итого к оплате', totalDue: 'Итого к оплате',
membersCount: 'Участники',
ledgerEntries: 'Записи леджера',
pendingRequests: 'Ожидают подтверждения',
shareRent: 'Аренда', shareRent: 'Аренда',
shareUtilities: 'Коммуналка', shareUtilities: 'Коммуналка',
shareOffset: 'Общие покупки', shareOffset: 'Общие покупки',
ledgerTitle: 'Вошедшие операции', ledgerTitle: 'Вошедшие операции',
emptyDashboard: 'Пока нет готового billing cycle.', emptyDashboard: 'Пока нет готового billing cycle.',
cardAccess: 'Доступ', latestActivityTitle: 'Последняя активность',
cardAccessBody: 'Telegram-личность подтверждена и сопоставлена с участником household.', latestActivityEmpty: 'Здесь появятся последние коммунальные платежи и покупки.',
cardLocale: 'Локаль', householdSettingsTitle: 'Настройки household',
cardLocaleBody: 'RU/EN переключаются сразу, без перезагрузки.', householdSettingsBody: 'Здесь можно менять язык household и подтверждать новых соседей.',
cardNext: 'Дальше', residentHouseTitle: 'Доступ к household',
cardNextBody: 'Баланс, леджер и страницы дома подключатся к этой навигации.', residentHouseBody:
sectionTitle: 'Основа готова для следующих функций', 'Настройки household и подтверждение заявок управляются админами. Свой язык можно менять переключателем выше.',
sectionBody:
'Этот layout специально сделан узким и mobile-first, чтобы хорошо жить внутри Telegram webview.',
pendingMembersTitle: 'Ожидающие участники', pendingMembersTitle: 'Ожидающие участники',
pendingMembersBody: pendingMembersBody:
'Подтверждай соседей здесь после того, как они отправят заявку через кнопку подключения.', 'Подтверждай соседей здесь после того, как они отправят заявку через кнопку подключения.',
@@ -129,6 +132,6 @@ export const dictionary = {
pendingMemberHandle: '@{username}', pendingMemberHandle: '@{username}',
balancesEmpty: 'Баланс появится здесь, когда подключим dashboard API.', balancesEmpty: 'Баланс появится здесь, когда подключим dashboard API.',
ledgerEmpty: 'Записи леджера появятся здесь после подключения finance view.', ledgerEmpty: 'Записи леджера появятся здесь после подключения finance view.',
houseEmpty: 'Правила дома, Wi-Fi и полезные инструкции будут здесь.' houseEmpty: 'Детали household появятся здесь.'
} }
} satisfies Record<Locale, Record<string, string>> } satisfies Record<Locale, Record<string, string>>

View File

@@ -228,13 +228,15 @@ button {
} }
.balance-list, .balance-list,
.ledger-list { .ledger-list,
.home-grid {
display: grid; display: grid;
gap: 12px; gap: 12px;
} }
.balance-item, .balance-item,
.ledger-item { .ledger-item,
.stat-card {
border: 1px solid rgb(255 255 255 / 0.08); border: 1px solid rgb(255 255 255 / 0.08);
border-radius: 18px; border-radius: 18px;
padding: 14px; padding: 14px;
@@ -260,6 +262,27 @@ button {
margin-top: 6px; margin-top: 6px;
} }
.home-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.stat-card {
display: grid;
gap: 8px;
}
.stat-card span {
color: #c6c2bb;
font-size: 0.82rem;
text-transform: uppercase;
letter-spacing: 0.08em;
}
.stat-card strong {
font-family: 'Space Grotesk', 'IBM Plex Sans', sans-serif;
font-size: clamp(1.2rem, 4vw, 1.7rem);
}
.panel--wide { .panel--wide {
min-height: 170px; min-height: 170px;
} }
@@ -275,6 +298,10 @@ button {
grid-template-columns: 1.3fr 1fr 1fr; grid-template-columns: 1.3fr 1fr 1fr;
} }
.home-grid {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.panel--wide { .panel--wide {
grid-column: 1 / -1; grid-column: 1 / -1;
} }