refactor(miniapp): extract shell states and finance visuals

This commit is contained in:
2026-03-11 18:44:12 +04:00
parent b193f8ddce
commit ebd12eb46e
6 changed files with 119 additions and 51 deletions

View File

@@ -42,6 +42,9 @@ import { ProfileCard } from './components/layout/profile-card'
import { TopBar } from './components/layout/top-bar'
import { FinanceSummaryCards } from './components/finance/finance-summary-cards'
import { FinanceVisuals } from './components/finance/finance-visuals'
import { BlockedState } from './components/session/blocked-state'
import { LoadingState } from './components/session/loading-state'
import { OnboardingState } from './components/session/onboarding-state'
import {
demoAdminSettings,
demoCycleState,
@@ -3760,16 +3763,16 @@ function App() {
<Switch>
<Match when={session().status === 'loading'}>
<HeroBanner
badges={[copy().loadingBadge]}
<LoadingState
badge={copy().loadingBadge}
title={copy().loadingTitle}
body={copy().loadingBody}
/>
</Match>
<Match when={session().status === 'blocked'}>
<HeroBanner
badges={[copy().loadingBadge]}
<BlockedState
badge={copy().loadingBadge}
title={
blockedSession()?.reason === 'telegram_only'
? copy().telegramOnlyTitle
@@ -3780,24 +3783,23 @@ function App() {
? copy().telegramOnlyBody
: copy().unexpectedErrorBody
}
action={{ label: copy().reload, onClick: () => window.location.reload() }}
reloadLabel={copy().reload}
onReload={() => window.location.reload()}
/>
</Match>
<Match when={session().status === 'onboarding'}>
<section class="hero-card">
<div class="hero-card__meta">
<span class="pill">{copy().loadingBadge}</span>
</div>
<h2>
{onboardingSession()?.mode === 'pending'
<OnboardingState
badge={copy().loadingBadge}
title={
onboardingSession()?.mode === 'pending'
? copy().pendingTitle
: onboardingSession()?.mode === 'open_from_group'
? copy().openFromGroupTitle
: copy().joinTitle}
</h2>
<p>
{onboardingSession()?.mode === 'pending'
: copy().joinTitle
}
body={
onboardingSession()?.mode === 'pending'
? copy().pendingBody.replace(
'{household}',
onboardingSession()?.householdName ?? copy().householdFallback
@@ -3807,29 +3809,18 @@ function App() {
: copy().joinBody.replace(
'{household}',
onboardingSession()?.householdName ?? copy().householdFallback
)}
</p>
<div class="nav-grid">
{onboardingSession()?.mode === 'join_required' ? (
<Button variant="ghost" disabled={joining()} onClick={handleJoinHousehold}>
{joining() ? copy().joining : copy().joinAction}
</Button>
) : null}
{joinDeepLink() ? (
<a
class="ui-button ui-button--ghost"
href={joinDeepLink() ?? '#'}
target="_blank"
rel="noreferrer"
>
{copy().botLinkAction}
</a>
) : null}
<Button variant="ghost" onClick={() => window.location.reload()}>
{copy().reload}
</Button>
</div>
</section>
)
}
canJoin={onboardingSession()?.mode === 'join_required'}
joining={joining()}
joinActionLabel={copy().joinAction}
joiningLabel={copy().joining}
botLinkLabel={copy().botLinkAction}
botLink={joinDeepLink()}
reloadLabel={copy().reload}
onJoin={handleJoinHousehold}
onReload={() => window.location.reload()}
/>
</Match>
<Match when={session().status === 'ready'}>

View File

@@ -129,10 +129,8 @@ export function FinanceVisuals(props: Props) {
</For>
</svg>
<div class="purchase-chart__center">
<span>{props.labels.purchaseTotalLabel}</span>
<strong>
{props.purchaseChart.totalMajor} {props.dashboard.currency}
</strong>
<strong>{props.purchaseChart.totalMajor}</strong>
<small>{props.dashboard.currency}</small>
</div>
</div>
<div class="purchase-chart__legend">

View File

@@ -0,0 +1,20 @@
import { HeroBanner } from '../layout/hero-banner'
type Props = {
badge: string
title: string
body: string
reloadLabel: string
onReload: () => void
}
export function BlockedState(props: Props) {
return (
<HeroBanner
badges={[props.badge]}
title={props.title}
body={props.body}
action={{ label: props.reloadLabel, onClick: props.onReload }}
/>
)
}

View File

@@ -0,0 +1,11 @@
import { HeroBanner } from '../layout/hero-banner'
type Props = {
badge: string
title: string
body: string
}
export function LoadingState(props: Props) {
return <HeroBanner badges={[props.badge]} title={props.title} body={props.body} />
}

View File

@@ -0,0 +1,47 @@
import { Show } from 'solid-js'
import { Button } from '../ui'
type Props = {
badge: string
title: string
body: string
joinActionLabel: string
joiningLabel: string
joining: boolean
canJoin: boolean
botLinkLabel: string
botLink: string | null
reloadLabel: string
onJoin: () => void
onReload: () => void
}
export function OnboardingState(props: Props) {
return (
<section class="hero-card">
<div class="hero-card__meta">
<span class="pill">{props.badge}</span>
</div>
<h2>{props.title}</h2>
<p>{props.body}</p>
<div class="nav-grid">
<Show when={props.canJoin}>
<Button variant="ghost" disabled={props.joining} onClick={props.onJoin}>
{props.joining ? props.joiningLabel : props.joinActionLabel}
</Button>
</Show>
<Show when={props.botLink}>
{(link) => (
<a class="ui-button ui-button--ghost" href={link()} target="_blank" rel="noreferrer">
{props.botLinkLabel}
</a>
)}
</Show>
<Button variant="ghost" onClick={props.onReload}>
{props.reloadLabel}
</Button>
</div>
</section>
)
}

View File

@@ -493,20 +493,21 @@ button {
inset: 0;
display: grid;
place-content: center;
gap: 4px;
gap: 2px;
text-align: center;
}
.purchase-chart__center span {
color: #c6c2bb;
font-size: 0.72rem;
text-transform: uppercase;
letter-spacing: 0.08em;
}
.purchase-chart__center strong {
font-family: 'Space Grotesk', 'IBM Plex Sans', sans-serif;
font-size: clamp(1.15rem, 4vw, 1.5rem);
font-size: clamp(1.1rem, 4vw, 1.45rem);
line-height: 1;
}
.purchase-chart__center small {
color: #c6c2bb;
font-size: 0.72rem;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.purchase-chart__legend {