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

View File

@@ -129,10 +129,8 @@ export function FinanceVisuals(props: Props) {
</For> </For>
</svg> </svg>
<div class="purchase-chart__center"> <div class="purchase-chart__center">
<span>{props.labels.purchaseTotalLabel}</span> <strong>{props.purchaseChart.totalMajor}</strong>
<strong> <small>{props.dashboard.currency}</small>
{props.purchaseChart.totalMajor} {props.dashboard.currency}
</strong>
</div> </div>
</div> </div>
<div class="purchase-chart__legend"> <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; inset: 0;
display: grid; display: grid;
place-content: center; place-content: center;
gap: 4px; gap: 2px;
text-align: center; text-align: center;
} }
.purchase-chart__center span {
color: #c6c2bb;
font-size: 0.72rem;
text-transform: uppercase;
letter-spacing: 0.08em;
}
.purchase-chart__center strong { .purchase-chart__center strong {
font-family: 'Space Grotesk', 'IBM Plex Sans', sans-serif; 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 { .purchase-chart__legend {