diff --git a/apps/miniapp/package.json b/apps/miniapp/package.json index ff2d7ce..eed07c8 100644 --- a/apps/miniapp/package.json +++ b/apps/miniapp/package.json @@ -10,8 +10,13 @@ "lint": "oxlint \"src\"" }, "dependencies": { + "@kobalte/core": "0.13.11", + "@tanstack/solid-query": "5.90.23", "@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": { "@tailwindcss/vite": "^4.1.13", diff --git a/apps/miniapp/src/App.tsx b/apps/miniapp/src/App.tsx index d2f4e7d..1806e6f 100644 --- a/apps/miniapp/src/App.tsx +++ b/apps/miniapp/src/App.tsx @@ -36,6 +36,20 @@ import { type MiniAppPendingMember } from './miniapp-api' 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' type SessionState = @@ -106,19 +120,8 @@ const chartPalette = ['#f7b389', '#6fd3c0', '#f06a8d', '#94a8ff', '#f3d36f', '#7 const demoSession: Extract = { status: 'ready', mode: 'demo', - member: { - id: 'demo-member', - displayName: 'Demo Resident', - status: 'active', - isAdmin: false, - preferredLocale: 'en', - householdDefaultLocale: 'en' - }, - telegramUser: { - firstName: 'Demo', - username: 'demo_user', - languageCode: 'en' - } + member: demoMember, + telegramUser: demoTelegramUser } 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() { const fallbackLocale = detectLocale() setLocale(fallbackLocale) @@ -962,7 +1027,7 @@ function App() { const initData = webApp?.initData?.trim() if (!initData) { if (import.meta.env.DEV) { - setSession(demoSession) + applyDemoState() return } @@ -1017,99 +1082,7 @@ function App() { } } catch { if (import.meta.env.DEV) { - setDisplayNameDraft(demoSession.member.displayName) - 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' - } - ]) + applyDemoState() return } @@ -1874,142 +1847,6 @@ function App() { })) } - function renderFinanceSummaryCards(data: MiniAppDashboard): JSX.Element { - return ( - <> -
- {copy().remainingLabel} - - {data.totalRemainingMajor} {data.currency} - -
-
- {copy().shareRent} - - {data.rentDisplayAmountMajor} {data.currency} - -
-
- {copy().shareUtilities} - - {utilityTotalMajor()} {data.currency} - -
-
- {copy().purchasesTitle} - - {purchaseTotalMajor()} {data.currency} - -
- - ) - } - - function renderFinanceVisuals(data: MiniAppDashboard): JSX.Element { - const purchaseChart = purchaseInvestmentChart() - - return ( - <> -
-
- {copy().financeVisualsTitle} - - {copy().membersCount}: {String(data.members.length)} - -
-

{copy().financeVisualsBody}

-
- {memberBalanceVisuals().map((item) => ( -
-
- {item.member.displayName} - - {item.member.remainingMajor} {data.currency} - -
-
-
- {item.segments.map((segment) => ( - - ))} -
-
-
- {item.segments.map((segment) => ( - - {segment.label}: {segment.amountMajor} {data.currency} - - ))} -
-
- ))} -
-
- -
-
- {copy().purchaseInvestmentsTitle} - - {copy().purchaseTotalLabel}: {purchaseChart.totalMajor} {data.currency} - -
-

{copy().purchaseInvestmentsBody}

- {purchaseChart.slices.length === 0 ? ( -

{copy().purchaseInvestmentsEmpty}

- ) : ( -
-
- -
- {copy().purchaseTotalLabel} - - {purchaseChart.totalMajor} {data.currency} - -
-
-
- {purchaseChart.slices.map((slice) => ( -
-
- - {slice.label} -
-

- {slice.amountMajor} {data.currency} · {copy().purchaseShareLabel}{' '} - {slice.percentage}% -

-
- ))} -
-
- )} -
- - ) - } - const renderPanel = () => { switch (activeNav()) { case 'balances': @@ -2063,8 +1900,35 @@ function App() { ) : null} -
{renderFinanceSummaryCards(data)}
- {renderFinanceVisuals(data)} +
+ +
+
{copy().householdBalancesTitle} @@ -3739,7 +3603,19 @@ function App() {
} - render={(data) => renderFinanceSummaryCards(data)} + render={(data) => ( + + )} /> {readySession()?.member.isAdmin ? (
@@ -3814,7 +3690,24 @@ function App() { renderFinanceVisuals(data)} + render={(data) => ( + + )} />
@@ -3856,66 +3749,46 @@ function App() {
-
-
-

{copy().appSubtitle}

-

{copy().appTitle}

-
- - -
+ void handleMemberLocaleChange(nextLocale)} + /> -
- {copy().loadingBadge} -

{copy().loadingTitle}

-

{copy().loadingBody}

-
+
-
- {copy().loadingBadge} -

- {blockedSession()?.reason === 'telegram_only' + -

- {blockedSession()?.reason === 'telegram_only' + : copy().unexpectedErrorTitle + } + body={ + blockedSession()?.reason === 'telegram_only' ? copy().telegramOnlyBody - : copy().unexpectedErrorBody} -

- -

+ : copy().unexpectedErrorBody + } + action={{ label: copy().reload, onClick: () => window.location.reload() }} + />
- {copy().loadingBadge} +
+ {copy().loadingBadge} +

{onboardingSession()?.mode === 'pending' ? copy().pendingTitle @@ -3938,18 +3811,13 @@ function App() {

-
-
- - {readySession()?.mode === 'demo' ? copy().demoBadge : copy().liveBadge} - - - {readySession()?.member.isAdmin ? copy().adminTag : copy().residentTag} - - - {readySession()?.member.status - ? memberStatusLabel(readySession()!.member.status) - : copy().memberStatusActive} - -
+ setProfileEditorOpen(true) + } + : undefined + } + /> -

- {copy().welcome},{' '} - {readySession()?.telegramUser.firstName ?? readySession()?.member.displayName} -

-

{copy().overviewBody}

- -
- -
-
-
- - + } + active={activeNav()} + onChange={setActiveNav} + />
-
-
- {readySession()?.member.displayName} - {readySession()?.member.isAdmin ? copy().adminTag : copy().residentTag} -
-

- {copy().memberStatusSummary.replace( - '{status}', - readySession()?.member.status - ? memberStatusLabel(readySession()!.member.status) - : copy().memberStatusActive - )} -

-
- - {readySession()?.mode === 'demo' ? copy().demoBadge : copy().liveBadge} - - {locale().toUpperCase()} -
-
+
{renderPanel()}
+ {(item) => ( + + {item.label} + {item.value} + + )} + + ) +} diff --git a/apps/miniapp/src/components/finance/finance-visuals.tsx b/apps/miniapp/src/components/finance/finance-visuals.tsx new file mode 100644 index 0000000..4f0c350 --- /dev/null +++ b/apps/miniapp/src/components/finance/finance-visuals.tsx @@ -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 ( + <> +
+
+ {props.labels.financeVisualsTitle} + + {props.labels.membersCount}: {String(props.dashboard.members.length)} + +
+

{props.labels.financeVisualsBody}

+
+ + {(item) => ( +
+
+ {item.member.displayName} + + {item.member.remainingMajor} {props.dashboard.currency} + +
+
+
+ + {(segment) => ( + + )} + +
+
+
+ + {(segment) => ( + + {segment.label}: {segment.amountMajor} {props.dashboard.currency} + + )} + +
+
+ )} +
+
+
+ +
+
+ {props.labels.purchaseInvestmentsTitle} + + {props.labels.purchaseTotalLabel}: {props.purchaseChart.totalMajor}{' '} + {props.dashboard.currency} + +
+

{props.labels.purchaseInvestmentsBody}

+ + +

{props.labels.purchaseInvestmentsEmpty}

+
+ 0}> +
+
+ +
+ {props.labels.purchaseTotalLabel} + + {props.purchaseChart.totalMajor} {props.dashboard.currency} + +
+
+
+ + {(slice) => ( +
+
+ + {slice.label} +
+

+ {slice.amountMajor} {props.dashboard.currency} ·{' '} + {props.labels.purchaseShareLabel} {slice.percentage}% +

+
+ )} +
+
+
+
+
+
+ + ) +} diff --git a/apps/miniapp/src/components/layout/hero-banner.tsx b/apps/miniapp/src/components/layout/hero-banner.tsx new file mode 100644 index 0000000..a37bd73 --- /dev/null +++ b/apps/miniapp/src/components/layout/hero-banner.tsx @@ -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 ( +
+
+ {props.badges.map((badge, index) => ( + 0}>{badge} + ))} +
+

{props.title}

+

{props.body}

+ + {(action) => ( +
+ +
+ )} +
+
+ ) +} diff --git a/apps/miniapp/src/components/layout/navigation-tabs.tsx b/apps/miniapp/src/components/layout/navigation-tabs.tsx new file mode 100644 index 0000000..4495e23 --- /dev/null +++ b/apps/miniapp/src/components/layout/navigation-tabs.tsx @@ -0,0 +1,28 @@ +import type { JSX } from 'solid-js' + +type TabItem = { + key: T + label: string +} + +type Props = { + items: readonly TabItem[] + active: T + onChange: (key: T) => void +} + +export function NavigationTabs(props: Props): JSX.Element { + return ( + + ) +} diff --git a/apps/miniapp/src/components/layout/profile-card.tsx b/apps/miniapp/src/components/layout/profile-card.tsx new file mode 100644 index 0000000..a1ae035 --- /dev/null +++ b/apps/miniapp/src/components/layout/profile-card.tsx @@ -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 ( + +
+ {props.displayName} + {props.roleLabel} +
+

{props.statusSummary}

+
+ {props.modeBadge} + {props.localeBadge} +
+
+ ) +} diff --git a/apps/miniapp/src/components/layout/top-bar.tsx b/apps/miniapp/src/components/layout/top-bar.tsx new file mode 100644 index 0000000..0142f93 --- /dev/null +++ b/apps/miniapp/src/components/layout/top-bar.tsx @@ -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 ( +
+
+

{props.subtitle}

+

{props.title}

+
+ + +
+ ) +} diff --git a/apps/miniapp/src/components/ui.tsx b/apps/miniapp/src/components/ui.tsx deleted file mode 100644 index f7222c4..0000000 --- a/apps/miniapp/src/components/ui.tsx +++ /dev/null @@ -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 - }> -) { - return ( - - ) -} - -export function IconButton( - props: ParentProps<{ - label: string - class?: string - disabled?: boolean - onClick?: JSX.EventHandlerUnion - }> -) { - const maybeClass = props.class ? { class: props.class } : {} - const maybeDisabled = props.disabled !== undefined ? { disabled: props.disabled } : {} - const maybeOnClick = props.onClick ? { onClick: props.onClick } : {} - - return ( - - ) -} - -export function Field( - props: ParentProps<{ - label: string - hint?: string - wide?: boolean - class?: string - }> -) { - return ( - - ) -} - -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 ( - - - - ) -} diff --git a/apps/miniapp/src/components/ui/button.tsx b/apps/miniapp/src/components/ui/button.tsx new file mode 100644 index 0000000..718314b --- /dev/null +++ b/apps/miniapp/src/components/ui/button.tsx @@ -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 +}> & + VariantProps + +export function Button(props: ButtonProps) { + return ( + + ) +} + +export function IconButton( + props: ParentProps<{ + label: string + class?: string + disabled?: boolean + onClick?: JSX.EventHandlerUnion + }> +) { + const maybeClass = props.class ? { class: props.class } : {} + const maybeDisabled = props.disabled !== undefined ? { disabled: props.disabled } : {} + const maybeOnClick = props.onClick ? { onClick: props.onClick } : {} + + return ( + + ) +} diff --git a/apps/miniapp/src/components/ui/card.tsx b/apps/miniapp/src/components/ui/card.tsx new file mode 100644 index 0000000..53b519c --- /dev/null +++ b/apps/miniapp/src/components/ui/card.tsx @@ -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 ( +
+ {props.children} +
+ ) +} + +export function StatCard(props: ParentProps<{ class?: string }>) { + return
{props.children}
+} + +export function MiniChip(props: ParentProps<{ muted?: boolean; class?: string }>) { + return ( + + {props.children} + + ) +} diff --git a/apps/miniapp/src/components/ui/dialog.tsx b/apps/miniapp/src/components/ui/dialog.tsx new file mode 100644 index 0000000..49810f1 --- /dev/null +++ b/apps/miniapp/src/components/ui/dialog.tsx @@ -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 ( + !open && props.onClose()}> + + + + + + ) +} diff --git a/apps/miniapp/src/components/ui/field.tsx b/apps/miniapp/src/components/ui/field.tsx new file mode 100644 index 0000000..ff38076 --- /dev/null +++ b/apps/miniapp/src/components/ui/field.tsx @@ -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 ( + + ) +} diff --git a/apps/miniapp/src/components/ui/index.ts b/apps/miniapp/src/components/ui/index.ts new file mode 100644 index 0000000..534a555 --- /dev/null +++ b/apps/miniapp/src/components/ui/index.ts @@ -0,0 +1,4 @@ +export * from './button' +export * from './card' +export * from './dialog' +export * from './field' diff --git a/apps/miniapp/src/demo/miniapp-demo.ts b/apps/miniapp/src/demo/miniapp-demo.ts new file mode 100644 index 0000000..cfb3abb --- /dev/null +++ b/apps/miniapp/src/demo/miniapp-demo.ts @@ -0,0 +1,285 @@ +import type { + MiniAppAdminCycleState, + MiniAppAdminSettingsPayload, + MiniAppDashboard, + MiniAppPendingMember, + MiniAppSession +} from '../miniapp-api' + +export const demoMember: NonNullable = { + id: 'demo-member', + householdId: 'demo-household', + displayName: 'Stas', + status: 'active', + isAdmin: true, + preferredLocale: 'en', + householdDefaultLocale: 'en' +} + +export const demoTelegramUser: NonNullable = { + 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' + } + ] +} diff --git a/apps/miniapp/src/index.tsx b/apps/miniapp/src/index.tsx index 7847bac..3d52a0a 100644 --- a/apps/miniapp/src/index.tsx +++ b/apps/miniapp/src/index.tsx @@ -1,6 +1,8 @@ /* @refresh reload */ +import { QueryClientProvider } from '@tanstack/solid-query' import { render } from 'solid-js/web' +import { miniAppQueryClient } from './app/query-client' import './index.css' import App from './App' @@ -10,4 +12,11 @@ if (!root) { throw new Error('Root element not found') } -render(() => , root) +render( + () => ( + + + + ), + root +) diff --git a/apps/miniapp/src/lib/cn.ts b/apps/miniapp/src/lib/cn.ts new file mode 100644 index 0000000..40ed99b --- /dev/null +++ b/apps/miniapp/src/lib/cn.ts @@ -0,0 +1,5 @@ +import { clsx, type ClassValue } from 'clsx' + +export function cn(...inputs: ClassValue[]) { + return clsx(inputs) +} diff --git a/bun.lock b/bun.lock index dd532b8..dd3bbd3 100644 --- a/bun.lock +++ b/bun.lock @@ -30,8 +30,13 @@ "apps/miniapp": { "name": "@household/miniapp", "dependencies": { + "@kobalte/core": "0.13.11", + "@tanstack/solid-query": "5.90.23", "@twa-dev/sdk": "8.0.2", + "class-variance-authority": "0.7.1", + "clsx": "2.1.1", "solid-js": "^1.9.9", + "zod": "4.3.6", }, "devDependencies": { "@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=="], + "@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=="], "@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=="], + "@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=="], "@household/adapters-db": ["@household/adapters-db@workspace:packages/adapters-db"], @@ -223,6 +236,10 @@ "@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=="], "@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=="], + "@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=="], "@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/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/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/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/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=="], + "@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=="], "@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=="], + "@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/types": ["@twa-dev/types@8.0.2", "", {}, "sha512-ICQ6n4NaUPPzV3/GzflVQS6Nnu5QX2vr9OlOG8ZkFf3rSJXzRKazrLAbZlVhCPPWkIW3MMuELPsE6tByrA49qA=="], @@ -457,6 +492,10 @@ "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-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-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=="], "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=="], + "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=="], "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],