diff --git a/.Jules/palette.md b/.Jules/palette.md new file mode 100644 index 0000000..ed75972 --- /dev/null +++ b/.Jules/palette.md @@ -0,0 +1,7 @@ +## 2024-05-22 - Replacing Alerts with Inline Error States +**Learning:** Browser alerts are disruptive and non-accessible. Using state-driven inline error messages allows for a more integrated UX and better accessibility (using ARIA roles like 'alert'). +**Action:** Always prefer inline validation messages with proper ARIA attributes over browser native dialogs for form validation. + +## 2024-05-22 - Derived State Pattern for Syncing Props +**Learning:** When a child component needs to manage a prop's value locally (e.g., clearing an error message), use a 'derived state' pattern with a tracker variable to ensure prop updates are still captured without causing infinite loops or missing updates. +**Action:** Use the tracker pattern (prevProp) inside the component body to sync external props to local state when local overrides are possible. diff --git a/App.tsx b/App.tsx index 28169f5..9a8f1e7 100644 --- a/App.tsx +++ b/App.tsx @@ -21,6 +21,7 @@ const PERSONA_STORAGE_KEY = 'wohnprojekt_persona_cache'; const App: React.FC = () => { const [showLanding, setShowLanding] = useState(true); const [currentUser, setCurrentUser] = useState(null); + const [loginError, setLoginError] = useState(''); const [currentView, setCurrentView] = useState('chat'); const [isSidebarOpen, setIsSidebarOpen] = useState(false); @@ -100,8 +101,9 @@ const App: React.FC = () => { const user = users.find(u => u.email.toLowerCase() === email.toLowerCase() && u.status === 'aktiv'); if (user) { setCurrentUser(user); + setLoginError(''); } else { - alert("Zugang verweigert. Nur verifizierte Wohnpro-Bewohner können sich im Wohnpro Guide anmelden."); + setLoginError("Zugang verweigert. Nur verifizierte Wohnpro-Bewohner können sich im Wohnpro Guide anmelden."); } }; @@ -256,7 +258,7 @@ const App: React.FC = () => { Synchronisiere mit Nextcloud... )} - + ); } diff --git a/components/LoginView.tsx b/components/LoginView.tsx index a7c3c0b..1550f2e 100644 --- a/components/LoginView.tsx +++ b/components/LoginView.tsx @@ -10,6 +10,13 @@ interface LoginViewProps { const LoginView: React.FC = ({ onLogin, error: externalError }) => { const [email, setEmail] = useState(''); const [error, setError] = useState(externalError || ''); + const [prevExternalError, setPrevExternalError] = useState(externalError); + + // Derived state pattern to sync external error prop + if (externalError !== prevExternalError) { + setError(externalError || ''); + setPrevExternalError(externalError); + } const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); @@ -36,14 +43,24 @@ const LoginView: React.FC = ({ onLogin, error: externalError })
+ { setEmail(e.target.value); setError(''); }} placeholder="Wohnpro E-Mail Adresse" + aria-invalid={!!error} + aria-describedby={error ? "email-error" : undefined} className={`w-full bg-gray-50 border ${error ? 'border-red-200' : 'border-gray-100'} rounded-2xl px-6 py-4 focus:outline-none focus:ring-2 focus:ring-black/5 transition-all text-lg placeholder:text-gray-300`} /> - {error &&

{error}

} + {error && ( + + )}