fix(miniapp): address review feedback

This commit is contained in:
2026-03-12 02:28:56 +04:00
parent 135a2301ca
commit 789854358e
6 changed files with 103 additions and 75 deletions

View File

@@ -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<SessionState, { status: 'ready' }> = {
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,25 +2193,33 @@ function App() {
}))
}
onBillingRentDueDayChange={(value) =>
setBillingForm((current) => ({
value === null
? undefined
: setBillingForm((current) => ({
...current,
rentDueDay: value
}))
}
onBillingRentWarningDayChange={(value) =>
setBillingForm((current) => ({
value === null
? undefined
: setBillingForm((current) => ({
...current,
rentWarningDay: value
}))
}
onBillingUtilitiesDueDayChange={(value) =>
setBillingForm((current) => ({
value === null
? undefined
: setBillingForm((current) => ({
...current,
utilitiesDueDay: value
}))
}
onBillingUtilitiesReminderDayChange={(value) =>
setBillingForm((current) => ({
value === null
? undefined
: setBillingForm((current) => ({
...current,
utilitiesReminderDay: value
}))

View File

@@ -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)

View File

@@ -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: 'Леджер',

View File

@@ -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;
}

View File

@@ -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))
}

View File

@@ -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 (
<Show
when={props.readyIsAdmin}
@@ -446,7 +463,9 @@ export function HouseScreen(props: Props) {
max="31"
value={String(props.billingForm.rentDueDay)}
onInput={(event) =>
props.onBillingRentDueDayChange(Number(event.currentTarget.value))
props.onBillingRentDueDayChange(
parseBillingDayInput(event.currentTarget.value)
)
}
/>
</Field>
@@ -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)
)
}
/>
</Field>
@@ -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)
)
}
/>
</Field>
@@ -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)
)
}
/>
</Field>
@@ -562,14 +587,14 @@ export function HouseScreen(props: Props) {
<div class="ledger-compact-card__main">
<header>
<strong>{category.name}</strong>
<span>{category.isActive ? 'ON' : 'OFF'}</span>
<span>{category.isActive ? enabledLabel() : disabledLabel()}</span>
</header>
<p>{props.copy.utilityCategoryName ?? ''}</p>
<div class="ledger-compact-card__meta">
<span
class={`mini-chip ${category.isActive ? '' : 'mini-chip--muted'}`}
>
{category.isActive ? 'ON' : 'OFF'}
{category.isActive ? enabledLabel() : disabledLabel()}
</span>
</div>
</div>
@@ -815,8 +840,8 @@ export function HouseScreen(props: Props) {
)
}
>
<option value="true">ON</option>
<option value="false">OFF</option>
<option value="true">{enabledLabel()}</option>
<option value="false">{disabledLabel()}</option>
</select>
</Field>
</div>