Skip to content

Commit 4242841

Browse files
authored
Merge pull request #12 from pior-labs/chore/cleanup-sign-in-page
Make sign-in SSO-only and add a theme switcher
2 parents 533e9f8 + 7581482 commit 4242841

12 files changed

Lines changed: 189 additions & 330 deletions

File tree

packages/web/src/app.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@
6767
--finlens-accent-shadow: 0 4px 14px -2px color-mix(in srgb, var(--accent) 38%, transparent);
6868
--finlens-focus-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 16%, transparent);
6969

70+
--finlens-action-hover-bg: color-mix(in srgb, var(--ink) 90%, var(--accent));
71+
--finlens-action-shadow: 0 14px 36px -12px color-mix(in srgb, var(--ink) 55%, transparent), inset 0 1px 0 rgba(var(--frost-rgb),0.06);
72+
--finlens-action-shadow-hover: 0 18px 42px -12px color-mix(in srgb, var(--ink) 60%, transparent), inset 0 1px 0 rgba(var(--frost-rgb),0.08);
73+
7074
--finlens-muted-track: color-mix(in srgb, var(--ink) 6%, transparent);
7175
--finlens-soft-divider: color-mix(in srgb, var(--ink) 10%, transparent);
7276
--finlens-category-favorite-bg: color-mix(in srgb, var(--accent) 18%, rgba(var(--surface-rgb),1) 82%);
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { useTheme } from '@/hooks/useTheme';
2+
3+
/**
4+
* Compact theme picker for pages without an account menu. Shows the same colour
5+
* swatches as ThemeSwitcher, but as a segmented pill rather than a labelled list.
6+
*/
7+
export function ThemeToggle({ className = '' }: { className?: string }) {
8+
const { theme, setTheme, themes } = useTheme();
9+
10+
return (
11+
<div
12+
role="group"
13+
aria-label="Theme"
14+
className={`inline-flex items-center gap-1 rounded-full border border-ink/15 bg-frost/50 p-1 shadow-[inset_0_1px_0_rgba(var(--frost-rgb),0.6)] backdrop-blur-md ${className}`}
15+
>
16+
{themes.map((option) => {
17+
const active = option.id === theme;
18+
return (
19+
<button
20+
key={option.id}
21+
type="button"
22+
onClick={() => setTheme(option.id)}
23+
aria-pressed={active}
24+
aria-label={`${option.name} theme`}
25+
title={`${option.name}${option.hint}`}
26+
className={[
27+
'flex h-7 w-7 cursor-pointer items-center justify-center rounded-full border-0 transition-[background-color,opacity,transform] duration-200',
28+
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/45 focus-visible:ring-offset-2 focus-visible:ring-offset-cream',
29+
active
30+
? 'bg-ink/10 opacity-100'
31+
: 'bg-transparent opacity-55 hover:opacity-100 motion-safe:hover:scale-105',
32+
].join(' ')}
33+
>
34+
<span
35+
aria-hidden="true"
36+
className={[
37+
'flex h-5 w-5 shrink-0 items-center justify-center rounded-full transition-shadow duration-200',
38+
active
39+
? 'shadow-[inset_0_0_0_1px_rgba(var(--frost-rgb),0.5),0_0_0_1.5px_var(--accent)]'
40+
: 'shadow-[inset_0_0_0_1px_rgba(var(--frost-rgb),0.5)]',
41+
].join(' ')}
42+
style={{ background: option.swatch[0] }}
43+
>
44+
<span className="flex h-2.5 w-2.5 overflow-hidden rounded-full">
45+
<span className="h-full w-1/2" style={{ background: option.swatch[1] }} />
46+
<span className="h-full w-1/2" style={{ background: option.swatch[2] }} />
47+
</span>
48+
</span>
49+
</button>
50+
);
51+
})}
52+
</div>
53+
);
54+
}

packages/web/src/hooks/useAuth.tsx

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ export interface AuthUser {
99
interface AuthContextValue {
1010
user: AuthUser | null;
1111
loading: boolean;
12-
login: (email: string, password: string) => Promise<void>;
1312
loginWithSSO: () => Promise<void>;
1413
logout: () => Promise<void>;
1514
refresh: () => Promise<void>;
@@ -78,35 +77,6 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
7877
void refresh();
7978
}, []);
8079

81-
const login = async (email: string, password: string): Promise<void> => {
82-
const response = await fetch('/api/auth/sign-in/email', {
83-
method: 'POST',
84-
credentials: 'include',
85-
headers: {
86-
'Content-Type': 'application/json'
87-
},
88-
body: JSON.stringify({ email, password })
89-
});
90-
91-
if (!response.ok) {
92-
const payload = await parseJson(response);
93-
throw new Error(toErrorMessage(payload, `Request failed: ${response.status}`));
94-
}
95-
96-
const payload = await parseJson<{ user?: { id: string | number; name: string; email: string } }>(response);
97-
98-
if (!payload.user) {
99-
await refresh();
100-
return;
101-
}
102-
103-
setUser({
104-
id: Number(payload.user.id),
105-
name: payload.user.name,
106-
email: payload.user.email
107-
});
108-
};
109-
11080
const loginWithSSO = async (): Promise<void> => {
11181
const response = await fetch('/api/auth/sign-in/oauth2', {
11282
method: 'POST',
@@ -154,7 +124,6 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
154124
() => ({
155125
user,
156126
loading,
157-
login,
158127
loginWithSSO,
159128
logout,
160129
refresh

packages/web/src/pages/login/components/BrandPanel.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { BrandMark } from '@/components/BrandMark';
2+
import { ThemeToggle } from '@/components/ThemeToggle';
23

34
export function BrandPanel() {
45
return (
@@ -26,6 +27,8 @@ export function BrandPanel() {
2627
A shared, unhurried view of your household — statements, categories, and the small
2728
patterns that add up over a year.
2829
</p>
30+
31+
<ThemeToggle className="mt-8" />
2932
</div>
3033

3134
<div aria-hidden="true" />

packages/web/src/pages/login/components/LoginField.tsx

Lines changed: 0 additions & 52 deletions
This file was deleted.

packages/web/src/pages/login/components/LoginFormPanel.tsx

Lines changed: 0 additions & 115 deletions
This file was deleted.

packages/web/src/pages/login/components/LoginSubmitButton.tsx

Lines changed: 0 additions & 36 deletions
This file was deleted.
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { LoginErrorMessage } from './LoginErrorMessage';
2+
import { SsoSignInButton } from './SsoSignInButton';
3+
4+
type SignInPanelProps = {
5+
loading: boolean;
6+
error: string | null;
7+
errorId: string;
8+
onSsoLogin: () => void;
9+
};
10+
11+
export function SignInPanel({ loading, error, errorId, onSsoLogin }: SignInPanelProps) {
12+
return (
13+
<section
14+
aria-labelledby="login-form-heading"
15+
className="theme-overlay-anim flex w-full items-center justify-center py-2 md:py-10"
16+
style={{ animationDelay: '120ms' }}
17+
>
18+
<div className="relative w-full max-w-96">
19+
<div className="theme-glass relative rounded-[28px] p-7 sm:p-9">
20+
<p className="font-serif text-[11px] uppercase tracking-[0.24em] text-ink-2">
21+
Single sign-on
22+
</p>
23+
24+
<h2
25+
id="login-form-heading"
26+
className="mt-3 font-serif text-[26px] leading-[1.05] tracking-tight text-ink"
27+
>
28+
Sign <span className="italic text-accent">in</span>
29+
</h2>
30+
31+
<div className="mt-5 flex flex-col gap-6">
32+
<p className="text-[15px] leading-[1.65] text-ink-2">
33+
finlens uses your Pior Labs account. You’ll sign in there, then land right back here.
34+
</p>
35+
36+
<LoginErrorMessage id={errorId} error={error} />
37+
38+
<SsoSignInButton
39+
loading={loading}
40+
errorId={errorId}
41+
hasError={Boolean(error)}
42+
onClick={onSsoLogin}
43+
/>
44+
</div>
45+
</div>
46+
</div>
47+
</section>
48+
);
49+
}

0 commit comments

Comments
 (0)