Skip to content
This repository was archived by the owner on Apr 24, 2026. It is now read-only.

Commit 99c5db2

Browse files
authored
Merge pull request #35 from renderghost/feature/issue-33-auth-page-shadcn
Feature #33: Refactor auth page with shadcn/ui
2 parents 8a2aca0 + d4b29ee commit 99c5db2

2 files changed

Lines changed: 122 additions & 102 deletions

File tree

‎src/app/auth/page.tsx‎

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,53 @@
11
import LoginForm from '@/components/auth/LoginForm';
2+
import {
3+
Card,
4+
CardContent,
5+
CardDescription,
6+
CardHeader,
7+
CardTitle,
8+
} from '@/components/ui/card';
9+
import Link from 'next/link';
210

311
export default function AuthPage() {
412
return (
5-
<main className="flex min-h-screen flex-col items-center justify-center p-6 bg-gray-50">
6-
<div className="w-full max-w-md">
7-
<div className="text-center mb-8">
8-
<h1 className="text-4xl font-bold mb-2">Lanyards</h1>
9-
<p className="text-gray-600">
10-
Sign in with your Bluesky app password
11-
</p>
12-
</div>
13+
<main className="flex min-h-screen flex-col items-center justify-center p-6">
14+
<div className="flex w-full max-w-md flex-col gap-6">
15+
<Card>
16+
<CardHeader className="text-center">
17+
<CardTitle className="text-2xl leading-8">
18+
Sign in to Lanyards
19+
</CardTitle>
20+
<CardDescription className="leading-5">
21+
Use your Bluesky app password to continue
22+
</CardDescription>
23+
</CardHeader>
24+
<CardContent>
25+
<LoginForm />
26+
</CardContent>
27+
</Card>
1328

14-
<div className="bg-white p-8 rounded-xl shadow-sm">
15-
<LoginForm />
16-
</div>
17-
18-
<div className="mt-6 text-center text-sm text-gray-500">
29+
<div className="flex flex-col gap-2 text-center text-sm leading-5 text-muted-foreground">
1930
<p>
2031
Don&apos;t have a Bluesky account?{' '}
21-
<a
32+
<Link
2233
href="https://bsky.app"
2334
target="_blank"
2435
rel="noopener noreferrer"
25-
className="text-blue-600 hover:underline"
36+
className="text-primary underline-offset-4 hover:underline"
2637
>
2738
Create one here
28-
</a>
39+
</Link>
2940
</p>
30-
<p className="mt-2">
41+
<p>
3142
Need an app password?{' '}
32-
<a
43+
<Link
3344
href="https://bsky.app/settings/app-passwords"
3445
target="_blank"
3546
rel="noopener noreferrer"
36-
className="text-blue-600 hover:underline"
47+
className="text-primary underline-offset-4 hover:underline"
3748
>
3849
Generate one in settings
39-
</a>
50+
</Link>
4051
</p>
4152
</div>
4253
</div>

‎src/components/auth/LoginForm.tsx‎

Lines changed: 91 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
'use client';
22

33
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';
410

511
export default function LoginForm() {
612
const [identifier, setIdentifier] = useState('');
@@ -40,99 +46,102 @@ export default function LoginForm() {
4046
};
4147

4248
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>
6673

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"
9292
>
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>
97104

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>
117122
</div>
118123

119124
{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>
123129
)}
124130

125-
<button
131+
<Button
126132
type="submit"
127133
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"
129135
>
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>
136145
</form>
137146
);
138147
}

0 commit comments

Comments
 (0)