From 632f8888b55c57c8ab767773099206a65c8cc9de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikola=20Dere=C5=BEi=C4=87?= Date: Mon, 24 Nov 2025 14:45:59 +0100 Subject: [PATCH] refactor: replace payment dropdown with independent toggles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace showPaymentInstructionsInMonthlyStatement dropdown with enableIbanPayment and enableRevolutPayment boolean toggles - Update UserSettingsForm to use separate fieldsets for IBAN and Revolut with independent toggle switches - Add hidden inputs to preserve values when toggles are disabled - Update validation logic to check enableIbanPayment instead of show2dCodeInMonthlyStatement - Reorganize translation keys to match new structure (iban-* and revolut-* prefixes) - Update ViewLocationCard to use enableIbanPayment field This provides better UX by allowing users to enable both payment methods simultaneously if needed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- app/lib/actions/userSettingsActions.ts | 35 ++++--- app/lib/db-types.ts | 14 +-- app/ui/UserSettingsForm.tsx | 128 ++++++++++++++++++------- app/ui/ViewLocationCard.tsx | 6 +- messages/en.json | 45 +++++---- messages/hr.json | 41 ++++---- 6 files changed, 168 insertions(+), 101 deletions(-) diff --git a/app/lib/actions/userSettingsActions.ts b/app/lib/actions/userSettingsActions.ts index b767e79..62e175c 100644 --- a/app/lib/actions/userSettingsActions.ts +++ b/app/lib/actions/userSettingsActions.ts @@ -19,7 +19,6 @@ export type State = { ownerTown?: string[]; ownerIBAN?: string[]; currency?: string[]; - show2dCodeInMonthlyStatement?: string[]; }; message?: string | null; success?: boolean; @@ -29,6 +28,8 @@ export type State = { * Schema for validating user settings form fields */ const FormSchema = (t: IntlTemplateFn) => z.object({ + currency: z.string().optional(), + enableIbanPayment: z.boolean().optional(), ownerName: z.string().max(25).optional(), ownerStreet: z.string().max(25).optional(), ownerTown: z.string().max(27).optional(), @@ -43,11 +44,11 @@ const FormSchema = (t: IntlTemplateFn) => z.object({ }, { message: t("owner-iban-invalid") } ), - currency: z.string().optional(), - show2dCodeInMonthlyStatement: z.boolean().optional().nullable(), + enableRevolutPayment: z.boolean().optional(), + ownerRevolutProfileName: z.string().max(25).optional(), }) .refine((data) => { - if (data.show2dCodeInMonthlyStatement) { + if (data.enableIbanPayment) { return !!data.ownerName && data.ownerName.trim().length > 0; } return true; @@ -56,7 +57,7 @@ const FormSchema = (t: IntlTemplateFn) => z.object({ path: ["ownerName"], }) .refine((data) => { - if (data.show2dCodeInMonthlyStatement) { + if (data.enableIbanPayment) { return !!data.ownerStreet && data.ownerStreet.trim().length > 0; } return true; @@ -65,7 +66,7 @@ const FormSchema = (t: IntlTemplateFn) => z.object({ path: ["ownerStreet"], }) .refine((data) => { - if (data.show2dCodeInMonthlyStatement) { + if (data.enableIbanPayment) { return !!data.ownerTown && data.ownerTown.trim().length > 0; } return true; @@ -74,7 +75,7 @@ const FormSchema = (t: IntlTemplateFn) => z.object({ path: ["ownerTown"], }) .refine((data) => { - if (data.show2dCodeInMonthlyStatement) { + if (data.enableIbanPayment) { if (!data.ownerIBAN || data.ownerIBAN.trim().length === 0) { return false; } @@ -88,7 +89,7 @@ const FormSchema = (t: IntlTemplateFn) => z.object({ path: ["ownerIBAN"], }) .refine((data) => { - if (data.show2dCodeInMonthlyStatement) { + if (data.enableIbanPayment) { return !!data.currency && data.currency.trim().length > 0; } return true; @@ -141,7 +142,9 @@ export const updateUserSettings = withUser(async (user: AuthenticatedUser, prevS ownerTown: formData.get('ownerTown') || undefined, ownerIBAN: formData.get('ownerIBAN') || undefined, currency: formData.get('currency') || undefined, - show2dCodeInMonthlyStatement: formData.get('generateTenantCode') === 'on', + enableIbanPayment: formData.get('enableIbanPayment') === 'on' ? true : false, + enableRevolutPayment: formData.get('enableRevolutPayment') === 'on' ? true : false, + ownerRevolutProfileName: formData.get('ownerRevolutProfileName') || undefined, }); // If form validation fails, return errors early. Otherwise, continue... @@ -153,7 +156,7 @@ export const updateUserSettings = withUser(async (user: AuthenticatedUser, prevS }; } - const { ownerName, ownerStreet, ownerTown, ownerIBAN, currency, show2dCodeInMonthlyStatement } = validatedFields.data; + const { enableIbanPayment, ownerName, ownerStreet, ownerTown, ownerIBAN, currency, enableRevolutPayment, ownerRevolutProfileName } = validatedFields.data; // Normalize IBAN: remove spaces and convert to uppercase const normalizedOwnerIBAN = ownerIBAN ? ownerIBAN.replace(/\s/g, '').toUpperCase() : null; @@ -164,12 +167,14 @@ export const updateUserSettings = withUser(async (user: AuthenticatedUser, prevS const userSettings: UserSettings = { userId, - ownerName: ownerName || null, - ownerStreet: ownerStreet || null, - ownerTown: ownerTown || null, + enableIbanPayment: enableIbanPayment ?? false, + ownerName: ownerName ?? undefined, + ownerStreet: ownerStreet ?? undefined, + ownerTown: ownerTown ?? undefined, ownerIBAN: normalizedOwnerIBAN, - currency: currency || null, - show2dCodeInMonthlyStatement: show2dCodeInMonthlyStatement ?? false, + currency: currency ?? undefined, + enableRevolutPayment: enableRevolutPayment ?? false, + ownerRevolutProfileName: ownerRevolutProfileName ?? undefined, }; await dbClient.collection("userSettings") diff --git a/app/lib/db-types.ts b/app/lib/db-types.ts index e5f7e0a..7196b99 100644 --- a/app/lib/db-types.ts +++ b/app/lib/db-types.ts @@ -18,6 +18,8 @@ export interface YearMonth { export interface UserSettings { /** user's ID */ userId: string; + /** whether enableshow IBAN payment instructions in monthly statement */ + enableIbanPayment?: boolean | null; /** owner name */ ownerName?: string | null; /** owner street */ @@ -28,18 +30,10 @@ export interface UserSettings { ownerIBAN?: string | null; /** currency (ISO 4217) */ currency?: string | null; + /** whether to enable Revolut payment instructions in monthly statement */ + enableRevolutPayment?: boolean | null; /** owner Revolut payment link */ ownerRevolutProfileName?: string | null; - /** whether to show 2D code in monthly statement */ - show2dCodeInMonthlyStatement?: boolean | null; - /** whether to show payment instructions in monthly statement */ - showPaymentInstructionsInMonthlyStatement?: "disabled" | "iban" | "revolut" | null; - - // /** whether enableshow IBAN payment instructions in monthly statement */ - // enableIbanPaymentInstructionsInMonthlyStatement?: boolean | null; - // /** whether to enable Revolut payment instructions in monthly statement */ - // enableRevolutPaymentInstructionsInMonthlyStatement?: boolean | null; - }; /** bill object in the form returned by MongoDB */ diff --git a/app/ui/UserSettingsForm.tsx b/app/ui/UserSettingsForm.tsx index 898191d..4233ecd 100644 --- a/app/ui/UserSettingsForm.tsx +++ b/app/ui/UserSettingsForm.tsx @@ -28,18 +28,20 @@ const FormFields: FC = ({ userSettings, errors, message }) => { // Track current form values for real-time validation const [formValues, setFormValues] = useState({ + enableIbanPayment: userSettings?.enableIbanPayment ?? false, ownerName: userSettings?.ownerName ?? "", ownerStreet: userSettings?.ownerStreet ?? "", ownerTown: userSettings?.ownerTown ?? "", ownerIBAN: formatIban(userSettings?.ownerIBAN) ?? "", currency: userSettings?.currency ?? "EUR", + + enableRevolutPayment: userSettings?.enableRevolutPayment ?? false, ownerRevolutProfileName: userSettings?.ownerRevolutProfileName ?? "", - showPaymentInstructions: userSettings?.showPaymentInstructionsInMonthlyStatement ?? "disabled", }); // https://revolut.me/aderezic?currency=EUR&amount=70000 - const handleInputChange = (field: keyof typeof formValues, value: string) => { + const handleInputChange = (field: keyof typeof formValues, value: string | boolean) => { setFormValues(prev => ({ ...prev, [field]: value })); }; @@ -106,39 +108,38 @@ const FormFields: FC = ({ userSettings, errors, message }) => { +
- {t("tenant-payment-instructions--legend")} + {t("iban-payment-instructions--legend")} - {t("info-box-message")} + {t("iban-payment-instructions--intro-message")} -
- +
+
- {formValues.showPaymentInstructions === "iban" && ( + + { formValues.enableIbanPayment ? ( <> -
Informacije za uplatu
+
{t("iban-form-title")}
handleInputChange("ownerName", e.target.value)} @@ -156,14 +157,14 @@ const FormFields: FC = ({ userSettings, errors, message }) => {
handleInputChange("ownerStreet", e.target.value)} @@ -181,14 +182,14 @@ const FormFields: FC = ({ userSettings, errors, message }) => {
handleInputChange("ownerTown", e.target.value)} @@ -206,13 +207,13 @@ const FormFields: FC = ({ userSettings, errors, message }) => {
handleInputChange("ownerIBAN", e.target.value)} @@ -229,21 +230,68 @@ const FormFields: FC = ({ userSettings, errors, message }) => {
{t("payment-additional-notes")} - - )} - {formValues.showPaymentInstructions === "revolut" && ( + + ) : // ELSE include hidden inputs to preserve existing values <> -
Informacije za uplatu
+ + + + + + } +
+ +
+ {t("revolut-payment-instructions--legend")} + + {t("revolut-payment-instructions--intro-message")} + +
+ +
+ + { formValues.enableRevolutPayment ? ( + <> +
{t("revolut-form-title")}
handleInputChange("ownerRevolutProfileName", e.target.value)} @@ -260,7 +308,17 @@ const FormFields: FC = ({ userSettings, errors, message }) => {
{t("payment-additional-notes")} - )} + ) + : // ELSE include hidden input to preserve existing value + <> + + + }
diff --git a/app/ui/ViewLocationCard.tsx b/app/ui/ViewLocationCard.tsx index bede467..b2eb33c 100644 --- a/app/ui/ViewLocationCard.tsx +++ b/app/ui/ViewLocationCard.tsx @@ -79,7 +79,7 @@ export const ViewLocationCard:FC = ({location, userSettin const { hub3aText, paymentParams } = useMemo(() => { - if(!userSettings?.show2dCodeInMonthlyStatement || !generateTenantCode) { + if(!userSettings?.enableIbanPayment || !generateTenantCode) { return { hub3aText: "", paymentParams: {} as PaymentParams @@ -107,7 +107,7 @@ export const ViewLocationCard:FC = ({location, userSettin hub3aText: EncodePayment(paymentParams), paymentParams }); - }, [userSettings?.show2dCodeInMonthlyStatement, generateTenantCode, locationName, tenantName, tenantStreet, tenantTown, userSettings, monthlyExpense, yearMonth]); + }, []); return(
@@ -126,7 +126,7 @@ export const ViewLocationCard:FC = ({location, userSettin : null } { - userSettings?.show2dCodeInMonthlyStatement && generateTenantCode ? + userSettings?.enableIbanPayment && generateTenantCode ? <>

{t("payment-info-header")}

    diff --git a/messages/en.json b/messages/en.json index d770e94..61ff01e 100644 --- a/messages/en.json +++ b/messages/en.json @@ -191,28 +191,32 @@ }, "user-settings-form": { "title": "User settings", - "info-box-message": "By activating this option, a 2D barcode will be included in the monthly statement sent to the tenant, allowing them to make a direct payment to your bank account.", - "tenant-2d-code-legend": "TENANT 2D CODE", - "tenant-2d-code-toggle-label": "include 2D code in monthly statements", - "tenant-payment-instructions--legend": "Payment Instructions", - "tenant-payment-instructions--info": "If you enable this option, the monthly statement sent to the tenant will include payment instructions and a 2D barcode, allowing them to make a direct payment to your bank account.", - "tenant-payment-instructions--show-no-instructions": "🚫 - do not show payment instructions", - "tenant-payment-instructions--show-iban-instructions": "🏦 - show payment instructions for IBAN", - "tenant-payment-instructions--show-revolut-instructions": "🆁 - show payment instructions for Revolut", + "iban-payment-instructions--legend": "Payment to Your IBAN", + "iban-payment-instructions--intro-message": "By activating this option, payment instructions will be included in the monthly statement sent to the tenant, allowing a direct payment via IBAN to be made to your bank account.", + "iban-payment-instructions--toggle-label": "enable IBAN payment instructions", - "owner-name-label": "Your First and Last Name", - "owner-name-placeholder": "enter your first and last name", - "owner-street-label": "Your Street and House Number", - "owner-street-placeholder": "enter your street and house number", - "owner-town-label": "Your Postal Code and Town", - "owner-town-placeholder": "enter your postal code and town", - "owner-iban-label": "IBAN", - "owner-iban-placeholder": "enter your IBAN for receiving payments", + "iban-form-title": "Payment Information for IBAN", + "iban-owner-name-label": "Your First and Last Name", + "iban-owner-name-placeholder": "enter your first and last name", + "iban-owner-street-label": "Your Street and House Number", + "iban-owner-street-placeholder": "enter your street and house number", + "iban-owner-town-label": "Your Postal Code and Town", + "iban-owner-town-placeholder": "enter your postal code and town", + "iban-owner-iban-label": "IBAN", + "iban-owner-iban-placeholder": "enter your IBAN for receiving payments", - "owner-revolut-profile-label": "Revolut profile name", - "owner-revolut-profile-placeholder": "enter your Revolut profile name for receiving payments", - "owner-revolut-profile-tooltip": "You can find your Revolut profile name in the Revolut app under your user profile. It is displayed below your name and starts with the '@' symbol (e.g., '@john123').", + + "revolut-form-title": "Payment Information for Revolut", + "revolut-payment-instructions--legend": "Payment to Your Revolut Profile", + "revolut-payment-instructions--intro-message": "By activating this option, payment instructions will be included in the monthly statement sent to the tenant, allowing a direct payment via Revolut to be made to your Revolut profile.", + "revolut-payment-instructions--toggle-label": "enable Revolut payment instructions", + + "revolut-profile-label": "Revolut profile name", + "revolut-profile-placeholder": "enter your Revolut profile name for receiving payments", + "revolut-profile-tooltip": "You can find your Revolut profile name in the Revolut app under your user profile. It is displayed below your name and starts with the '@' symbol (e.g., '@john123').", + + "payment-additional-notes": "IMPORTANT: For the payment instructions to be displayed to the tenant, you must also enable this option in the property's settings.", "general-settings-legend": "General Settings", "currency-label": "Currency", @@ -226,7 +230,6 @@ "owner-iban-invalid": "Invalid IBAN format. Please enter a valid IBAN", "currency-required": "Currency is mandatory", "validation-failed": "Validation failed. Please check the form and try again." - }, - "payment-additional-notes": "IMPORTANT: For the payment instructions to be displayed to the tenant, you must also enable this option in the property's settings." + } } } \ No newline at end of file diff --git a/messages/hr.json b/messages/hr.json index 285d9ac..43fd3ef 100644 --- a/messages/hr.json +++ b/messages/hr.json @@ -190,23 +190,30 @@ }, "user-settings-form": { "title": "Korisničke postavke", - "info-box-message": "Ako uključite ovu opciju na mjesečnom obračunu koji se šalje podstanaru biti će prikazane upute za uplatu i 2D bar kod, putem kojeg će moći izvršiti izravnu uplatu na vaš bankovni račun.", - "tenant-payment-instructions--legend": "Upute za uplatu", - "tenant-payment-instructions--info": "Ako uključite ovu opciju na mjesečnom obračunu koji se šalje podstanaru biti će prikazane upute za uplatu i 2D bar kod, putem kojeg će moći izvršiti izravnu uplatu na vaš bankovni račun.", - "tenant-payment-instructions--show-no-instructions": "🚫 - Ne prikazivati upute za uplatu", - "tenant-payment-instructions--show-iban-instructions": "🏦 - Prikazuj upute za uplatu na IBAN", - "tenant-payment-instructions--show-revolut-instructions": "🆁 - Prikazuj upute za uplatu na Revolut", - "owner-name-label": "Vaše ime i prezime", - "owner-name-placeholder": "unesite svoje ime i prezime", - "owner-street-label": "Ulica i kućni broj", - "owner-street-placeholder": "unesite ulicu i kućni broj", - "owner-town-label": "Poštanski broj i Grad", - "owner-town-placeholder": "unesite poštanski broj i grad", - "owner-iban-label": "IBAN", - "owner-iban-placeholder": "IBAN putem kojeg ćete primate uplate", - "owner-revolut-profile-label": "Naziv vašeg Revolut profila", - "owner-revolut-profile-placeholder": "profil putem kojeg ćete primati uplate", - "owner-revolut-profile-tooltip": "Naziv vašeg Revolute profila možete pronaći u aplikaciji Revolut u korisničkom profilu. Prikazan je ispod vašeg imena i prezimena - počinje sa znakom '@' (npr: '@ivan123').", + + + "iban-payment-instructions--legend": "Uplata na vaš IBAN", + "iban-payment-instructions--intro-message": "Aktiviranjem ove opcije, upute za uplatu bit će uključene u mjesečni izvještaj poslan podstanaru, omogućujući im da izvrše izravnu uplatu putem IBAN-a na vaš bankovni račun.", + "iban-payment-instructions--toggle-label": "uključi IBAN uplatu", + + "iban-form-title": "Informacije za uplatu na IBAN", + "iban-owner-name-label": "Vaše ime i prezime", + "iban-owner-name-placeholder": "unesite svoje ime i prezime", + "iban-owner-street-label": "Ulica i kućni broj", + "iban-owner-street-placeholder": "unesite ulicu i kućni broj", + "iban-owner-town-label": "Poštanski broj i Grad", + "iban-owner-town-placeholder": "unesite poštanski broj i grad", + "iban-owner-iban-label": "IBAN", + "iban-owner-iban-placeholder": "IBAN putem kojeg ćete primate uplate", + + "revolut-payment-instructions--legend": "Uplata na vaš Revolut profil", + "revolut-payment-instructions--intro-message": "Aktiviranjem ove opcije, upute za uplatu bit će uključene u mjesečni izvještaj poslan podstanaru, omogućujući im da izvrše izravnu uplatu putem Revolut-a na vaš profil.", + "revolut-payment-instructions--toggle-label": "uključi Revolut uplatu", + + "revolut-form-title": "Informacije za uplatu na Revolut", + "revolut-profile-label": "Naziv vašeg Revolut profila", + "revolut-profile-placeholder": "profil putem kojeg ćete primati uplate", + "revolut-profile-tooltip": "Naziv vašeg Revolut profila možete pronaći u aplikaciji Revolut u korisničkom profilu. Prikazan je ispod vašeg imena i prezimena - počinje sa znakom '@' (npr: '@ivan123').", "general-settings-legend": "Opće postavke", "currency-label": "Valuta",