From 789854358edc15766ab770b6e8682e51326cbe5c Mon Sep 17 00:00:00 2001 From: whekin Date: Thu, 12 Mar 2026 02:28:56 +0400 Subject: [PATCH] fix(miniapp): address review feedback --- apps/miniapp/src/App.tsx | 68 ++++++++----------- .../finance/member-balance-card.tsx | 26 +------ apps/miniapp/src/i18n.ts | 4 ++ apps/miniapp/src/index.css | 7 ++ apps/miniapp/src/lib/money.ts | 24 +++++++ apps/miniapp/src/screens/house-screen.tsx | 49 +++++++++---- 6 files changed, 103 insertions(+), 75 deletions(-) create mode 100644 apps/miniapp/src/lib/money.ts diff --git a/apps/miniapp/src/App.tsx b/apps/miniapp/src/App.tsx index 29b8150..00c2087 100644 --- a/apps/miniapp/src/App.tsx +++ b/apps/miniapp/src/App.tsx @@ -1,6 +1,7 @@ import { Match, Show, Switch, createMemo, createSignal, onMount } from 'solid-js' import { dictionary, type Locale } from './i18n' +import { majorStringToMinor, minorToMajorString } from './lib/money' import { fetchAdminSettingsQuery, fetchBillingCycleQuery, @@ -124,6 +125,7 @@ type PaymentDraft = { type TestingRolePreview = 'admin' | 'resident' const chartPalette = ['#f7b389', '#6fd3c0', '#f06a8d', '#94a8ff', '#f3d36f', '#7dc96d'] as const +const TESTING_ROLE_TAP_WINDOW_MS = 30 * 60 * 1000 const demoSession: Extract = { status: 'ready', @@ -178,27 +180,6 @@ function defaultCyclePeriod(): string { return new Date().toISOString().slice(0, 7) } -function majorStringToMinor(value: string): bigint { - const trimmed = value.trim() - const negative = trimmed.startsWith('-') - const normalized = negative ? trimmed.slice(1) : trimmed - const [whole = '0', fraction = ''] = normalized.split('.') - const major = BigInt(whole || '0') - const cents = BigInt((fraction.padEnd(2, '0').slice(0, 2) || '00').replace(/\D/g, '') || '0') - const minor = major * 100n + cents - - return negative ? -minor : minor -} - -function minorToMajorString(value: bigint): string { - const negative = value < 0n - const absolute = negative ? -value : value - const whole = absolute / 100n - const fraction = String(absolute % 100n).padStart(2, '0') - - return `${negative ? '-' : ''}${whole.toString()}.${fraction}` -} - function absoluteMinor(value: bigint): bigint { return value < 0n ? -value : value } @@ -691,7 +672,10 @@ function App() { } const now = Date.now() - const nextHistory = [...roleChipTapHistory().filter((timestamp) => now - timestamp < 1800), now] + const nextHistory = [ + ...roleChipTapHistory().filter((timestamp) => now - timestamp < TESTING_ROLE_TAP_WINDOW_MS), + now + ] if (nextHistory.length >= 5) { setRoleChipTapHistory([]) @@ -2209,28 +2193,36 @@ function App() { })) } onBillingRentDueDayChange={(value) => - setBillingForm((current) => ({ - ...current, - rentDueDay: value - })) + value === null + ? undefined + : setBillingForm((current) => ({ + ...current, + rentDueDay: value + })) } onBillingRentWarningDayChange={(value) => - setBillingForm((current) => ({ - ...current, - rentWarningDay: value - })) + value === null + ? undefined + : setBillingForm((current) => ({ + ...current, + rentWarningDay: value + })) } onBillingUtilitiesDueDayChange={(value) => - setBillingForm((current) => ({ - ...current, - utilitiesDueDay: value - })) + value === null + ? undefined + : setBillingForm((current) => ({ + ...current, + utilitiesDueDay: value + })) } onBillingUtilitiesReminderDayChange={(value) => - setBillingForm((current) => ({ - ...current, - utilitiesReminderDay: value - })) + value === null + ? undefined + : setBillingForm((current) => ({ + ...current, + utilitiesReminderDay: value + })) } onBillingTimezoneChange={(value) => setBillingForm((current) => ({ diff --git a/apps/miniapp/src/components/finance/member-balance-card.tsx b/apps/miniapp/src/components/finance/member-balance-card.tsx index 636024f..08c629a 100644 --- a/apps/miniapp/src/components/finance/member-balance-card.tsx +++ b/apps/miniapp/src/components/finance/member-balance-card.tsx @@ -1,6 +1,7 @@ import { For, Show } from 'solid-js' import { cn } from '../../lib/cn' +import { majorStringToMinor, sumMajorStrings } from '../../lib/money' import type { MiniAppDashboard } from '../../miniapp-api' import { MiniChip, StatCard } from '../ui' @@ -11,31 +12,6 @@ type Props = { detail?: boolean } -function majorStringToMinor(value: string): bigint { - const trimmed = value.trim() - const negative = trimmed.startsWith('-') - const normalized = negative ? trimmed.slice(1) : trimmed - const [whole = '0', fraction = ''] = normalized.split('.') - const major = BigInt(whole || '0') - const cents = BigInt((fraction.padEnd(2, '0').slice(0, 2) || '00').replace(/\D/g, '') || '0') - const minor = major * 100n + cents - - return negative ? -minor : minor -} - -function minorToMajorString(value: bigint): string { - const negative = value < 0n - const absolute = negative ? -value : value - const whole = absolute / 100n - const fraction = String(absolute % 100n).padStart(2, '0') - - return `${negative ? '-' : ''}${whole.toString()}.${fraction}` -} - -function sumMajorStrings(left: string, right: string): string { - return minorToMajorString(majorStringToMinor(left) + majorStringToMinor(right)) -} - export function MemberBalanceCard(props: Props) { const utilitiesAdjustedMajor = () => sumMajorStrings(props.member.utilityShareMajor, props.member.purchaseOffsetMajor) diff --git a/apps/miniapp/src/i18n.ts b/apps/miniapp/src/i18n.ts index 7940127..ffb09e9 100644 --- a/apps/miniapp/src/i18n.ts +++ b/apps/miniapp/src/i18n.ts @@ -31,6 +31,8 @@ export const dictionary = { language: 'Language', householdLanguage: 'Household language', savingLanguage: 'Saving…', + onLabel: 'On', + offLabel: 'Off', home: 'Home', balances: 'Balances', ledger: 'Ledger', @@ -273,6 +275,8 @@ export const dictionary = { language: 'Язык', householdLanguage: 'Язык дома', savingLanguage: 'Сохраняем…', + onLabel: 'Вкл', + offLabel: 'Выкл', home: 'Главная', balances: 'Баланс', ledger: 'Леджер', diff --git a/apps/miniapp/src/index.css b/apps/miniapp/src/index.css index caad749..b53bf5f 100644 --- a/apps/miniapp/src/index.css +++ b/apps/miniapp/src/index.css @@ -884,6 +884,13 @@ button { } .mini-chip-button { + appearance: none; + -webkit-appearance: none; + border: 0; + background: transparent; + padding: 0; + font: inherit; + line-height: inherit; cursor: pointer; } diff --git a/apps/miniapp/src/lib/money.ts b/apps/miniapp/src/lib/money.ts new file mode 100644 index 0000000..520a0e3 --- /dev/null +++ b/apps/miniapp/src/lib/money.ts @@ -0,0 +1,24 @@ +export function majorStringToMinor(value: string): bigint { + const trimmed = value.trim() + const negative = trimmed.startsWith('-') + const normalized = negative ? trimmed.slice(1) : trimmed + const [whole = '0', fraction = ''] = normalized.split('.') + const major = BigInt(whole || '0') + const cents = BigInt((fraction.padEnd(2, '0').slice(0, 2) || '00').replace(/\D/g, '') || '0') + const minor = major * 100n + cents + + return negative ? -minor : minor +} + +export function minorToMajorString(value: bigint): string { + const negative = value < 0n + const absolute = negative ? -value : value + const whole = absolute / 100n + const fraction = String(absolute % 100n).padStart(2, '0') + + return `${negative ? '-' : ''}${whole.toString()}.${fraction}` +} + +export function sumMajorStrings(left: string, right: string): string { + return minorToMajorString(majorStringToMinor(left) + majorStringToMinor(right)) +} diff --git a/apps/miniapp/src/screens/house-screen.tsx b/apps/miniapp/src/screens/house-screen.tsx index 3c388e5..eddcb8f 100644 --- a/apps/miniapp/src/screens/house-screen.tsx +++ b/apps/miniapp/src/screens/house-screen.tsx @@ -116,10 +116,10 @@ type Props = { onBillingAdjustmentPolicyChange: (value: 'utilities' | 'rent' | 'separate') => void onBillingRentAmountChange: (value: string) => void onBillingRentCurrencyChange: (value: 'USD' | 'GEL') => void - onBillingRentDueDayChange: (value: number) => void - onBillingRentWarningDayChange: (value: number) => void - onBillingUtilitiesDueDayChange: (value: number) => void - onBillingUtilitiesReminderDayChange: (value: number) => void + onBillingRentDueDayChange: (value: number | null) => void + onBillingRentWarningDayChange: (value: number | null) => void + onBillingUtilitiesDueDayChange: (value: number | null) => void + onBillingUtilitiesReminderDayChange: (value: number | null) => void onBillingTimezoneChange: (value: string) => void onOpenAddUtilityBill: () => void onCloseAddUtilityBill: () => void @@ -165,6 +165,23 @@ type Props = { } export function HouseScreen(props: Props) { + function parseBillingDayInput(value: string): number | null { + const trimmed = value.trim() + if (trimmed.length === 0) { + return null + } + + const parsed = Number.parseInt(trimmed, 10) + if (!Number.isFinite(parsed) || !Number.isInteger(parsed) || parsed < 1 || parsed > 31) { + return null + } + + return parsed + } + + const enabledLabel = () => props.copy.onLabel ?? 'ON' + const disabledLabel = () => props.copy.offLabel ?? 'OFF' + return ( - props.onBillingRentDueDayChange(Number(event.currentTarget.value)) + props.onBillingRentDueDayChange( + parseBillingDayInput(event.currentTarget.value) + ) } /> @@ -457,7 +476,9 @@ export function HouseScreen(props: Props) { max="31" value={String(props.billingForm.rentWarningDay)} onInput={(event) => - props.onBillingRentWarningDayChange(Number(event.currentTarget.value)) + props.onBillingRentWarningDayChange( + parseBillingDayInput(event.currentTarget.value) + ) } /> @@ -468,7 +489,9 @@ export function HouseScreen(props: Props) { max="31" value={String(props.billingForm.utilitiesDueDay)} onInput={(event) => - props.onBillingUtilitiesDueDayChange(Number(event.currentTarget.value)) + props.onBillingUtilitiesDueDayChange( + parseBillingDayInput(event.currentTarget.value) + ) } /> @@ -479,7 +502,9 @@ export function HouseScreen(props: Props) { max="31" value={String(props.billingForm.utilitiesReminderDay)} onInput={(event) => - props.onBillingUtilitiesReminderDayChange(Number(event.currentTarget.value)) + props.onBillingUtilitiesReminderDayChange( + parseBillingDayInput(event.currentTarget.value) + ) } /> @@ -562,14 +587,14 @@ export function HouseScreen(props: Props) {
{category.name} - {category.isActive ? 'ON' : 'OFF'} + {category.isActive ? enabledLabel() : disabledLabel()}

{props.copy.utilityCategoryName ?? ''}

- {category.isActive ? 'ON' : 'OFF'} + {category.isActive ? enabledLabel() : disabledLabel()}
@@ -815,8 +840,8 @@ export function HouseScreen(props: Props) { ) } > - - + +