Add real-time warning for missing profile data in account form

Added conditional warning alert that displays when required fields are
empty, updating in real-time as the user types:
- Tracks form values with useState and onChange handlers
- Displays warning when firstName, lastName, address, or IBAN is missing
- Warning appears/disappears instantly as user fills or clears fields
- Explains that 2D barcode won't be shown to tenants until all data is complete
- Added translations in English and Croatian

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Knee Cola
2025-11-17 20:33:40 +01:00
parent 216b08c12b
commit 6d6c65d4e3
3 changed files with 31 additions and 1 deletions

View File

@@ -1,6 +1,6 @@
"use client"; "use client";
import { FC } from "react"; import { FC, useState } from "react";
import { UserProfile } from "../lib/db-types"; import { UserProfile } from "../lib/db-types";
import { updateUserProfile } from "../lib/actions/userProfileActions"; import { updateUserProfile } from "../lib/actions/userProfileActions";
import { useFormState, useFormStatus } from "react-dom"; import { useFormState, useFormStatus } from "react-dom";
@@ -23,6 +23,21 @@ const FormFields: FC<FormFieldsProps> = ({ profile, errors, message }) => {
const t = useTranslations("account-form"); const t = useTranslations("account-form");
const locale = useLocale(); const locale = useLocale();
// Track current form values for real-time validation
const [formValues, setFormValues] = useState({
firstName: profile?.firstName ?? "",
lastName: profile?.lastName ?? "",
address: profile?.address ?? "",
iban: profile?.iban ?? "",
});
const handleInputChange = (field: keyof typeof formValues, value: string) => {
setFormValues(prev => ({ ...prev, [field]: value }));
};
// Check if any required field is missing
const hasMissingData = !formValues.firstName || !formValues.lastName || !formValues.address || !formValues.iban;
return ( return (
<> <>
<div className="alert max-w-md flex flex-row items-start"> <div className="alert max-w-md flex flex-row items-start">
@@ -42,6 +57,7 @@ const FormFields: FC<FormFieldsProps> = ({ profile, errors, message }) => {
placeholder={t("first-name-placeholder")} placeholder={t("first-name-placeholder")}
className="input input-bordered w-full" className="input input-bordered w-full"
defaultValue={profile?.firstName ?? ""} defaultValue={profile?.firstName ?? ""}
onChange={(e) => handleInputChange("firstName", e.target.value)}
disabled={pending} disabled={pending}
/> />
<div id="firstName-error" aria-live="polite" aria-atomic="true"> <div id="firstName-error" aria-live="polite" aria-atomic="true">
@@ -65,6 +81,7 @@ const FormFields: FC<FormFieldsProps> = ({ profile, errors, message }) => {
placeholder={t("last-name-placeholder")} placeholder={t("last-name-placeholder")}
className="input input-bordered w-full" className="input input-bordered w-full"
defaultValue={profile?.lastName ?? ""} defaultValue={profile?.lastName ?? ""}
onChange={(e) => handleInputChange("lastName", e.target.value)}
disabled={pending} disabled={pending}
/> />
<div id="lastName-error" aria-live="polite" aria-atomic="true"> <div id="lastName-error" aria-live="polite" aria-atomic="true">
@@ -87,6 +104,7 @@ const FormFields: FC<FormFieldsProps> = ({ profile, errors, message }) => {
className="textarea textarea-bordered w-full" className="textarea textarea-bordered w-full"
placeholder={t("address-placeholder")} placeholder={t("address-placeholder")}
defaultValue={profile?.address ?? ""} defaultValue={profile?.address ?? ""}
onChange={(e) => handleInputChange("address", e.target.value)}
disabled={pending} disabled={pending}
></textarea> ></textarea>
<div id="address-error" aria-live="polite" aria-atomic="true"> <div id="address-error" aria-live="polite" aria-atomic="true">
@@ -110,6 +128,7 @@ const FormFields: FC<FormFieldsProps> = ({ profile, errors, message }) => {
placeholder={t("iban-placeholder")} placeholder={t("iban-placeholder")}
className="input input-bordered w-full" className="input input-bordered w-full"
defaultValue={profile?.iban ?? ""} defaultValue={profile?.iban ?? ""}
onChange={(e) => handleInputChange("iban", e.target.value)}
disabled={pending} disabled={pending}
/> />
<div id="iban-error" aria-live="polite" aria-atomic="true"> <div id="iban-error" aria-live="polite" aria-atomic="true">
@@ -130,6 +149,15 @@ const FormFields: FC<FormFieldsProps> = ({ profile, errors, message }) => {
)} )}
</div> </div>
{hasMissingData && (
<div className="alert mt-4 max-w-md flex flex-row items-start">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" className="stroke-current shrink-0 w-6 h-6">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<span className="text-left">{t("warning-missing-data")}</span>
</div>
)}
<div className="pt-4"> <div className="pt-4">
<button className="btn btn-primary w-[5.5em]" disabled={pending}> <button className="btn btn-primary w-[5.5em]" disabled={pending}>
{pending ? ( {pending ? (

View File

@@ -139,6 +139,7 @@
"account-form": { "account-form": {
"title": "Profile Information", "title": "Profile Information",
"info-box-message": "This information will be used to generate a 2D barcode displayed in the bill view, allowing tenants to scan it and refund the money you have spent on paying utility bills in their name.", "info-box-message": "This information will be used to generate a 2D barcode displayed in the bill view, allowing tenants to scan it and refund the money you have spent on paying utility bills in their name.",
"warning-missing-data": "Warning: Some profile fields are missing. The 2D barcode will not be displayed to tenants on the shared bill view until all fields are filled in.",
"first-name-label": "First Name", "first-name-label": "First Name",
"first-name-placeholder": "Enter your first name", "first-name-placeholder": "Enter your first name",
"last-name-label": "Last Name", "last-name-label": "Last Name",

View File

@@ -138,6 +138,7 @@
"account-form": { "account-form": {
"title": "Podaci o profilu", "title": "Podaci o profilu",
"info-box-message": "Ovi podaci će se koristiti za generiranje 2D barkoda koji će biti prikazan u pregledu računa, omogućujući podstanarima da ga skeniraju i vrate novac koji ste potrošili na plaćanje režija u njihovo ime.", "info-box-message": "Ovi podaci će se koristiti za generiranje 2D barkoda koji će biti prikazan u pregledu računa, omogućujući podstanarima da ga skeniraju i vrate novac koji ste potrošili na plaćanje režija u njihovo ime.",
"warning-missing-data": "Upozorenje: Neki podaci profila nedostaju. 2D barkod neće biti prikazan podstanarima u podijeljenom pregledu računa dok sva polja ne budu popunjena.",
"first-name-label": "Ime", "first-name-label": "Ime",
"first-name-placeholder": "Unesite svoje ime", "first-name-placeholder": "Unesite svoje ime",
"last-name-label": "Prezime", "last-name-label": "Prezime",