refactor(miniapp): add rewrite foundation and demo fixtures

This commit is contained in:
2026-03-11 18:41:36 +04:00
parent d40f5e1d84
commit b193f8ddce
19 changed files with 1073 additions and 490 deletions

View File

@@ -10,8 +10,13 @@
"lint": "oxlint \"src\"" "lint": "oxlint \"src\""
}, },
"dependencies": { "dependencies": {
"@kobalte/core": "0.13.11",
"@tanstack/solid-query": "5.90.23",
"@twa-dev/sdk": "8.0.2", "@twa-dev/sdk": "8.0.2",
"solid-js": "^1.9.9" "class-variance-authority": "0.7.1",
"clsx": "2.1.1",
"solid-js": "^1.9.9",
"zod": "4.3.6"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/vite": "^4.1.13", "@tailwindcss/vite": "^4.1.13",

View File

@@ -36,6 +36,20 @@ import {
type MiniAppPendingMember type MiniAppPendingMember
} from './miniapp-api' } from './miniapp-api'
import { Button, Field, IconButton, Modal } from './components/ui' import { Button, Field, IconButton, Modal } from './components/ui'
import { HeroBanner } from './components/layout/hero-banner'
import { NavigationTabs } from './components/layout/navigation-tabs'
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 {
demoAdminSettings,
demoCycleState,
demoDashboard,
demoMember,
demoPendingMembers,
demoTelegramUser
} from './demo/miniapp-demo'
import { getTelegramWebApp } from './telegram-webapp' import { getTelegramWebApp } from './telegram-webapp'
type SessionState = type SessionState =
@@ -106,19 +120,8 @@ const chartPalette = ['#f7b389', '#6fd3c0', '#f06a8d', '#94a8ff', '#f3d36f', '#7
const demoSession: Extract<SessionState, { status: 'ready' }> = { const demoSession: Extract<SessionState, { status: 'ready' }> = {
status: 'ready', status: 'ready',
mode: 'demo', mode: 'demo',
member: { member: demoMember,
id: 'demo-member', telegramUser: demoTelegramUser
displayName: 'Demo Resident',
status: 'active',
isAdmin: false,
preferredLocale: 'en',
householdDefaultLocale: 'en'
},
telegramUser: {
firstName: 'Demo',
username: 'demo_user',
languageCode: 'en'
}
} }
function detectLocale(): Locale { function detectLocale(): Locale {
@@ -952,6 +955,68 @@ function App() {
} }
} }
function applyDemoState() {
setDisplayNameDraft(demoSession.member.displayName)
setSession(demoSession)
setDashboard(demoDashboard)
setPendingMembers([...demoPendingMembers])
setAdminSettings(demoAdminSettings)
setCycleState(demoCycleState)
setPurchaseDraftMap(purchaseDrafts(demoDashboard.ledger))
setPaymentDraftMap(paymentDrafts(demoDashboard.ledger))
setMemberDisplayNameDrafts(
Object.fromEntries(demoAdminSettings.members.map((member) => [member.id, member.displayName]))
)
setRentWeightDrafts(
Object.fromEntries(
demoAdminSettings.members.map((member) => [member.id, String(member.rentShareWeight)])
)
)
setMemberStatusDrafts(
Object.fromEntries(demoAdminSettings.members.map((member) => [member.id, member.status]))
)
setMemberAbsencePolicyDrafts(
Object.fromEntries(
demoAdminSettings.members.map((member) => [
member.id,
resolvedMemberAbsencePolicy(member.id, member.status, demoAdminSettings).policy
])
)
)
setBillingForm({
settlementCurrency: demoAdminSettings.settings.settlementCurrency,
paymentBalanceAdjustmentPolicy: demoAdminSettings.settings.paymentBalanceAdjustmentPolicy,
rentAmountMajor: demoAdminSettings.settings.rentAmountMinor
? (Number(demoAdminSettings.settings.rentAmountMinor) / 100).toFixed(2)
: '',
rentCurrency: demoAdminSettings.settings.rentCurrency,
rentDueDay: demoAdminSettings.settings.rentDueDay,
rentWarningDay: demoAdminSettings.settings.rentWarningDay,
utilitiesDueDay: demoAdminSettings.settings.utilitiesDueDay,
utilitiesReminderDay: demoAdminSettings.settings.utilitiesReminderDay,
timezone: demoAdminSettings.settings.timezone
})
setCycleForm((current) => ({
...current,
period: demoCycleState.cycle?.period ?? current.period,
rentCurrency: demoAdminSettings.settings.rentCurrency,
utilityCurrency: demoAdminSettings.settings.settlementCurrency,
rentAmountMajor: demoAdminSettings.settings.rentAmountMinor
? (Number(demoAdminSettings.settings.rentAmountMinor) / 100).toFixed(2)
: '',
utilityCategorySlug:
demoAdminSettings.categories.find((category) => category.isActive)?.slug ?? '',
utilityAmountMajor: ''
}))
setPaymentForm({
memberId: demoAdminSettings.members[0]?.id ?? '',
kind: 'rent',
amountMajor: '',
currency: demoAdminSettings.settings.settlementCurrency
})
setUtilityBillDrafts(cycleUtilityBillDrafts(demoCycleState.utilityBills))
}
async function bootstrap() { async function bootstrap() {
const fallbackLocale = detectLocale() const fallbackLocale = detectLocale()
setLocale(fallbackLocale) setLocale(fallbackLocale)
@@ -962,7 +1027,7 @@ function App() {
const initData = webApp?.initData?.trim() const initData = webApp?.initData?.trim()
if (!initData) { if (!initData) {
if (import.meta.env.DEV) { if (import.meta.env.DEV) {
setSession(demoSession) applyDemoState()
return return
} }
@@ -1017,99 +1082,7 @@ function App() {
} }
} catch { } catch {
if (import.meta.env.DEV) { if (import.meta.env.DEV) {
setDisplayNameDraft(demoSession.member.displayName) applyDemoState()
setSession(demoSession)
setDashboard({
period: '2026-03',
currency: 'GEL',
totalDueMajor: '1030.00',
totalPaidMajor: '501.00',
totalRemainingMajor: '529.00',
rentSourceAmountMajor: '700.00',
rentSourceCurrency: 'USD',
rentDisplayAmountMajor: '1932.00',
rentFxRateMicros: '2760000',
rentFxEffectiveDate: '2026-03-17',
members: [
{
memberId: 'demo-member',
displayName: 'Demo Resident',
rentShareMajor: '483.00',
utilityShareMajor: '32.00',
purchaseOffsetMajor: '-14.00',
netDueMajor: '501.00',
paidMajor: '501.00',
remainingMajor: '0.00',
explanations: ['Equal utility split', 'Shared purchase offset']
},
{
memberId: 'member-2',
displayName: 'Alice',
rentShareMajor: '483.00',
utilityShareMajor: '32.00',
purchaseOffsetMajor: '14.00',
netDueMajor: '529.00',
paidMajor: '0.00',
remainingMajor: '529.00',
explanations: ['Equal utility split']
}
],
ledger: [
{
id: 'purchase-1',
kind: 'purchase',
title: 'Soap',
memberId: 'member-2',
paymentKind: null,
amountMajor: '30.00',
currency: 'GEL',
displayAmountMajor: '30.00',
displayCurrency: 'GEL',
fxRateMicros: null,
fxEffectiveDate: null,
actorDisplayName: 'Alice',
occurredAt: '2026-03-12T11:00:00.000Z'
},
{
id: 'utility-1',
kind: 'utility',
title: 'Electricity',
memberId: null,
paymentKind: null,
amountMajor: '120.00',
currency: 'GEL',
displayAmountMajor: '120.00',
displayCurrency: 'GEL',
fxRateMicros: null,
fxEffectiveDate: null,
actorDisplayName: 'Alice',
occurredAt: '2026-03-12T12:00:00.000Z'
},
{
id: 'payment-1',
kind: 'payment',
title: 'rent',
memberId: 'demo-member',
paymentKind: 'rent',
amountMajor: '501.00',
currency: 'GEL',
displayAmountMajor: '501.00',
displayCurrency: 'GEL',
fxRateMicros: null,
fxEffectiveDate: null,
actorDisplayName: 'Demo Resident',
occurredAt: '2026-03-18T15:10:00.000Z'
}
]
})
setPendingMembers([
{
telegramUserId: '555777',
displayName: 'Mia',
username: 'mia',
languageCode: 'ru'
}
])
return return
} }
@@ -1874,142 +1847,6 @@ function App() {
})) }))
} }
function renderFinanceSummaryCards(data: MiniAppDashboard): JSX.Element {
return (
<>
<article class="stat-card">
<span>{copy().remainingLabel}</span>
<strong>
{data.totalRemainingMajor} {data.currency}
</strong>
</article>
<article class="stat-card">
<span>{copy().shareRent}</span>
<strong>
{data.rentDisplayAmountMajor} {data.currency}
</strong>
</article>
<article class="stat-card">
<span>{copy().shareUtilities}</span>
<strong>
{utilityTotalMajor()} {data.currency}
</strong>
</article>
<article class="stat-card">
<span>{copy().purchasesTitle}</span>
<strong>
{purchaseTotalMajor()} {data.currency}
</strong>
</article>
</>
)
}
function renderFinanceVisuals(data: MiniAppDashboard): JSX.Element {
const purchaseChart = purchaseInvestmentChart()
return (
<>
<article class="balance-item balance-item--wide">
<header>
<strong>{copy().financeVisualsTitle}</strong>
<span>
{copy().membersCount}: {String(data.members.length)}
</span>
</header>
<p>{copy().financeVisualsBody}</p>
<div class="member-visual-list">
{memberBalanceVisuals().map((item) => (
<article class="member-visual-card">
<header>
<strong>{item.member.displayName}</strong>
<span class={`balance-status ${memberRemainingClass(item.member)}`}>
{item.member.remainingMajor} {data.currency}
</span>
</header>
<div class="member-visual-bar">
<div
class="member-visual-bar__track"
style={{ width: `${item.barWidthPercent}%` }}
>
{item.segments.map((segment) => (
<span
class={`member-visual-bar__segment member-visual-bar__segment--${segment.key}`}
style={{ width: `${segment.widthPercent}%` }}
/>
))}
</div>
</div>
<div class="member-visual-meta">
{item.segments.map((segment) => (
<span class={`member-visual-chip member-visual-chip--${segment.key}`}>
{segment.label}: {segment.amountMajor} {data.currency}
</span>
))}
</div>
</article>
))}
</div>
</article>
<article class="balance-item balance-item--wide">
<header>
<strong>{copy().purchaseInvestmentsTitle}</strong>
<span>
{copy().purchaseTotalLabel}: {purchaseChart.totalMajor} {data.currency}
</span>
</header>
<p>{copy().purchaseInvestmentsBody}</p>
{purchaseChart.slices.length === 0 ? (
<p>{copy().purchaseInvestmentsEmpty}</p>
) : (
<div class="purchase-chart">
<div class="purchase-chart__figure">
<svg class="purchase-chart__donut" viewBox="0 0 120 120" aria-hidden="true">
<circle class="purchase-chart__ring" cx="60" cy="60" r="42" />
{purchaseChart.slices.map((slice) => (
<circle
class="purchase-chart__slice"
cx="60"
cy="60"
r="42"
stroke={slice.color}
stroke-dasharray={slice.dasharray}
stroke-dashoffset={slice.dashoffset}
/>
))}
</svg>
<div class="purchase-chart__center">
<span>{copy().purchaseTotalLabel}</span>
<strong>
{purchaseChart.totalMajor} {data.currency}
</strong>
</div>
</div>
<div class="purchase-chart__legend">
{purchaseChart.slices.map((slice) => (
<article class="purchase-chart__legend-item">
<div>
<span
class="purchase-chart__legend-swatch"
style={{ 'background-color': slice.color }}
/>
<strong>{slice.label}</strong>
</div>
<p>
{slice.amountMajor} {data.currency} · {copy().purchaseShareLabel}{' '}
{slice.percentage}%
</p>
</article>
))}
</div>
</div>
)}
</article>
</>
)
}
const renderPanel = () => { const renderPanel = () => {
switch (activeNav()) { switch (activeNav()) {
case 'balances': case 'balances':
@@ -2063,8 +1900,35 @@ function App() {
</div> </div>
</article> </article>
) : null} ) : null}
<div class="home-grid home-grid--summary">{renderFinanceSummaryCards(data)}</div> <div class="home-grid home-grid--summary">
{renderFinanceVisuals(data)} <FinanceSummaryCards
dashboard={data}
utilityTotalMajor={utilityTotalMajor()}
purchaseTotalMajor={purchaseTotalMajor()}
labels={{
remaining: copy().remainingLabel,
rent: copy().shareRent,
utilities: copy().shareUtilities,
purchases: copy().purchasesTitle
}}
/>
</div>
<FinanceVisuals
dashboard={data}
memberVisuals={memberBalanceVisuals()}
purchaseChart={purchaseInvestmentChart()}
remainingClass={memberRemainingClass}
labels={{
financeVisualsTitle: copy().financeVisualsTitle,
financeVisualsBody: copy().financeVisualsBody,
membersCount: copy().membersCount,
purchaseInvestmentsTitle: copy().purchaseInvestmentsTitle,
purchaseInvestmentsBody: copy().purchaseInvestmentsBody,
purchaseInvestmentsEmpty: copy().purchaseInvestmentsEmpty,
purchaseTotalLabel: copy().purchaseTotalLabel,
purchaseShareLabel: copy().purchaseShareLabel
}}
/>
<article class="balance-item"> <article class="balance-item">
<header> <header>
<strong>{copy().householdBalancesTitle}</strong> <strong>{copy().householdBalancesTitle}</strong>
@@ -3739,7 +3603,19 @@ function App() {
</article> </article>
</> </>
} }
render={(data) => renderFinanceSummaryCards(data)} render={(data) => (
<FinanceSummaryCards
dashboard={data}
utilityTotalMajor={utilityTotalMajor()}
purchaseTotalMajor={purchaseTotalMajor()}
labels={{
remaining: copy().remainingLabel,
rent: copy().shareRent,
utilities: copy().shareUtilities,
purchases: copy().purchasesTitle
}}
/>
)}
/> />
{readySession()?.member.isAdmin ? ( {readySession()?.member.isAdmin ? (
<article class="stat-card"> <article class="stat-card">
@@ -3814,7 +3690,24 @@ function App() {
<ShowDashboard <ShowDashboard
dashboard={dashboard()} dashboard={dashboard()}
fallback={null} fallback={null}
render={(data) => renderFinanceVisuals(data)} render={(data) => (
<FinanceVisuals
dashboard={data}
memberVisuals={memberBalanceVisuals()}
purchaseChart={purchaseInvestmentChart()}
remainingClass={memberRemainingClass}
labels={{
financeVisualsTitle: copy().financeVisualsTitle,
financeVisualsBody: copy().financeVisualsBody,
membersCount: copy().membersCount,
purchaseInvestmentsTitle: copy().purchaseInvestmentsTitle,
purchaseInvestmentsBody: copy().purchaseInvestmentsBody,
purchaseInvestmentsEmpty: copy().purchaseInvestmentsEmpty,
purchaseTotalLabel: copy().purchaseTotalLabel,
purchaseShareLabel: copy().purchaseShareLabel
}}
/>
)}
/> />
<article class="balance-item balance-item--wide"> <article class="balance-item balance-item--wide">
@@ -3856,66 +3749,46 @@ function App() {
<div class="shell__backdrop shell__backdrop--top" /> <div class="shell__backdrop shell__backdrop--top" />
<div class="shell__backdrop shell__backdrop--bottom" /> <div class="shell__backdrop shell__backdrop--bottom" />
<section class="topbar"> <TopBar
<div> subtitle={copy().appSubtitle}
<p class="eyebrow">{copy().appSubtitle}</p> title={copy().appTitle}
<h1>{copy().appTitle}</h1> languageLabel={copy().language}
</div> locale={locale()}
saving={savingMemberLocale()}
<label class="locale-switch"> onChange={(nextLocale) => void handleMemberLocaleChange(nextLocale)}
<span>{copy().language}</span> />
<div class="locale-switch__buttons">
<button
classList={{ 'is-active': locale() === 'en' }}
type="button"
disabled={savingMemberLocale()}
onClick={() => void handleMemberLocaleChange('en')}
>
EN
</button>
<button
classList={{ 'is-active': locale() === 'ru' }}
type="button"
disabled={savingMemberLocale()}
onClick={() => void handleMemberLocaleChange('ru')}
>
RU
</button>
</div>
</label>
</section>
<Switch> <Switch>
<Match when={session().status === 'loading'}> <Match when={session().status === 'loading'}>
<section class="hero-card"> <HeroBanner
<span class="pill">{copy().loadingBadge}</span> badges={[copy().loadingBadge]}
<h2>{copy().loadingTitle}</h2> title={copy().loadingTitle}
<p>{copy().loadingBody}</p> body={copy().loadingBody}
</section> />
</Match> </Match>
<Match when={session().status === 'blocked'}> <Match when={session().status === 'blocked'}>
<section class="hero-card"> <HeroBanner
<span class="pill">{copy().loadingBadge}</span> badges={[copy().loadingBadge]}
<h2> title={
{blockedSession()?.reason === 'telegram_only' blockedSession()?.reason === 'telegram_only'
? copy().telegramOnlyTitle ? copy().telegramOnlyTitle
: copy().unexpectedErrorTitle} : copy().unexpectedErrorTitle
</h2> }
<p> body={
{blockedSession()?.reason === 'telegram_only' blockedSession()?.reason === 'telegram_only'
? copy().telegramOnlyBody ? copy().telegramOnlyBody
: copy().unexpectedErrorBody} : copy().unexpectedErrorBody
</p> }
<button class="ghost-button" type="button" onClick={() => window.location.reload()}> action={{ label: copy().reload, onClick: () => window.location.reload() }}
{copy().reload} />
</button>
</section>
</Match> </Match>
<Match when={session().status === 'onboarding'}> <Match when={session().status === 'onboarding'}>
<section class="hero-card"> <section class="hero-card">
<div class="hero-card__meta">
<span class="pill">{copy().loadingBadge}</span> <span class="pill">{copy().loadingBadge}</span>
</div>
<h2> <h2>
{onboardingSession()?.mode === 'pending' {onboardingSession()?.mode === 'pending'
? copy().pendingTitle ? copy().pendingTitle
@@ -3938,18 +3811,13 @@ function App() {
</p> </p>
<div class="nav-grid"> <div class="nav-grid">
{onboardingSession()?.mode === 'join_required' ? ( {onboardingSession()?.mode === 'join_required' ? (
<button <Button variant="ghost" disabled={joining()} onClick={handleJoinHousehold}>
class="ghost-button"
type="button"
disabled={joining()}
onClick={handleJoinHousehold}
>
{joining() ? copy().joining : copy().joinAction} {joining() ? copy().joining : copy().joinAction}
</button> </Button>
) : null} ) : null}
{joinDeepLink() ? ( {joinDeepLink() ? (
<a <a
class="ghost-button" class="ui-button ui-button--ghost"
href={joinDeepLink() ?? '#'} href={joinDeepLink() ?? '#'}
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
@@ -3957,83 +3825,60 @@ function App() {
{copy().botLinkAction} {copy().botLinkAction}
</a> </a>
) : null} ) : null}
<button class="ghost-button" type="button" onClick={() => window.location.reload()}> <Button variant="ghost" onClick={() => window.location.reload()}>
{copy().reload} {copy().reload}
</button> </Button>
</div> </div>
</section> </section>
</Match> </Match>
<Match when={session().status === 'ready'}> <Match when={session().status === 'ready'}>
<section class="hero-card"> <HeroBanner
<div class="hero-card__meta"> badges={[
<span class="pill"> readySession()?.mode === 'demo' ? copy().demoBadge : copy().liveBadge,
{readySession()?.mode === 'demo' ? copy().demoBadge : copy().liveBadge} readySession()?.member.isAdmin ? copy().adminTag : copy().residentTag,
</span> readySession()?.member.status
<span class="pill pill--muted">
{readySession()?.member.isAdmin ? copy().adminTag : copy().residentTag}
</span>
<span class="pill pill--muted">
{readySession()?.member.status
? memberStatusLabel(readySession()!.member.status) ? memberStatusLabel(readySession()!.member.status)
: copy().memberStatusActive} : copy().memberStatusActive
</span> ]}
</div> title={`${copy().welcome}, ${readySession()?.telegramUser.firstName ?? readySession()?.member.displayName}`}
body={copy().overviewBody}
action={
readySession()?.mode === 'live'
? {
label: copy().manageProfileAction,
onClick: () => setProfileEditorOpen(true)
}
: undefined
}
/>
<h2> <NavigationTabs
{copy().welcome},{' '} items={
{readySession()?.telegramUser.firstName ?? readySession()?.member.displayName}
</h2>
<p>{copy().overviewBody}</p>
<Show when={readySession()?.mode === 'live'}>
<div class="panel-toolbar">
<Button variant="secondary" onClick={() => setProfileEditorOpen(true)}>
{copy().manageProfileAction}
</Button>
</div>
</Show>
</section>
<nav class="nav-grid">
{(
[ [
['home', copy().home], { key: 'home', label: copy().home },
['balances', copy().balances], { key: 'balances', label: copy().balances },
['ledger', copy().ledger], { key: 'ledger', label: copy().ledger },
['house', copy().house] { key: 'house', label: copy().house }
] as const ] as const
).map(([key, label]) => ( }
<button active={activeNav()}
classList={{ 'is-active': activeNav() === key }} onChange={setActiveNav}
type="button" />
onClick={() => setActiveNav(key)}
>
{label}
</button>
))}
</nav>
<section class="content-grid"> <section class="content-grid">
<article class="balance-item balance-item--accent profile-card"> <ProfileCard
<header> displayName={readySession()?.member.displayName ?? ''}
<strong>{readySession()?.member.displayName}</strong> roleLabel={readySession()?.member.isAdmin ? copy().adminTag : copy().residentTag}
<span>{readySession()?.member.isAdmin ? copy().adminTag : copy().residentTag}</span> statusSummary={copy().memberStatusSummary.replace(
</header>
<p>
{copy().memberStatusSummary.replace(
'{status}', '{status}',
readySession()?.member.status readySession()?.member.status
? memberStatusLabel(readySession()!.member.status) ? memberStatusLabel(readySession()!.member.status)
: copy().memberStatusActive : copy().memberStatusActive
)} )}
</p> modeBadge={readySession()?.mode === 'demo' ? copy().demoBadge : copy().liveBadge}
<div class="ledger-compact-card__meta"> localeBadge={locale().toUpperCase()}
<span class="mini-chip"> />
{readySession()?.mode === 'demo' ? copy().demoBadge : copy().liveBadge}
</span>
<span class="mini-chip mini-chip--muted">{locale().toUpperCase()}</span>
</div>
</article>
<div class="content-stack">{renderPanel()}</div> <div class="content-stack">{renderPanel()}</div>
</section> </section>
<Modal <Modal

View File

@@ -0,0 +1,10 @@
import { QueryClient } from '@tanstack/solid-query'
export const miniAppQueryClient = new QueryClient({
defaultOptions: {
queries: {
retry: 1,
staleTime: 30_000
}
}
})

View File

@@ -0,0 +1,53 @@
import { For } from 'solid-js'
import type { MiniAppDashboard } from '../../miniapp-api'
import { StatCard } from '../ui'
type SummaryItem = {
label: string
value: string
}
type Props = {
dashboard: MiniAppDashboard
utilityTotalMajor: string
purchaseTotalMajor: string
labels: {
remaining: string
rent: string
utilities: string
purchases: string
}
}
export function FinanceSummaryCards(props: Props) {
const items: SummaryItem[] = [
{
label: props.labels.remaining,
value: `${props.dashboard.totalRemainingMajor} ${props.dashboard.currency}`
},
{
label: props.labels.rent,
value: `${props.dashboard.rentDisplayAmountMajor} ${props.dashboard.currency}`
},
{
label: props.labels.utilities,
value: `${props.utilityTotalMajor} ${props.dashboard.currency}`
},
{
label: props.labels.purchases,
value: `${props.purchaseTotalMajor} ${props.dashboard.currency}`
}
]
return (
<For each={items}>
{(item) => (
<StatCard>
<span>{item.label}</span>
<strong>{item.value}</strong>
</StatCard>
)}
</For>
)
}

View File

@@ -0,0 +1,163 @@
import { For, Match, Switch } from 'solid-js'
import type { MiniAppDashboard } from '../../miniapp-api'
type MemberVisual = {
member: MiniAppDashboard['members'][number]
totalMinor: bigint
barWidthPercent: number
segments: {
key: string
label: string
amountMajor: string
amountMinor: bigint
widthPercent: number
}[]
}
type PurchaseSlice = {
key: string
label: string
amountMajor: string
color: string
percentage: number
dasharray: string
dashoffset: string
}
type Props = {
dashboard: MiniAppDashboard
memberVisuals: readonly MemberVisual[]
purchaseChart: {
totalMajor: string
slices: readonly PurchaseSlice[]
}
labels: {
financeVisualsTitle: string
financeVisualsBody: string
membersCount: string
purchaseInvestmentsTitle: string
purchaseInvestmentsBody: string
purchaseInvestmentsEmpty: string
purchaseTotalLabel: string
purchaseShareLabel: string
}
remainingClass: (member: MiniAppDashboard['members'][number]) => string
}
export function FinanceVisuals(props: Props) {
return (
<>
<article class="balance-item balance-item--wide">
<header>
<strong>{props.labels.financeVisualsTitle}</strong>
<span>
{props.labels.membersCount}: {String(props.dashboard.members.length)}
</span>
</header>
<p>{props.labels.financeVisualsBody}</p>
<div class="member-visual-list">
<For each={props.memberVisuals}>
{(item) => (
<article class="member-visual-card">
<header>
<strong>{item.member.displayName}</strong>
<span class={`balance-status ${props.remainingClass(item.member)}`}>
{item.member.remainingMajor} {props.dashboard.currency}
</span>
</header>
<div class="member-visual-bar">
<div
class="member-visual-bar__track"
style={{ width: `${item.barWidthPercent}%` }}
>
<For each={item.segments}>
{(segment) => (
<span
class={`member-visual-bar__segment member-visual-bar__segment--${segment.key}`}
style={{ width: `${segment.widthPercent}%` }}
/>
)}
</For>
</div>
</div>
<div class="member-visual-meta">
<For each={item.segments}>
{(segment) => (
<span class={`member-visual-chip member-visual-chip--${segment.key}`}>
{segment.label}: {segment.amountMajor} {props.dashboard.currency}
</span>
)}
</For>
</div>
</article>
)}
</For>
</div>
</article>
<article class="balance-item balance-item--wide">
<header>
<strong>{props.labels.purchaseInvestmentsTitle}</strong>
<span>
{props.labels.purchaseTotalLabel}: {props.purchaseChart.totalMajor}{' '}
{props.dashboard.currency}
</span>
</header>
<p>{props.labels.purchaseInvestmentsBody}</p>
<Switch>
<Match when={props.purchaseChart.slices.length === 0}>
<p>{props.labels.purchaseInvestmentsEmpty}</p>
</Match>
<Match when={props.purchaseChart.slices.length > 0}>
<div class="purchase-chart">
<div class="purchase-chart__figure">
<svg class="purchase-chart__donut" viewBox="0 0 120 120" aria-hidden="true">
<circle class="purchase-chart__ring" cx="60" cy="60" r="42" />
<For each={props.purchaseChart.slices}>
{(slice) => (
<circle
class="purchase-chart__slice"
cx="60"
cy="60"
r="42"
stroke={slice.color}
stroke-dasharray={slice.dasharray}
stroke-dashoffset={slice.dashoffset}
/>
)}
</For>
</svg>
<div class="purchase-chart__center">
<span>{props.labels.purchaseTotalLabel}</span>
<strong>
{props.purchaseChart.totalMajor} {props.dashboard.currency}
</strong>
</div>
</div>
<div class="purchase-chart__legend">
<For each={props.purchaseChart.slices}>
{(slice) => (
<article class="purchase-chart__legend-item">
<div>
<span
class="purchase-chart__legend-swatch"
style={{ 'background-color': slice.color }}
/>
<strong>{slice.label}</strong>
</div>
<p>
{slice.amountMajor} {props.dashboard.currency} ·{' '}
{props.labels.purchaseShareLabel} {slice.percentage}%
</p>
</article>
)}
</For>
</div>
</div>
</Match>
</Switch>
</article>
</>
)
}

View File

@@ -0,0 +1,38 @@
import { Show, type JSX } from 'solid-js'
import { Button, MiniChip } from '../ui'
type Props = {
badges: readonly string[]
title: string
body: string
action?:
| {
label: string
onClick: () => void
}
| undefined
}
export function HeroBanner(props: Props): JSX.Element {
return (
<section class="hero-card">
<div class="hero-card__meta">
{props.badges.map((badge, index) => (
<MiniChip muted={index > 0}>{badge}</MiniChip>
))}
</div>
<h2>{props.title}</h2>
<p>{props.body}</p>
<Show when={props.action}>
{(action) => (
<div class="panel-toolbar">
<Button variant="secondary" onClick={() => action().onClick()}>
{action().label}
</Button>
</div>
)}
</Show>
</section>
)
}

View File

@@ -0,0 +1,28 @@
import type { JSX } from 'solid-js'
type TabItem<T extends string> = {
key: T
label: string
}
type Props<T extends string> = {
items: readonly TabItem<T>[]
active: T
onChange: (key: T) => void
}
export function NavigationTabs<T extends string>(props: Props<T>): JSX.Element {
return (
<nav class="nav-grid">
{props.items.map((item) => (
<button
classList={{ 'is-active': props.active === item.key }}
type="button"
onClick={() => props.onChange(item.key)}
>
{item.label}
</button>
))}
</nav>
)
}

View File

@@ -0,0 +1,25 @@
import { Card, MiniChip } from '../ui'
type Props = {
displayName: string
roleLabel: string
statusSummary: string
modeBadge: string
localeBadge: string
}
export function ProfileCard(props: Props) {
return (
<Card class="profile-card" accent>
<header>
<strong>{props.displayName}</strong>
<span>{props.roleLabel}</span>
</header>
<p>{props.statusSummary}</p>
<div class="ledger-compact-card__meta">
<MiniChip>{props.modeBadge}</MiniChip>
<MiniChip muted>{props.localeBadge}</MiniChip>
</div>
</Card>
)
}

View File

@@ -0,0 +1,43 @@
import type { Locale } from '../../i18n'
type Props = {
subtitle: string
title: string
languageLabel: string
locale: Locale
saving: boolean
onChange: (locale: Locale) => void
}
export function TopBar(props: Props) {
return (
<section class="topbar">
<div>
<p class="eyebrow">{props.subtitle}</p>
<h1>{props.title}</h1>
</div>
<label class="locale-switch">
<span>{props.languageLabel}</span>
<div class="locale-switch__buttons">
<button
classList={{ 'is-active': props.locale === 'en' }}
type="button"
disabled={props.saving}
onClick={() => props.onChange('en')}
>
EN
</button>
<button
classList={{ 'is-active': props.locale === 'ru' }}
type="button"
disabled={props.saving}
onClick={() => props.onChange('ru')}
>
RU
</button>
</div>
</label>
</section>
)
}

View File

@@ -1,119 +0,0 @@
import { Show, createEffect, onCleanup, type JSX, type ParentProps } from 'solid-js'
type ButtonVariant = 'primary' | 'secondary' | 'danger' | 'ghost' | 'icon'
export function Button(
props: ParentProps<{
type?: 'button' | 'submit' | 'reset'
variant?: ButtonVariant
class?: string
disabled?: boolean
onClick?: JSX.EventHandlerUnion<HTMLButtonElement, MouseEvent>
}>
) {
return (
<button
type={props.type ?? 'button'}
class={`ui-button ui-button--${props.variant ?? 'secondary'} ${props.class ?? ''}`.trim()}
disabled={props.disabled}
onClick={props.onClick}
>
{props.children}
</button>
)
}
export function IconButton(
props: ParentProps<{
label: string
class?: string
disabled?: boolean
onClick?: JSX.EventHandlerUnion<HTMLButtonElement, MouseEvent>
}>
) {
const maybeClass = props.class ? { class: props.class } : {}
const maybeDisabled = props.disabled !== undefined ? { disabled: props.disabled } : {}
const maybeOnClick = props.onClick ? { onClick: props.onClick } : {}
return (
<Button variant="icon" {...maybeClass} {...maybeDisabled} {...maybeOnClick}>
<span aria-hidden="true">{props.children}</span>
<span class="sr-only">{props.label}</span>
</Button>
)
}
export function Field(
props: ParentProps<{
label: string
hint?: string
wide?: boolean
class?: string
}>
) {
return (
<label
class={`settings-field ${props.wide ? 'settings-field--wide' : ''} ${props.class ?? ''}`.trim()}
>
<span>{props.label}</span>
{props.children}
<Show when={props.hint}>{(hint) => <small>{hint()}</small>}</Show>
</label>
)
}
export function Modal(
props: ParentProps<{
open: boolean
title: string
description?: string
closeLabel: string
footer?: JSX.Element
onClose: () => void
}>
) {
createEffect(() => {
if (!props.open) {
return
}
const onKeyDown = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
props.onClose()
}
}
window.addEventListener('keydown', onKeyDown)
onCleanup(() => window.removeEventListener('keydown', onKeyDown))
})
return (
<Show when={props.open}>
<div class="modal-backdrop" onClick={() => props.onClose()}>
<section
class="modal-sheet"
role="dialog"
aria-modal="true"
aria-label={props.title}
onClick={(event) => event.stopPropagation()}
>
<header class="modal-sheet__header">
<div>
<h3>{props.title}</h3>
<Show when={props.description}>{(description) => <p>{description()}</p>}</Show>
</div>
<IconButton label={props.closeLabel} onClick={() => props.onClose()}>
x
</IconButton>
</header>
<div class="modal-sheet__body">{props.children}</div>
<Show when={props.footer}>
{(footer) => <footer class="modal-sheet__footer">{footer()}</footer>}
</Show>
</section>
</div>
</Show>
)
}

View File

@@ -0,0 +1,60 @@
import { cva, type VariantProps } from 'class-variance-authority'
import type { JSX, ParentProps } from 'solid-js'
import { cn } from '../../lib/cn'
const buttonVariants = cva('ui-button', {
variants: {
variant: {
primary: 'ui-button--primary',
secondary: 'ui-button--secondary',
danger: 'ui-button--danger',
ghost: 'ui-button--ghost',
icon: 'ui-button--icon'
}
},
defaultVariants: {
variant: 'secondary'
}
})
type ButtonProps = ParentProps<{
type?: 'button' | 'submit' | 'reset'
class?: string
disabled?: boolean
onClick?: JSX.EventHandlerUnion<HTMLButtonElement, MouseEvent>
}> &
VariantProps<typeof buttonVariants>
export function Button(props: ButtonProps) {
return (
<button
type={props.type ?? 'button'}
class={cn(buttonVariants({ variant: props.variant }), props.class)}
disabled={props.disabled}
onClick={props.onClick}
>
{props.children}
</button>
)
}
export function IconButton(
props: ParentProps<{
label: string
class?: string
disabled?: boolean
onClick?: JSX.EventHandlerUnion<HTMLButtonElement, MouseEvent>
}>
) {
const maybeClass = props.class ? { class: props.class } : {}
const maybeDisabled = props.disabled !== undefined ? { disabled: props.disabled } : {}
const maybeOnClick = props.onClick ? { onClick: props.onClick } : {}
return (
<Button variant="icon" {...maybeClass} {...maybeDisabled} {...maybeOnClick}>
<span aria-hidden="true">{props.children}</span>
<span class="sr-only">{props.label}</span>
</Button>
)
}

View File

@@ -0,0 +1,23 @@
import type { ParentProps } from 'solid-js'
import { cn } from '../../lib/cn'
export function Card(props: ParentProps<{ class?: string; accent?: boolean }>) {
return (
<article class={cn('balance-item', props.accent && 'balance-item--accent', props.class)}>
{props.children}
</article>
)
}
export function StatCard(props: ParentProps<{ class?: string }>) {
return <article class={cn('stat-card', props.class)}>{props.children}</article>
}
export function MiniChip(props: ParentProps<{ muted?: boolean; class?: string }>) {
return (
<span class={cn('mini-chip', props.muted && 'mini-chip--muted', props.class)}>
{props.children}
</span>
)
}

View File

@@ -0,0 +1,41 @@
import * as Dialog from '@kobalte/core/dialog'
import { Show, type JSX, type ParentProps } from 'solid-js'
export function Modal(
props: ParentProps<{
open: boolean
title: string
description?: string
closeLabel: string
footer?: JSX.Element
onClose: () => void
}>
) {
return (
<Dialog.Root open={props.open} onOpenChange={(open) => !open && props.onClose()}>
<Dialog.Portal>
<Dialog.Overlay class="modal-backdrop" />
<div class="modal-backdrop">
<Dialog.Content class="modal-sheet" aria-label={props.title}>
<header class="modal-sheet__header">
<div>
<Dialog.Title>{props.title}</Dialog.Title>
<Show when={props.description}>
{(description) => <Dialog.Description>{description()}</Dialog.Description>}
</Show>
</div>
<Dialog.CloseButton class="ui-button ui-button--icon">
<span aria-hidden="true">x</span>
<span class="sr-only">{props.closeLabel}</span>
</Dialog.CloseButton>
</header>
<div class="modal-sheet__body">{props.children}</div>
<Show when={props.footer}>
{(footer) => <footer class="modal-sheet__footer">{footer()}</footer>}
</Show>
</Dialog.Content>
</div>
</Dialog.Portal>
</Dialog.Root>
)
}

View File

@@ -0,0 +1,20 @@
import { Show, type ParentProps } from 'solid-js'
import { cn } from '../../lib/cn'
export function Field(
props: ParentProps<{
label: string
hint?: string
wide?: boolean
class?: string
}>
) {
return (
<label class={cn('settings-field', props.wide && 'settings-field--wide', props.class)}>
<span>{props.label}</span>
{props.children}
<Show when={props.hint}>{(hint) => <small>{hint()}</small>}</Show>
</label>
)
}

View File

@@ -0,0 +1,4 @@
export * from './button'
export * from './card'
export * from './dialog'
export * from './field'

View File

@@ -0,0 +1,285 @@
import type {
MiniAppAdminCycleState,
MiniAppAdminSettingsPayload,
MiniAppDashboard,
MiniAppPendingMember,
MiniAppSession
} from '../miniapp-api'
export const demoMember: NonNullable<MiniAppSession['member']> = {
id: 'demo-member',
householdId: 'demo-household',
displayName: 'Stas',
status: 'active',
isAdmin: true,
preferredLocale: 'en',
householdDefaultLocale: 'en'
}
export const demoTelegramUser: NonNullable<MiniAppSession['telegramUser']> = {
firstName: 'Stas',
username: 'stas_demo',
languageCode: 'en'
}
export const demoDashboard: MiniAppDashboard = {
period: '2026-03',
currency: 'GEL',
totalDueMajor: '2410.00',
totalPaidMajor: '650.00',
totalRemainingMajor: '1760.00',
rentSourceAmountMajor: '875.00',
rentSourceCurrency: 'USD',
rentDisplayAmountMajor: '2415.00',
rentFxRateMicros: '2760000',
rentFxEffectiveDate: '2026-03-17',
members: [
{
memberId: 'demo-member',
displayName: 'Stas',
rentShareMajor: '603.75',
utilityShareMajor: '78.00',
purchaseOffsetMajor: '-66.00',
netDueMajor: '615.75',
paidMajor: '615.75',
remainingMajor: '0.00',
explanations: ['Weighted rent share', 'Custom purchase split credit']
},
{
memberId: 'member-chorb',
displayName: 'Chorbanaut',
rentShareMajor: '603.75',
utilityShareMajor: '78.00',
purchaseOffsetMajor: '12.00',
netDueMajor: '693.75',
paidMajor: '0.00',
remainingMajor: '693.75',
explanations: ['Standard resident share']
},
{
memberId: 'member-el',
displayName: 'El',
rentShareMajor: '1207.50',
utilityShareMajor: '0.00',
purchaseOffsetMajor: '54.00',
netDueMajor: '1261.50',
paidMajor: '34.25',
remainingMajor: '1227.25',
explanations: ['Away policy applied to utilities']
}
],
ledger: [
{
id: 'purchase-1',
kind: 'purchase',
title: 'Bought kitchen towels',
memberId: 'demo-member',
paymentKind: null,
amountMajor: '24.00',
currency: 'GEL',
displayAmountMajor: '24.00',
displayCurrency: 'GEL',
fxRateMicros: null,
fxEffectiveDate: null,
actorDisplayName: 'Stas',
occurredAt: '2026-03-04T11:00:00.000Z',
purchaseSplitMode: 'equal',
purchaseParticipants: [
{ memberId: 'demo-member', included: true, shareAmountMajor: null },
{ memberId: 'member-chorb', included: true, shareAmountMajor: null },
{ memberId: 'member-el', included: false, shareAmountMajor: null }
]
},
{
id: 'purchase-2',
kind: 'purchase',
title: 'Electric kettle',
memberId: 'member-chorb',
paymentKind: null,
amountMajor: '96.00',
currency: 'GEL',
displayAmountMajor: '96.00',
displayCurrency: 'GEL',
fxRateMicros: null,
fxEffectiveDate: null,
actorDisplayName: 'Chorbanaut',
occurredAt: '2026-03-08T16:20:00.000Z',
purchaseSplitMode: 'custom_amounts',
purchaseParticipants: [
{ memberId: 'demo-member', included: true, shareAmountMajor: '42.00' },
{ memberId: 'member-chorb', included: true, shareAmountMajor: '24.00' },
{ memberId: 'member-el', included: true, shareAmountMajor: '30.00' }
]
},
{
id: 'utility-1',
kind: 'utility',
title: 'Electricity',
memberId: null,
paymentKind: null,
amountMajor: '154.00',
currency: 'GEL',
displayAmountMajor: '154.00',
displayCurrency: 'GEL',
fxRateMicros: null,
fxEffectiveDate: null,
actorDisplayName: 'Stas',
occurredAt: '2026-03-09T12:00:00.000Z'
},
{
id: 'utility-2',
kind: 'utility',
title: 'Internet',
memberId: null,
paymentKind: null,
amountMajor: '80.00',
currency: 'GEL',
displayAmountMajor: '80.00',
displayCurrency: 'GEL',
fxRateMicros: null,
fxEffectiveDate: null,
actorDisplayName: 'Stas',
occurredAt: '2026-03-10T10:30:00.000Z'
},
{
id: 'payment-1',
kind: 'payment',
title: 'rent',
memberId: 'demo-member',
paymentKind: 'rent',
amountMajor: '615.75',
currency: 'GEL',
displayAmountMajor: '615.75',
displayCurrency: 'GEL',
fxRateMicros: null,
fxEffectiveDate: null,
actorDisplayName: 'Stas',
occurredAt: '2026-03-11T18:10:00.000Z'
},
{
id: 'payment-2',
kind: 'payment',
title: 'utilities',
memberId: 'member-el',
paymentKind: 'utilities',
amountMajor: '34.25',
currency: 'GEL',
displayAmountMajor: '34.25',
displayCurrency: 'GEL',
fxRateMicros: null,
fxEffectiveDate: null,
actorDisplayName: 'El',
occurredAt: '2026-03-13T09:00:00.000Z'
}
]
}
export const demoPendingMembers: readonly MiniAppPendingMember[] = [
{
telegramUserId: '555777',
displayName: 'Mia',
username: 'mia',
languageCode: 'ru'
},
{
telegramUserId: '777999',
displayName: 'Dima',
username: 'dima',
languageCode: 'en'
}
]
export const demoAdminSettings: MiniAppAdminSettingsPayload = {
settings: {
householdId: 'demo-household',
settlementCurrency: 'GEL',
paymentBalanceAdjustmentPolicy: 'utilities',
rentAmountMinor: '241500',
rentCurrency: 'USD',
rentDueDay: 20,
rentWarningDay: 17,
utilitiesDueDay: 4,
utilitiesReminderDay: 3,
timezone: 'Asia/Tbilisi'
},
topics: [
{ role: 'purchase', telegramThreadId: '101', topicName: 'Purchases' },
{ role: 'feedback', telegramThreadId: '102', topicName: 'Anonymous feedback' },
{ role: 'reminders', telegramThreadId: '103', topicName: 'Reminders' },
{ role: 'payments', telegramThreadId: '104', topicName: 'Payments' }
],
categories: [
{
id: 'cat-electricity',
householdId: 'demo-household',
slug: 'electricity',
name: 'Electricity',
sortOrder: 0,
isActive: true
},
{
id: 'cat-internet',
householdId: 'demo-household',
slug: 'internet',
name: 'Internet',
sortOrder: 1,
isActive: true
},
{
id: 'cat-gas',
householdId: 'demo-household',
slug: 'gas',
name: 'Gas',
sortOrder: 2,
isActive: false
}
],
members: [
{ id: 'demo-member', displayName: 'Stas', status: 'active', rentShareWeight: 1, isAdmin: true },
{
id: 'member-chorb',
displayName: 'Chorbanaut',
status: 'active',
rentShareWeight: 1,
isAdmin: false
},
{ id: 'member-el', displayName: 'El', status: 'away', rentShareWeight: 2, isAdmin: false }
],
memberAbsencePolicies: [
{
memberId: 'member-el',
effectiveFromPeriod: '2026-03',
policy: 'away_rent_only'
}
]
}
export const demoCycleState: MiniAppAdminCycleState = {
cycle: {
id: 'cycle-demo-2026-03',
period: '2026-03',
currency: 'GEL'
},
rentRule: {
amountMinor: '241500',
currency: 'USD'
},
utilityBills: [
{
id: 'utility-bill-1',
billName: 'Electricity',
amountMinor: '15400',
currency: 'GEL',
createdByMemberId: 'demo-member',
createdAt: '2026-03-09T12:00:00.000Z'
},
{
id: 'utility-bill-2',
billName: 'Internet',
amountMinor: '8000',
currency: 'GEL',
createdByMemberId: 'demo-member',
createdAt: '2026-03-10T10:30:00.000Z'
}
]
}

View File

@@ -1,6 +1,8 @@
/* @refresh reload */ /* @refresh reload */
import { QueryClientProvider } from '@tanstack/solid-query'
import { render } from 'solid-js/web' import { render } from 'solid-js/web'
import { miniAppQueryClient } from './app/query-client'
import './index.css' import './index.css'
import App from './App' import App from './App'
@@ -10,4 +12,11 @@ if (!root) {
throw new Error('Root element not found') throw new Error('Root element not found')
} }
render(() => <App />, root) render(
() => (
<QueryClientProvider client={miniAppQueryClient}>
<App />
</QueryClientProvider>
),
root
)

View File

@@ -0,0 +1,5 @@
import { clsx, type ClassValue } from 'clsx'
export function cn(...inputs: ClassValue[]) {
return clsx(inputs)
}

View File

@@ -30,8 +30,13 @@
"apps/miniapp": { "apps/miniapp": {
"name": "@household/miniapp", "name": "@household/miniapp",
"dependencies": { "dependencies": {
"@kobalte/core": "0.13.11",
"@tanstack/solid-query": "5.90.23",
"@twa-dev/sdk": "8.0.2", "@twa-dev/sdk": "8.0.2",
"class-variance-authority": "0.7.1",
"clsx": "2.1.1",
"solid-js": "^1.9.9", "solid-js": "^1.9.9",
"zod": "4.3.6",
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/vite": "^4.1.13", "@tailwindcss/vite": "^4.1.13",
@@ -141,6 +146,8 @@
"@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
"@corvu/utils": ["@corvu/utils@0.4.2", "", { "dependencies": { "@floating-ui/dom": "^1.6.11" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-Ox2kYyxy7NoXdKWdHeDEjZxClwzO4SKM8plAaVwmAJPxHMqA0rLOoAsa+hBDwRLpctf+ZRnAd/ykguuJidnaTA=="],
"@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="], "@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="],
"@esbuild-kit/core-utils": ["@esbuild-kit/core-utils@3.3.2", "", { "dependencies": { "esbuild": "~0.18.20", "source-map-support": "^0.5.21" } }, "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ=="], "@esbuild-kit/core-utils": ["@esbuild-kit/core-utils@3.3.2", "", { "dependencies": { "esbuild": "~0.18.20", "source-map-support": "^0.5.21" } }, "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ=="],
@@ -199,6 +206,12 @@
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="],
"@floating-ui/core": ["@floating-ui/core@1.7.5", "", { "dependencies": { "@floating-ui/utils": "^0.2.11" } }, "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ=="],
"@floating-ui/dom": ["@floating-ui/dom@1.7.6", "", { "dependencies": { "@floating-ui/core": "^1.7.5", "@floating-ui/utils": "^0.2.11" } }, "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ=="],
"@floating-ui/utils": ["@floating-ui/utils@0.2.11", "", {}, "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg=="],
"@grammyjs/types": ["@grammyjs/types@3.25.0", "", {}, "sha512-iN9i5p+8ZOu9OMxWNcguojQfz4K/PDyMPOnL7PPCON+SoA/F8OKMH3uR7CVUkYfdNe0GCz8QOzAWrnqusQYFOg=="], "@grammyjs/types": ["@grammyjs/types@3.25.0", "", {}, "sha512-iN9i5p+8ZOu9OMxWNcguojQfz4K/PDyMPOnL7PPCON+SoA/F8OKMH3uR7CVUkYfdNe0GCz8QOzAWrnqusQYFOg=="],
"@household/adapters-db": ["@household/adapters-db@workspace:packages/adapters-db"], "@household/adapters-db": ["@household/adapters-db@workspace:packages/adapters-db"],
@@ -223,6 +236,10 @@
"@household/scripts": ["@household/scripts@workspace:scripts"], "@household/scripts": ["@household/scripts@workspace:scripts"],
"@internationalized/date": ["@internationalized/date@3.12.0", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-/PyIMzK29jtXaGU23qTvNZxvBXRtKbNnGDFD+PY6CZw/Y8Ex8pFUzkuCJCG9aOqmShjqhS9mPqP6Dk5onQY8rQ=="],
"@internationalized/number": ["@internationalized/number@3.6.5", "", { "dependencies": { "@swc/helpers": "^0.5.0" } }, "sha512-6hY4Kl4HPBvtfS62asS/R22JzNNy8vi/Ssev7x6EobfCp+9QIB2hKvI2EtbdJ0VSQacxVNtqhE/NmF/NZ0gm6g=="],
"@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="],
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
@@ -237,6 +254,10 @@
"@js-temporal/polyfill": ["@js-temporal/polyfill@0.5.1", "", { "dependencies": { "jsbi": "^4.3.0" } }, "sha512-hloP58zRVCRSpgDxmqCWJNlizAlUgJFqG2ypq79DCvyv9tHjRYMDOcPFjzfl/A1/YxDvRCZz8wvZvmapQnKwFQ=="], "@js-temporal/polyfill": ["@js-temporal/polyfill@0.5.1", "", { "dependencies": { "jsbi": "^4.3.0" } }, "sha512-hloP58zRVCRSpgDxmqCWJNlizAlUgJFqG2ypq79DCvyv9tHjRYMDOcPFjzfl/A1/YxDvRCZz8wvZvmapQnKwFQ=="],
"@kobalte/core": ["@kobalte/core@0.13.11", "", { "dependencies": { "@floating-ui/dom": "^1.5.1", "@internationalized/date": "^3.4.0", "@internationalized/number": "^3.2.1", "@kobalte/utils": "^0.9.1", "@solid-primitives/props": "^3.1.8", "@solid-primitives/resize-observer": "^2.0.26", "solid-presence": "^0.1.8", "solid-prevent-scroll": "^0.1.4" }, "peerDependencies": { "solid-js": "^1.8.15" } }, "sha512-hK7TYpdib/XDb/r/4XDBFaO9O+3ZHz4ZWryV4/3BfES+tSQVgg2IJupDnztKXB0BqbSRy/aWlHKw1SPtNPYCFQ=="],
"@kobalte/utils": ["@kobalte/utils@0.9.1", "", { "dependencies": { "@solid-primitives/event-listener": "^2.2.14", "@solid-primitives/keyed": "^1.2.0", "@solid-primitives/map": "^0.4.7", "@solid-primitives/media": "^2.2.4", "@solid-primitives/props": "^3.1.8", "@solid-primitives/refs": "^1.0.5", "@solid-primitives/utils": "^6.2.1" }, "peerDependencies": { "solid-js": "^1.8.8" } }, "sha512-eeU60A3kprIiBDAfv9gUJX1tXGLuZiKMajUfSQURAF2pk4ZoMYiqIzmrMBvzcxP39xnYttgTyQEVLwiTZnrV4w=="],
"@nothing-but/utils": ["@nothing-but/utils@0.17.0", "", {}, "sha512-TuCHcHLOqDL0SnaAxACfuRHBNRgNJcNn9X0GiH5H3YSDBVquCr3qEIG3FOQAuMyZCbu9w8nk2CHhOsn7IvhIwQ=="], "@nothing-but/utils": ["@nothing-but/utils@0.17.0", "", {}, "sha512-TuCHcHLOqDL0SnaAxACfuRHBNRgNJcNn9X0GiH5H3YSDBVquCr3qEIG3FOQAuMyZCbu9w8nk2CHhOsn7IvhIwQ=="],
"@oxlint/binding-android-arm-eabi": ["@oxlint/binding-android-arm-eabi@1.51.0", "", { "os": "android", "cpu": "arm" }, "sha512-jJYIqbx4sX+suIxWstc4P7SzhEwb4ArWA2KVrmEuu9vH2i0qM6QIHz/ehmbGE4/2fZbpuMuBzTl7UkfNoqiSgw=="], "@oxlint/binding-android-arm-eabi": ["@oxlint/binding-android-arm-eabi@1.51.0", "", { "os": "android", "cpu": "arm" }, "sha512-jJYIqbx4sX+suIxWstc4P7SzhEwb4ArWA2KVrmEuu9vH2i0qM6QIHz/ehmbGE4/2fZbpuMuBzTl7UkfNoqiSgw=="],
@@ -341,8 +362,14 @@
"@solid-primitives/keyboard": ["@solid-primitives/keyboard@1.3.5", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.5", "@solid-primitives/rootless": "^1.5.3", "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-sav+l+PL+74z3yaftVs7qd8c2SXkqzuxPOVibUe5wYMt+U5Hxp3V3XCPgBPN2I6cANjvoFtz0NiU8uHVLdi9FQ=="], "@solid-primitives/keyboard": ["@solid-primitives/keyboard@1.3.5", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.5", "@solid-primitives/rootless": "^1.5.3", "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-sav+l+PL+74z3yaftVs7qd8c2SXkqzuxPOVibUe5wYMt+U5Hxp3V3XCPgBPN2I6cANjvoFtz0NiU8uHVLdi9FQ=="],
"@solid-primitives/keyed": ["@solid-primitives/keyed@1.5.3", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-zNadtyYBhJSOjXtogkGHmRxjGdz9KHc8sGGVAGlUABkE8BED2tbIZoxkwSqzOwde8OcUEH0bb5DLZUWIMvyBSA=="],
"@solid-primitives/map": ["@solid-primitives/map@0.4.13", "", { "dependencies": { "@solid-primitives/trigger": "^1.1.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-B1zyFbsiTQvqPr+cuPCXO72sRuczG9Swncqk5P74NCGw1VE8qa/Ry9GlfI1e/VdeQYHjan+XkbE3rO2GW/qKew=="],
"@solid-primitives/media": ["@solid-primitives/media@2.3.5", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.5", "@solid-primitives/rootless": "^1.5.3", "@solid-primitives/static-store": "^0.1.3", "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-LX9fB5WDaK87FMDtUB1qokBOfT2et9Uobv/zZaKLH9caFSz4+P70MBKEIBHcZQy+9MV5M2XvGYLTbLskjkzMjA=="], "@solid-primitives/media": ["@solid-primitives/media@2.3.5", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.5", "@solid-primitives/rootless": "^1.5.3", "@solid-primitives/static-store": "^0.1.3", "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-LX9fB5WDaK87FMDtUB1qokBOfT2et9Uobv/zZaKLH9caFSz4+P70MBKEIBHcZQy+9MV5M2XvGYLTbLskjkzMjA=="],
"@solid-primitives/props": ["@solid-primitives/props@3.2.3", "", { "dependencies": { "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-XzG6en9gSFwmvbKcATm2BxL63HegZ+BAG5fmHi8jyBppQHcaths7ffz+6vYvwYy3nlgLa20ufJLj7tst+PcHFA=="],
"@solid-primitives/refs": ["@solid-primitives/refs@1.1.3", "", { "dependencies": { "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-aam02fjNKpBteewF/UliPSQCVJsIIGOLEWQOh+ll6R/QePzBOOBMcC4G+5jTaO75JuUS1d/14Q1YXT3X0Ow6iA=="], "@solid-primitives/refs": ["@solid-primitives/refs@1.1.3", "", { "dependencies": { "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-aam02fjNKpBteewF/UliPSQCVJsIIGOLEWQOh+ll6R/QePzBOOBMcC4G+5jTaO75JuUS1d/14Q1YXT3X0Ow6iA=="],
"@solid-primitives/resize-observer": ["@solid-primitives/resize-observer@2.1.5", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.5", "@solid-primitives/rootless": "^1.5.3", "@solid-primitives/static-store": "^0.1.3", "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-AiyTknKcNBaKHbcSMuxtSNM8FjIuiSuFyFghdD0TcCMU9hKi9EmsC5pjfjDwxE+5EueB1a+T/34PLRI5vbBbKw=="], "@solid-primitives/resize-observer": ["@solid-primitives/resize-observer@2.1.5", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.5", "@solid-primitives/rootless": "^1.5.3", "@solid-primitives/static-store": "^0.1.3", "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-AiyTknKcNBaKHbcSMuxtSNM8FjIuiSuFyFghdD0TcCMU9hKi9EmsC5pjfjDwxE+5EueB1a+T/34PLRI5vbBbKw=="],
@@ -355,8 +382,12 @@
"@solid-primitives/styles": ["@solid-primitives/styles@0.1.3", "", { "dependencies": { "@solid-primitives/rootless": "^1.5.3", "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-7YdA21prMeCX+oOF/1RAn02+cGz/pG4dyPWtHBC2H8aZvnC7IfThBt80mP+TioejrdfE7Lc54Uh18f7Pig+gRQ=="], "@solid-primitives/styles": ["@solid-primitives/styles@0.1.3", "", { "dependencies": { "@solid-primitives/rootless": "^1.5.3", "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-7YdA21prMeCX+oOF/1RAn02+cGz/pG4dyPWtHBC2H8aZvnC7IfThBt80mP+TioejrdfE7Lc54Uh18f7Pig+gRQ=="],
"@solid-primitives/trigger": ["@solid-primitives/trigger@1.2.3", "", { "dependencies": { "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-Za2JebEiDyfamjmDwRaESYqBBYOlgYGzB8kHYH0QrkXyLf2qNADlKdGN+z3vWSLCTDcKxChS43Kssjuc0OZhng=="],
"@solid-primitives/utils": ["@solid-primitives/utils@6.4.0", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-AeGTBg8Wtkh/0s+evyLtP8piQoS4wyqqQaAFs2HJcFMMjYAtUgo+ZPduRXLjPlqKVc2ejeR544oeqpbn8Egn8A=="], "@solid-primitives/utils": ["@solid-primitives/utils@6.4.0", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-AeGTBg8Wtkh/0s+evyLtP8piQoS4wyqqQaAFs2HJcFMMjYAtUgo+ZPduRXLjPlqKVc2ejeR544oeqpbn8Egn8A=="],
"@swc/helpers": ["@swc/helpers@0.5.19", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-QamiFeIK3txNjgUTNppE6MiG3p7TdninpZu0E0PbqVh1a9FNLT2FRhisaa4NcaX52XVhA5l7Pk58Ft7Sqi/2sA=="],
"@t3-oss/env-core": ["@t3-oss/env-core@0.13.10", "", { "peerDependencies": { "arktype": "^2.1.0", "typescript": ">=5.0.0", "valibot": "^1.0.0-beta.7 || ^1.0.0", "zod": "^3.24.0 || ^4.0.0" }, "optionalPeers": ["arktype", "typescript", "valibot", "zod"] }, "sha512-NNFfdlJ+HmPHkLi2HKy7nwuat9SIYOxei9K10lO2YlcSObDILY7mHZNSHsieIM3A0/5OOzw/P/b+yLvPdaG52g=="], "@t3-oss/env-core": ["@t3-oss/env-core@0.13.10", "", { "peerDependencies": { "arktype": "^2.1.0", "typescript": ">=5.0.0", "valibot": "^1.0.0-beta.7 || ^1.0.0", "zod": "^3.24.0 || ^4.0.0" }, "optionalPeers": ["arktype", "typescript", "valibot", "zod"] }, "sha512-NNFfdlJ+HmPHkLi2HKy7nwuat9SIYOxei9K10lO2YlcSObDILY7mHZNSHsieIM3A0/5OOzw/P/b+yLvPdaG52g=="],
"@tailwindcss/node": ["@tailwindcss/node@4.2.1", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.31.1", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.1" } }, "sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg=="], "@tailwindcss/node": ["@tailwindcss/node@4.2.1", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.31.1", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.1" } }, "sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg=="],
@@ -389,6 +420,10 @@
"@tailwindcss/vite": ["@tailwindcss/vite@4.2.1", "", { "dependencies": { "@tailwindcss/node": "4.2.1", "@tailwindcss/oxide": "4.2.1", "tailwindcss": "4.2.1" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-TBf2sJjYeb28jD2U/OhwdW0bbOsxkWPwQ7SrqGf9sVcoYwZj7rkXljroBO9wKBut9XnmQLXanuDUeqQK0lGg/w=="], "@tailwindcss/vite": ["@tailwindcss/vite@4.2.1", "", { "dependencies": { "@tailwindcss/node": "4.2.1", "@tailwindcss/oxide": "4.2.1", "tailwindcss": "4.2.1" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-TBf2sJjYeb28jD2U/OhwdW0bbOsxkWPwQ7SrqGf9sVcoYwZj7rkXljroBO9wKBut9XnmQLXanuDUeqQK0lGg/w=="],
"@tanstack/query-core": ["@tanstack/query-core@5.90.20", "", {}, "sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg=="],
"@tanstack/solid-query": ["@tanstack/solid-query@5.90.23", "", { "dependencies": { "@tanstack/query-core": "5.90.20" }, "peerDependencies": { "solid-js": "^1.6.0" } }, "sha512-pbZc4+Kgm7ktzIuu01R3KOWfazQKgNp4AZvW0RSvv+sNMpYoileUDAkXEcjDJe6RJmb3fVvTR4LlcSL5pxDElQ=="],
"@twa-dev/sdk": ["@twa-dev/sdk@8.0.2", "", { "dependencies": { "@twa-dev/types": "^8.0.1" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0" } }, "sha512-Pp5GxnxP2blboVZFiM9aWjs4cb8IpW3x2jP3kLOMvIqy0jzNUTuFHkwHtx+zEvh/UcF2F+wmS8G6ebIA0XPXcg=="], "@twa-dev/sdk": ["@twa-dev/sdk@8.0.2", "", { "dependencies": { "@twa-dev/types": "^8.0.1" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0" } }, "sha512-Pp5GxnxP2blboVZFiM9aWjs4cb8IpW3x2jP3kLOMvIqy0jzNUTuFHkwHtx+zEvh/UcF2F+wmS8G6ebIA0XPXcg=="],
"@twa-dev/types": ["@twa-dev/types@8.0.2", "", {}, "sha512-ICQ6n4NaUPPzV3/GzflVQS6Nnu5QX2vr9OlOG8ZkFf3rSJXzRKazrLAbZlVhCPPWkIW3MMuELPsE6tByrA49qA=="], "@twa-dev/types": ["@twa-dev/types@8.0.2", "", {}, "sha512-ICQ6n4NaUPPzV3/GzflVQS6Nnu5QX2vr9OlOG8ZkFf3rSJXzRKazrLAbZlVhCPPWkIW3MMuELPsE6tByrA49qA=="],
@@ -457,6 +492,10 @@
"caniuse-lite": ["caniuse-lite@1.0.30001776", "", {}, "sha512-sg01JDPzZ9jGshqKSckOQthXnYwOEP50jeVFhaSFbZcOy05TiuuaffDOfcwtCisJ9kNQuLBFibYywv2Bgm9osw=="], "caniuse-lite": ["caniuse-lite@1.0.30001776", "", {}, "sha512-sg01JDPzZ9jGshqKSckOQthXnYwOEP50jeVFhaSFbZcOy05TiuuaffDOfcwtCisJ9kNQuLBFibYywv2Bgm9osw=="],
"class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="],
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
@@ -681,6 +720,10 @@
"solid-js": ["solid-js@1.9.11", "", { "dependencies": { "csstype": "^3.1.0", "seroval": "~1.5.0", "seroval-plugins": "~1.5.0" } }, "sha512-WEJtcc5mkh/BnHA6Yrg4whlF8g6QwpmXXRg4P2ztPmcKeHHlH4+djYecBLhSpecZY2RRECXYUwIc/C2r3yzQ4Q=="], "solid-js": ["solid-js@1.9.11", "", { "dependencies": { "csstype": "^3.1.0", "seroval": "~1.5.0", "seroval-plugins": "~1.5.0" } }, "sha512-WEJtcc5mkh/BnHA6Yrg4whlF8g6QwpmXXRg4P2ztPmcKeHHlH4+djYecBLhSpecZY2RRECXYUwIc/C2r3yzQ4Q=="],
"solid-presence": ["solid-presence@0.1.8", "", { "dependencies": { "@corvu/utils": "~0.4.0" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-pWGtXUFWYYUZNbg5YpG5vkQJyOtzn2KXhxYaMx/4I+lylTLYkITOLevaCwMRN+liCVk0pqB6EayLWojNqBFECA=="],
"solid-prevent-scroll": ["solid-prevent-scroll@0.1.10", "", { "dependencies": { "@corvu/utils": "~0.4.1" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-KplGPX2GHiWJLZ6AXYRql4M127PdYzfwvLJJXMkO+CMb8Np4VxqDAg5S8jLdwlEuBis/ia9DKw2M8dFx5u8Mhw=="],
"solid-refresh": ["solid-refresh@0.6.3", "", { "dependencies": { "@babel/generator": "^7.23.6", "@babel/helper-module-imports": "^7.22.15", "@babel/types": "^7.23.6" }, "peerDependencies": { "solid-js": "^1.3" } }, "sha512-F3aPsX6hVw9ttm5LYlth8Q15x6MlI/J3Dn+o3EQyRTtTxidepSTwAYdozt01/YA+7ObcciagGEyXIopGZzQtbA=="], "solid-refresh": ["solid-refresh@0.6.3", "", { "dependencies": { "@babel/generator": "^7.23.6", "@babel/helper-module-imports": "^7.22.15", "@babel/types": "^7.23.6" }, "peerDependencies": { "solid-js": "^1.3" } }, "sha512-F3aPsX6hVw9ttm5LYlth8Q15x6MlI/J3Dn+o3EQyRTtTxidepSTwAYdozt01/YA+7ObcciagGEyXIopGZzQtbA=="],
"sonic-boom": ["sonic-boom@4.2.1", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q=="], "sonic-boom": ["sonic-boom@4.2.1", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q=="],
@@ -711,6 +754,8 @@
"tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],