feat: add quick payment action and improve copy button UX

Mini App Home Screen:
- Add 'Record Payment' button to utilities and rent period cards
- Pre-fill payment amount with member's share (rentShare/utilityShare)
- Modal dialog with amount input and currency display
- Toast notifications for copy and payment success/failure feedback

Copy Button Improvements:
- Increase spacing between icon and text (4px → 8px)
- Add hover background and padding for better touch target
- Green background highlight when copied (in addition to icon color change)
- Toast notification appears when copying any value

Backend:
- Add /api/miniapp/payments/add endpoint for quick payments
- Payment notifications sent to 'reminders' topic in Telegram
- Include member name, payment type, amount, and period in notification

Files:
- New: apps/miniapp/src/components/ui/toast.tsx
- Modified: apps/miniapp/src/routes/home.tsx, apps/miniapp/src/index.css,
  apps/miniapp/src/theme.css, apps/miniapp/src/i18n.ts,
  apps/bot/src/miniapp-billing.ts, apps/bot/src/server.ts

Quality Gates:  format, lint, typecheck, build, test

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
2026-03-14 08:51:53 +04:00
parent 771d64aa4e
commit 488a488137
45 changed files with 2236 additions and 101 deletions

View File

@@ -64,6 +64,22 @@ export const dictionary = {
payNowBody: '',
homeDueTitle: 'Due',
homeSettledTitle: 'Settled',
homeUtilitiesTitle: 'Utilities payment',
homeRentTitle: 'Rent payment',
homeNoPaymentTitle: 'No payment period',
homeUtilitiesUpcomingLabel: 'Utilities starts {date}',
homeRentUpcomingLabel: 'Rent starts {date}',
homeFillUtilitiesTitle: 'Fill utilities',
homeFillUtilitiesBody:
'No utility bills are recorded for this cycle yet. Add at least one bill to calculate utilities.',
homeFillUtilitiesSubmitAction: 'Save utility bill',
homeFillUtilitiesSubmitting: 'Saving…',
homeFillUtilitiesOpenLedgerAction: 'Open ledger',
homeUtilitiesBillsTitle: 'Utility bills',
homePurchasesTitle: 'Purchases',
homePurchasesOffsetLabel: 'Your purchases balance',
homePurchasesTotalLabel: 'Household purchases ({count})',
homeMembersCountLabel: 'Members',
whyAction: 'Why?',
currentCycleLabel: 'Current cycle',
cycleTotalLabel: 'Cycle total',
@@ -137,6 +153,12 @@ export const dictionary = {
testingPreviewResidentAction: 'Preview resident',
testingCurrentRoleLabel: 'Real access',
testingPreviewRoleLabel: 'Previewing',
testingPeriodCurrentLabel: 'Dashboard period',
testingPeriodOverrideLabel: 'Period override',
testingPeriodOverridePlaceholder: 'YYYY-MM',
testingTodayOverrideLabel: 'Today override',
testingTodayOverridePlaceholder: 'YYYY-MM-DD',
testingClearOverridesAction: 'Clear overrides',
purchaseReviewTitle: 'Purchases',
purchaseReviewBody: 'Edit or remove purchases if the bot recorded the wrong item.',
purchaseSplitTitle: 'Split',
@@ -151,6 +173,15 @@ export const dictionary = {
paymentsAdminTitle: 'Payments',
paymentsAdminBody: 'Add, fix, or remove payment records for the current cycle.',
paymentsAddAction: 'Add payment',
copiedToast: 'Copied!',
quickPaymentTitle: 'Record payment',
quickPaymentBody: 'Quickly record a {type} payment for the current cycle.',
quickPaymentAmountLabel: 'Amount',
quickPaymentCurrencyLabel: 'Currency',
quickPaymentSubmitAction: 'Save payment',
quickPaymentSubmitting: 'Saving…',
quickPaymentSuccess: 'Payment recorded successfully',
quickPaymentFailed: 'Failed to record payment',
addingPayment: 'Adding payment…',
paymentCreateBody: 'Create a payment record in a focused editor instead of a long inline form.',
paymentKind: 'Payment kind',
@@ -220,6 +251,7 @@ export const dictionary = {
currencyLabel: 'Currency',
rentAmount: 'Rent amount',
defaultRentAmount: 'Default rent',
rentCurrencyLabel: 'Rent currency',
defaultRentHint:
'New current cycles start from this rent unless you override a specific month.',
currentCycleRentLabel: 'Current cycle rent',
@@ -235,6 +267,16 @@ export const dictionary = {
timezone: 'Timezone',
timezoneHint: 'Use an IANA timezone like Asia/Tbilisi.',
timezoneInvalidHint: 'Pick a valid IANA timezone such as Asia/Tbilisi.',
rentPaymentDestinationsTitle: 'Rent payment destinations',
rentPaymentDestinationsEmpty: 'No rent payment destinations saved yet.',
rentPaymentDestinationAddAction: 'Add destination',
rentPaymentDestinationRemoveAction: 'Remove destination',
rentPaymentDestinationLabel: 'Label',
rentPaymentDestinationRecipient: 'Recipient name',
rentPaymentDestinationBank: 'Bank name',
rentPaymentDestinationAccount: 'Account / card / IBAN',
rentPaymentDestinationLink: 'Payment link',
rentPaymentDestinationNote: 'Note',
manageSettingsAction: 'Manage settings',
billingSettingsEditorBody:
'Household defaults live here. New current cycles start from these values.',
@@ -367,6 +409,22 @@ export const dictionary = {
payNowBody: '',
homeDueTitle: 'К оплате',
homeSettledTitle: 'Закрыто',
homeUtilitiesTitle: 'Оплата коммуналки',
homeRentTitle: 'Оплата аренды',
homeNoPaymentTitle: 'Период без оплаты',
homeUtilitiesUpcomingLabel: 'Коммуналка с {date}',
homeRentUpcomingLabel: 'Аренда с {date}',
homeFillUtilitiesTitle: 'Внести коммуналку',
homeFillUtilitiesBody:
'Для этого цикла коммунальные счета ещё не внесены. Добавь хотя бы один счёт, чтобы рассчитать коммуналку.',
homeFillUtilitiesSubmitAction: 'Сохранить счёт',
homeFillUtilitiesSubmitting: 'Сохраняем…',
homeFillUtilitiesOpenLedgerAction: 'Открыть леджер',
homeUtilitiesBillsTitle: 'Коммунальные счета',
homePurchasesTitle: 'Покупки',
homePurchasesOffsetLabel: 'Ваш баланс покупок',
homePurchasesTotalLabel: 'Покупок в доме ({count})',
homeMembersCountLabel: 'Жильцов',
whyAction: 'Почему?',
currentCycleLabel: 'Текущий цикл',
cycleTotalLabel: 'Всего за цикл',
@@ -440,6 +498,12 @@ export const dictionary = {
testingPreviewResidentAction: 'Вид жителя',
testingCurrentRoleLabel: 'Реальный доступ',
testingPreviewRoleLabel: 'Сейчас показан',
testingPeriodCurrentLabel: 'Период (из API)',
testingPeriodOverrideLabel: 'Переопределить период',
testingPeriodOverridePlaceholder: 'YYYY-MM',
testingTodayOverrideLabel: 'Переопределить сегодня',
testingTodayOverridePlaceholder: 'YYYY-MM-DD',
testingClearOverridesAction: 'Сбросить переопределения',
purchaseReviewTitle: 'Покупки',
purchaseReviewBody:
'Здесь можно исправить или удалить покупку, если бот распознал её неправильно.',
@@ -456,6 +520,15 @@ export const dictionary = {
paymentsAdminTitle: 'Оплаты',
paymentsAdminBody: 'Добавляй, исправляй или удаляй оплаты за текущий цикл.',
paymentsAddAction: 'Добавить оплату',
copiedToast: 'Скопировано!',
quickPaymentTitle: 'Записать оплату',
quickPaymentBody: 'Быстро запиши оплату {type} за текущий цикл.',
quickPaymentAmountLabel: 'Сумма',
quickPaymentCurrencyLabel: 'Валюта',
quickPaymentSubmitAction: 'Сохранить оплату',
quickPaymentSubmitting: 'Сохраняем…',
quickPaymentSuccess: 'Оплата успешно записана',
quickPaymentFailed: 'Не удалось записать оплату',
addingPayment: 'Добавляем оплату…',
paymentCreateBody: 'Создай оплату в отдельном окне вместо длинной встроенной формы.',
paymentKind: 'Тип оплаты',
@@ -524,6 +597,7 @@ export const dictionary = {
currencyLabel: 'Валюта',
rentAmount: 'Сумма аренды',
defaultRentAmount: 'Аренда по умолчанию',
rentCurrencyLabel: 'Валюта аренды',
defaultRentHint:
'Новые текущие циклы стартуют с этой суммой, если для конкретного месяца нет переопределения.',
currentCycleRentLabel: 'Аренда текущего цикла',
@@ -539,6 +613,16 @@ export const dictionary = {
timezone: 'Часовой пояс',
timezoneHint: 'Используй IANA-таймзону, например Asia/Tbilisi.',
timezoneInvalidHint: 'Выбери корректную IANA-таймзону, например Asia/Tbilisi.',
rentPaymentDestinationsTitle: 'Реквизиты для оплаты аренды',
rentPaymentDestinationsEmpty: 'Реквизиты для оплаты аренды ещё не добавлены.',
rentPaymentDestinationAddAction: 'Добавить реквизиты',
rentPaymentDestinationRemoveAction: 'Удалить',
rentPaymentDestinationLabel: 'Название',
rentPaymentDestinationRecipient: 'Получатель',
rentPaymentDestinationBank: 'Банк',
rentPaymentDestinationAccount: 'Счёт / карта / IBAN',
rentPaymentDestinationLink: 'Ссылка на оплату',
rentPaymentDestinationNote: 'Комментарий',
manageSettingsAction: 'Управлять настройками',
billingSettingsEditorBody:
'Здесь живут значения по умолчанию для дома. Новые текущие циклы стартуют отсюда.',