Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .Jules/palette.md
Original file line number Diff line number Diff line change
@@ -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.
6 changes: 4 additions & 2 deletions App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const PERSONA_STORAGE_KEY = 'wohnprojekt_persona_cache';
const App: React.FC = () => {
const [showLanding, setShowLanding] = useState(true);
const [currentUser, setCurrentUser] = useState<User | null>(null);
const [loginError, setLoginError] = useState<string>('');
const [currentView, setCurrentView] = useState<View>('chat');
const [isSidebarOpen, setIsSidebarOpen] = useState(false);

Expand Down Expand Up @@ -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.");
}
};

Expand Down Expand Up @@ -256,7 +258,7 @@ const App: React.FC = () => {
Synchronisiere mit Nextcloud...
</div>
)}
<LoginView onLogin={handleLogin} />
<LoginView onLogin={handleLogin} error={loginError} />
</>
);
}
Expand Down
19 changes: 18 additions & 1 deletion components/LoginView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ interface LoginViewProps {
const LoginView: React.FC<LoginViewProps> = ({ 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();
Expand All @@ -36,14 +43,24 @@ const LoginView: React.FC<LoginViewProps> = ({ onLogin, error: externalError })

<form onSubmit={handleSubmit} className="space-y-4">
<div className="relative group">
<label htmlFor="email-input" className="sr-only">
Wohnpro E-Mail Adresse
</label>
<input
id="email-input"
type="email"
value={email}
onChange={(e) => { setEmail(e.target.value); setError(''); }}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve failed-login feedback after input edits

Clearing only the local error here breaks repeat failure feedback: after one rejected login, typing in the field removes the message, and a second rejected login with the same backend message often won't reappear because the parent loginError value is unchanged and the prop-sync guard (externalError !== prevExternalError) never runs. In that flow, users can keep getting denied without seeing any error text.

Useful? React with 👍 / 👎.

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 && <p className="mt-2 text-sm text-red-500 text-left px-2">{error}</p>}
{error && (
<p id="email-error" role="alert" className="mt-2 text-sm text-red-500 text-left px-2">
{error}
</p>
)}
</div>

<button
Expand Down