Skip to content

Commit 9702d08

Browse files
feat: Add loading state to Auth forms
- Added `Spinner` component - Updated `Button` component to support `isLoading` prop - Implemented loading state in `Auth` page for login and signup forms - Improves UX by preventing multiple submissions and providing visual feedback
1 parent 22f39f1 commit 9702d08

File tree

4 files changed

+36
-2
lines changed

4 files changed

+36
-2
lines changed

.Jules/palette.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
11
# Palette Journal
2+
3+
## 2025-05-21 - [Feedback on Auth]
4+
**Learning:** Users often click submit multiple times on authentication forms if there's no immediate visual feedback, causing race conditions or frustration.
5+
**Action:** Always add a loading spinner and disable the button immediately upon submission for async auth operations.

web/components/ui/Button.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
import React from 'react';
22
import { THEMES } from '../../constants';
33
import { useTheme } from '../../contexts/ThemeContext';
4+
import { Spinner } from './Spinner';
45

56
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
67
variant?: 'primary' | 'secondary' | 'danger' | 'ghost';
78
size?: 'sm' | 'md' | 'lg';
9+
isLoading?: boolean;
810
}
911

1012
export const Button: React.FC<ButtonProps> = ({
1113
children,
1214
variant = 'primary',
1315
size = 'md',
1416
className = '',
17+
isLoading = false,
1518
...props
1619
}) => {
1720
const { style } = useTheme();
@@ -47,8 +50,11 @@ export const Button: React.FC<ButtonProps> = ({
4750
return (
4851
<button
4952
className={`${baseStyles} ${sizeStyles[size]} ${themeStyles} ${className}`}
53+
disabled={isLoading || props.disabled}
54+
aria-busy={isLoading}
5055
{...props}
5156
>
57+
{isLoading && <Spinner size={size === 'sm' ? 16 : 20} />}
5258
{children}
5359
</button>
5460
);

web/components/ui/Spinner.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import React from 'react';
2+
import { Loader2 } from 'lucide-react';
3+
4+
interface SpinnerProps extends React.SVGProps<SVGSVGElement> {
5+
size?: number | string;
6+
className?: string;
7+
ariaLabel?: string;
8+
}
9+
10+
export const Spinner: React.FC<SpinnerProps> = ({
11+
size = 20,
12+
className = '',
13+
ariaLabel = 'Loading',
14+
...props
15+
}) => {
16+
return (
17+
<Loader2
18+
className={`animate-spin ${className}`}
19+
size={size}
20+
aria-label={ariaLabel}
21+
{...props}
22+
/>
23+
);
24+
};

web/pages/Auth.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -251,8 +251,8 @@ export const Auth = () => {
251251
</motion.div>
252252
)}
253253

254-
<Button type="submit" disabled={loading} className={`w-full py-4 text-lg ${isNeo ? 'rounded-none' : ''}`}>
255-
{loading ? 'Processing...' : isLogin ? 'Log In' : 'Create Account'} <ArrowRight size={20} />
254+
<Button type="submit" isLoading={loading} className={`w-full py-4 text-lg ${isNeo ? 'rounded-none' : ''}`}>
255+
{isLogin ? 'Log In' : 'Create Account'} <ArrowRight size={20} />
256256
</Button>
257257
</form>
258258

0 commit comments

Comments
 (0)