|
1 | 1 | 'use client'; |
2 | 2 |
|
3 | 3 | import { useState } from 'react'; |
| 4 | +import Link from 'next/link'; |
| 5 | +import { Button } from '@/components/ui/button'; |
| 6 | +import { Input } from '@/components/ui/input'; |
| 7 | +import { Label } from '@/components/ui/label'; |
| 8 | +import { Alert, AlertDescription } from '@/components/ui/alert'; |
| 9 | +import { AlertCircle, Loader2 } from 'lucide-react'; |
4 | 10 |
|
5 | 11 | export default function LoginForm() { |
6 | 12 | const [identifier, setIdentifier] = useState(''); |
@@ -40,99 +46,102 @@ export default function LoginForm() { |
40 | 46 | }; |
41 | 47 |
|
42 | 48 | return ( |
43 | | - <form onSubmit={handleSubmit} className="w-full max-w-md space-y-4"> |
44 | | - <div> |
45 | | - <label |
46 | | - htmlFor="identifier" |
47 | | - className="block text-sm font-medium text-gray-700 mb-2" |
48 | | - > |
49 | | - Username or Handle |
50 | | - </label> |
51 | | - <input |
52 | | - type="text" |
53 | | - id="identifier" |
54 | | - value={identifier} |
55 | | - onChange={(e) => setIdentifier(e.target.value)} |
56 | | - placeholder="username.bsky.social or username" |
57 | | - className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" |
58 | | - required |
59 | | - disabled={loading} |
60 | | - autoComplete="username" |
61 | | - /> |
62 | | - <p className="mt-1 text-sm text-gray-500"> |
63 | | - Enter your Bluesky handle or username |
64 | | - </p> |
65 | | - </div> |
| 49 | + <form onSubmit={handleSubmit} className="flex flex-col gap-6"> |
| 50 | + <div className="flex flex-col gap-4"> |
| 51 | + <div className="flex flex-col gap-2"> |
| 52 | + <Label htmlFor="identifier" className="leading-5"> |
| 53 | + Username or Handle |
| 54 | + </Label> |
| 55 | + <Input |
| 56 | + type="text" |
| 57 | + id="identifier" |
| 58 | + value={identifier} |
| 59 | + onChange={(e) => setIdentifier(e.target.value)} |
| 60 | + placeholder="username.bsky.social" |
| 61 | + required |
| 62 | + disabled={loading} |
| 63 | + autoComplete="username" |
| 64 | + aria-describedby="identifier-help" |
| 65 | + /> |
| 66 | + <p |
| 67 | + id="identifier-help" |
| 68 | + className="text-sm leading-5 text-muted-foreground" |
| 69 | + > |
| 70 | + Enter your Bluesky handle or username |
| 71 | + </p> |
| 72 | + </div> |
66 | 73 |
|
67 | | - <div> |
68 | | - <label |
69 | | - htmlFor="password" |
70 | | - className="block text-sm font-medium text-gray-700 mb-2" |
71 | | - > |
72 | | - App Password |
73 | | - </label> |
74 | | - <input |
75 | | - type="password" |
76 | | - id="password" |
77 | | - value={password} |
78 | | - onChange={(e) => setPassword(e.target.value)} |
79 | | - placeholder="xxxx-xxxx-xxxx-xxxx" |
80 | | - className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" |
81 | | - required |
82 | | - disabled={loading} |
83 | | - autoComplete="current-password" |
84 | | - /> |
85 | | - <p className="mt-1 text-sm text-gray-500"> |
86 | | - Get your app password from{' '} |
87 | | - <a |
88 | | - href="https://bsky.app/settings/app-passwords" |
89 | | - target="_blank" |
90 | | - rel="noopener noreferrer" |
91 | | - className="text-blue-600 hover:underline" |
| 74 | + <div className="flex flex-col gap-2"> |
| 75 | + <Label htmlFor="password" className="leading-5"> |
| 76 | + App Password |
| 77 | + </Label> |
| 78 | + <Input |
| 79 | + type="password" |
| 80 | + id="password" |
| 81 | + value={password} |
| 82 | + onChange={(e) => setPassword(e.target.value)} |
| 83 | + placeholder="xxxx-xxxx-xxxx-xxxx" |
| 84 | + required |
| 85 | + disabled={loading} |
| 86 | + autoComplete="current-password" |
| 87 | + aria-describedby="password-help" |
| 88 | + /> |
| 89 | + <p |
| 90 | + id="password-help" |
| 91 | + className="text-sm leading-5 text-muted-foreground" |
92 | 92 | > |
93 | | - Bluesky settings |
94 | | - </a> |
95 | | - </p> |
96 | | - </div> |
| 93 | + Get your app password from{' '} |
| 94 | + <Link |
| 95 | + href="https://bsky.app/settings/app-passwords" |
| 96 | + target="_blank" |
| 97 | + rel="noopener noreferrer" |
| 98 | + className="text-primary underline-offset-4 hover:underline" |
| 99 | + > |
| 100 | + Bluesky settings |
| 101 | + </Link> |
| 102 | + </p> |
| 103 | + </div> |
97 | 104 |
|
98 | | - <div> |
99 | | - <label |
100 | | - htmlFor="pdsUrl" |
101 | | - className="block text-sm font-medium text-gray-700 mb-2" |
102 | | - > |
103 | | - PDS Server (optional) |
104 | | - </label> |
105 | | - <input |
106 | | - type="url" |
107 | | - id="pdsUrl" |
108 | | - value={pdsUrl} |
109 | | - onChange={(e) => setPdsUrl(e.target.value)} |
110 | | - placeholder="https://bsky.social" |
111 | | - className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" |
112 | | - disabled={loading} |
113 | | - /> |
114 | | - <p className="mt-1 text-sm text-gray-500"> |
115 | | - Defaults to bsky.social - only change if using a custom PDS |
116 | | - </p> |
| 105 | + <div className="flex flex-col gap-2"> |
| 106 | + <Label htmlFor="pdsUrl" className="leading-5"> |
| 107 | + PDS Server (optional) |
| 108 | + </Label> |
| 109 | + <Input |
| 110 | + type="url" |
| 111 | + id="pdsUrl" |
| 112 | + value={pdsUrl} |
| 113 | + onChange={(e) => setPdsUrl(e.target.value)} |
| 114 | + placeholder="https://bsky.social" |
| 115 | + disabled={loading} |
| 116 | + aria-describedby="pds-help" |
| 117 | + /> |
| 118 | + <p id="pds-help" className="text-sm leading-5 text-muted-foreground"> |
| 119 | + Defaults to bsky.social - only change if using a custom PDS |
| 120 | + </p> |
| 121 | + </div> |
117 | 122 | </div> |
118 | 123 |
|
119 | 124 | {error && ( |
120 | | - <div className="p-3 bg-red-50 border border-red-200 rounded-lg text-red-700 text-sm"> |
121 | | - {error} |
122 | | - </div> |
| 125 | + <Alert variant="destructive"> |
| 126 | + <AlertCircle className="h-4 w-4" /> |
| 127 | + <AlertDescription className="leading-5">{error}</AlertDescription> |
| 128 | + </Alert> |
123 | 129 | )} |
124 | 130 |
|
125 | | - <button |
| 131 | + <Button |
126 | 132 | type="submit" |
127 | 133 | disabled={loading || !identifier || !password} |
128 | | - className="w-full bg-blue-600 text-white py-3 px-6 rounded-lg font-medium hover:bg-blue-700 disabled:bg-gray-300 disabled:cursor-not-allowed transition-colors" |
| 134 | + className="w-full" |
129 | 135 | > |
130 | | - {loading ? 'Signing in...' : 'Sign in'} |
131 | | - </button> |
132 | | - |
133 | | - <p className="text-xs text-gray-500 text-center"> |
134 | | - Sign in to access your Lanyards profile |
135 | | - </p> |
| 136 | + {loading ? ( |
| 137 | + <> |
| 138 | + <Loader2 className="h-4 w-4 animate-spin" /> |
| 139 | + Signing in... |
| 140 | + </> |
| 141 | + ) : ( |
| 142 | + 'Sign in' |
| 143 | + )} |
| 144 | + </Button> |
136 | 145 | </form> |
137 | 146 | ); |
138 | 147 | } |
0 commit comments