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
3 changes: 3 additions & 0 deletions .Jules/palette.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 2024-05-22 - [Login Accessibility & Loading States]
**Learning:** Replacing browser `alert()` with managed state in the Login component allows for better UX (loading spinners, inline error messages) and improved accessibility (ARIA attributes).
**Action:** Always prefer inline validation over `alert()` for better user experience and screen reader support. Use `sr-only` for hidden labels.
4 changes: 2 additions & 2 deletions App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,9 @@ const App: React.FC = () => {
const user = users.find(u => u.email.toLowerCase() === email.toLowerCase() && u.status === 'aktiv');
if (user) {
setCurrentUser(user);
} else {
alert("Zugang verweigert. Nur verifizierte Wohnpro-Bewohner können sich im Wohnpro Guide anmelden.");
return true;
}
return false;
};

const sendMessage = async (text: string) => {
Expand Down
2 changes: 2 additions & 0 deletions components/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -164,12 +164,14 @@ const ChatView: React.FC<ChatViewProps> = ({ messages, onSendMessage, onEnterVoi
onClick={onEnterVoice}
className="p-3.5 text-gray-400 hover:text-green-600 transition-all rounded-full hover:bg-gray-50 active:scale-90"
title="Sprachmodus"
aria-label="Sprachmodus starten"
>
<MicIcon className="w-7 h-7" />
</button>
<button
type="submit"
disabled={!input.trim() || isLoading}
aria-label="Nachricht senden"
className={`p-4 rounded-full transition-all active:scale-90 ${
input.trim() && !isLoading
? 'bg-black text-white shadow-xl shadow-black/20 hover:bg-gray-900'
Expand Down
4 changes: 3 additions & 1 deletion components/Icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@ import {
HelpCircle,
ThumbsUp,
ThumbsDown,
Download
Download,
Loader2
} from 'lucide-react';

export const ChatIcon = ({ className = "w-6 h-6" }) => <MessageSquare className={className} />;
export const LoadingIcon = ({ className = "w-6 h-6" }) => <Loader2 className={`animate-spin ${className}`} />;
export const MicIcon = ({ className = "w-6 h-6" }) => <Mic className={className} />;
export const DocIcon = ({ className = "w-6 h-6" }) => <FileText className={className} />;
export const SettingsIcon = ({ className = "w-6 h-6" }) => <Settings className={className} />;
Expand Down
41 changes: 33 additions & 8 deletions components/LoginView.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,31 @@

import React, { useState } from 'react';
import { User } from '../types';
import { LoadingIcon } from './Icons';

interface LoginViewProps {
onLogin: (email: string) => void;
onLogin: (email: string) => boolean;
error?: string;
}

const LoginView: React.FC<LoginViewProps> = ({ onLogin, error: externalError }) => {
const [email, setEmail] = useState('');
const [error, setError] = useState(externalError || '');
const [isLoading, setIsLoading] = useState(false);

const handleSubmit = (e: React.FormEvent) => {
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!email) {
setError('Bitte gib deine Wohnpro E-Mail Adresse ein.');
return;
}
onLogin(email);

setIsLoading(true);
const success = onLogin(email);
if (!success) {
setError('Zugang verweigert. Nur verifizierte Wohnpro-Bewohner können sich anmelden.');
setIsLoading(false);
}
};

return (
Expand All @@ -35,22 +43,39 @@ const LoginView: React.FC<LoginViewProps> = ({ onLogin, error: externalError })
</div>

<form onSubmit={handleSubmit} className="space-y-4">
<div className="relative group">
<div className="relative group text-left">
<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(''); }}
placeholder="Wohnpro E-Mail Adresse"
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`}
disabled={isLoading}
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 disabled:opacity-50`}
/>
{error && <p className="mt-2 text-sm text-red-500 text-left px-2">{error}</p>}
{error && (
<p id="email-error" className="mt-2 text-sm text-red-500 px-2 animate-in fade-in slide-in-from-top-1">
{error}
</p>
)}
</div>

<button
type="submit"
className="w-full bg-black text-white rounded-2xl py-4 font-semibold text-lg hover:bg-gray-900 active:scale-[0.98] transition-all shadow-lg shadow-black/5"
disabled={isLoading}
className="w-full bg-black text-white rounded-2xl py-4 font-semibold text-lg hover:bg-gray-900 active:scale-[0.98] transition-all shadow-lg shadow-black/5 flex items-center justify-center gap-2 disabled:opacity-70 disabled:active:scale-100"
>
Guide öffnen
{isLoading ? (
<>
<LoadingIcon className="w-5 h-5" />
<span className="sr-only">Wird geladen...</span>
</>
) : (
'Guide öffnen'
)}
</button>
</form>

Expand Down
2 changes: 1 addition & 1 deletion components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const Sidebar: React.FC<SidebarProps> = ({ isOpen, onClose, sessions, currentVie
<div className="flex flex-col h-full p-6">
<div className="flex items-center justify-between mb-8 lg:hidden">
<span className="font-semibold text-xl tracking-tight">Wohnpro Guide</span>
<button onClick={onClose} className="p-2 hover:bg-gray-100 rounded-full">
<button onClick={onClose} className="p-2 hover:bg-gray-100 rounded-full" aria-label="Menü schließen">
<CloseIcon className="w-5 h-5" />
</button>
</div>
Expand Down