Skip to content

Commit e4c77d9

Browse files
committed
fix(auth): add /forgot-password route to public routes and update login/register pages layout
1 parent 2f7dc03 commit e4c77d9

3 files changed

Lines changed: 162 additions & 122 deletions

File tree

apps/studio/src/routes/__root.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { useObjectStackClient } from '../hooks/useObjectStackClient';
1515
import { SessionProvider, useSession } from '../hooks/useSession';
1616

1717
/** Routes that don't require authentication. */
18-
const PUBLIC_ROUTES = new Set(['/login', '/register']);
18+
const PUBLIC_ROUTES = new Set(['/login', '/register', '/forgot-password']);
1919

2020
/**
2121
* Routes where an environment selection is NOT required.

apps/studio/src/routes/login.tsx

Lines changed: 70 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@ import { createFileRoute, Link, useNavigate } from '@tanstack/react-router';
44
import { useEffect, useState } from 'react';
55
import { useClient } from '@objectstack/client-react';
66
import { Button } from '@/components/ui/button';
7-
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
7+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
88
import { Input } from '@/components/ui/input';
99
import { Label } from '@/components/ui/label';
1010
import { toast } from '@/hooks/use-toast';
1111
import { useSession } from '@/hooks/useSession';
1212
import { useProjects } from '@/hooks/useProjects';
1313
import { SocialSignInButtons } from '@/components/auth/social-sign-in-buttons';
14+
import { GalleryVerticalEnd } from 'lucide-react';
1415

1516
export const Route = createFileRoute('/login')({
1617
component: LoginPage,
@@ -69,51 +70,74 @@ function LoginPage() {
6970
};
7071

7172
return (
72-
<div className="flex flex-1 items-center justify-center bg-background px-4">
73-
<Card className="w-full max-w-sm">
74-
<CardHeader>
75-
<CardTitle>Sign in</CardTitle>
76-
<CardDescription>Access your ObjectStack Studio workspace.</CardDescription>
77-
</CardHeader>
78-
<form onSubmit={handleSubmit}>
79-
<CardContent className="space-y-4">
80-
<SocialSignInButtons mode="sign-in" />
81-
<div className="space-y-1.5">
82-
<Label htmlFor="email">Email</Label>
83-
<Input
84-
id="email"
85-
type="email"
86-
autoComplete="email"
87-
required
88-
value={email}
89-
onChange={(e) => setEmail(e.target.value)}
90-
/>
91-
</div>
92-
<div className="space-y-1.5">
93-
<Label htmlFor="password">Password</Label>
94-
<Input
95-
id="password"
96-
type="password"
97-
autoComplete="current-password"
98-
required
99-
value={password}
100-
onChange={(e) => setPassword(e.target.value)}
101-
/>
102-
</div>
103-
</CardContent>
104-
<CardFooter className="flex flex-col gap-3">
105-
<Button type="submit" className="w-full" disabled={submitting}>
106-
{submitting ? 'Signing in…' : 'Sign in'}
107-
</Button>
108-
<p className="text-xs text-muted-foreground">
109-
No account?{' '}
110-
<Link to="/register" className="text-primary hover:underline">
111-
Create one
112-
</Link>
113-
</p>
114-
</CardFooter>
115-
</form>
116-
</Card>
73+
<div className="flex min-h-svh w-full flex-col items-center justify-center gap-6 bg-muted p-6 md:p-10">
74+
<div className="flex w-full max-w-sm flex-col gap-6">
75+
<a href="#" className="flex items-center gap-2 self-center font-medium">
76+
<div className="flex size-6 items-center justify-center rounded-md bg-primary text-primary-foreground">
77+
<GalleryVerticalEnd className="size-4" />
78+
</div>
79+
ObjectStack
80+
</a>
81+
<div className="flex flex-col gap-6">
82+
<Card>
83+
<CardHeader className="text-center">
84+
<CardTitle className="text-xl">Welcome back</CardTitle>
85+
<CardDescription>Access your ObjectStack Studio workspace.</CardDescription>
86+
</CardHeader>
87+
<CardContent>
88+
<form onSubmit={handleSubmit}>
89+
<div className="flex flex-col gap-4">
90+
<SocialSignInButtons mode="sign-in" />
91+
<div className="flex flex-col gap-2">
92+
<Label htmlFor="email">Email</Label>
93+
<Input
94+
id="email"
95+
type="email"
96+
placeholder="m@example.com"
97+
autoComplete="email"
98+
required
99+
value={email}
100+
onChange={(e) => setEmail(e.target.value)}
101+
/>
102+
</div>
103+
<div className="flex flex-col gap-2">
104+
<div className="flex items-center">
105+
<Label htmlFor="password">Password</Label>
106+
<Link
107+
to="/forgot-password"
108+
className="ml-auto text-sm underline-offset-4 hover:underline"
109+
>
110+
Forgot your password?
111+
</Link>
112+
</div>
113+
<Input
114+
id="password"
115+
type="password"
116+
autoComplete="current-password"
117+
required
118+
value={password}
119+
onChange={(e) => setPassword(e.target.value)}
120+
/>
121+
</div>
122+
<Button type="submit" className="w-full" disabled={submitting}>
123+
{submitting ? 'Signing in…' : 'Login'}
124+
</Button>
125+
<p className="text-center text-sm text-muted-foreground">
126+
Don&apos;t have an account?{' '}
127+
<Link to="/register" className="underline underline-offset-4 hover:text-primary">
128+
Sign up
129+
</Link>
130+
</p>
131+
</div>
132+
</form>
133+
</CardContent>
134+
</Card>
135+
<p className="px-6 text-center text-xs text-muted-foreground [&_a]:underline [&_a]:underline-offset-4 [&_a]:hover:text-primary">
136+
By clicking continue, you agree to our{' '}
137+
<a href="#">Terms of Service</a> and <a href="#">Privacy Policy</a>.
138+
</p>
139+
</div>
140+
</div>
117141
</div>
118142
);
119143
}
Lines changed: 91 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,33 @@
11
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
22

3-
import { createFileRoute, Link, useNavigate } from '@tanstack/react-router';
4-
import { useEffect, useState } from 'react';
5-
import { useClient } from '@objectstack/client-react';
6-
import { Button } from '@/components/ui/button';
7-
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
8-
import { Input } from '@/components/ui/input';
9-
import { Label } from '@/components/ui/label';
10-
import { toast } from '@/hooks/use-toast';
11-
import { useSession } from '@/hooks/useSession';
12-
import { SocialSignInButtons } from '@/components/auth/social-sign-in-buttons';
3+
import { createFileRoute, Link, useNavigate } from "@tanstack/react-router";
4+
import { useEffect, useState } from "react";
5+
import { useClient } from "@objectstack/client-react";
6+
import { Button } from "@/components/ui/button";
7+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
8+
import { Input } from "@/components/ui/input";
9+
import { Label } from "@/components/ui/label";
10+
import { toast } from "@/hooks/use-toast";
11+
import { useSession } from "@/hooks/useSession";
12+
import { SocialSignInButtons } from "@/components/auth/social-sign-in-buttons";
13+
import { GalleryVerticalEnd } from "lucide-react";
1314

14-
export const Route = createFileRoute('/register')({
15+
export const Route = createFileRoute("/register")({
1516
component: RegisterPage,
1617
});
1718

1819
function RegisterPage() {
1920
const navigate = useNavigate();
2021
const client = useClient() as any;
2122
const { user, refresh } = useSession();
22-
const [name, setName] = useState('');
23-
const [email, setEmail] = useState('');
24-
const [password, setPassword] = useState('');
23+
const [name, setName] = useState("");
24+
const [email, setEmail] = useState("");
25+
const [password, setPassword] = useState("");
2526
const [submitting, setSubmitting] = useState(false);
2627

2728
useEffect(() => {
2829
if (user) {
29-
navigate({ to: '/' });
30+
navigate({ to: "/" });
3031
}
3132
}, [user, navigate]);
3233

@@ -37,76 +38,91 @@ function RegisterPage() {
3738
try {
3839
await client.auth.register({ name, email, password });
3940
await refresh();
40-
toast({ title: 'Account created' });
41-
navigate({ to: '/orgs/new' });
41+
toast({ title: "Account created" });
42+
navigate({ to: "/orgs/new" });
4243
} catch (err) {
4344
toast({
44-
title: 'Sign up failed',
45+
title: "Sign up failed",
4546
description: (err as Error).message,
46-
variant: 'destructive',
47+
variant: "destructive",
4748
});
4849
} finally {
4950
setSubmitting(false);
5051
}
5152
};
5253

5354
return (
54-
<div className="flex flex-1 items-center justify-center bg-background px-4">
55-
<Card className="w-full max-w-sm">
56-
<CardHeader>
57-
<CardTitle>Create account</CardTitle>
58-
<CardDescription>Join ObjectStack Studio.</CardDescription>
59-
</CardHeader>
60-
<form onSubmit={handleSubmit}>
61-
<CardContent className="space-y-4">
62-
<SocialSignInButtons mode="sign-up" />
63-
<div className="space-y-1.5">
64-
<Label htmlFor="name">Name</Label>
65-
<Input
66-
id="name"
67-
autoComplete="name"
68-
required
69-
value={name}
70-
onChange={(e) => setName(e.target.value)}
71-
/>
72-
</div>
73-
<div className="space-y-1.5">
74-
<Label htmlFor="email">Email</Label>
75-
<Input
76-
id="email"
77-
type="email"
78-
autoComplete="email"
79-
required
80-
value={email}
81-
onChange={(e) => setEmail(e.target.value)}
82-
/>
83-
</div>
84-
<div className="space-y-1.5">
85-
<Label htmlFor="password">Password</Label>
86-
<Input
87-
id="password"
88-
type="password"
89-
autoComplete="new-password"
90-
required
91-
minLength={8}
92-
value={password}
93-
onChange={(e) => setPassword(e.target.value)}
94-
/>
95-
</div>
96-
</CardContent>
97-
<CardFooter className="flex flex-col gap-3">
98-
<Button type="submit" className="w-full" disabled={submitting}>
99-
{submitting ? 'Creating…' : 'Create account'}
100-
</Button>
101-
<p className="text-xs text-muted-foreground">
102-
Already have an account?{' '}
103-
<Link to="/login" className="text-primary hover:underline">
104-
Sign in
105-
</Link>
106-
</p>
107-
</CardFooter>
108-
</form>
109-
</Card>
55+
<div className="flex min-h-svh w-full flex-col items-center justify-center gap-6 bg-muted p-6 md:p-10">
56+
<div className="flex w-full max-w-sm flex-col gap-6">
57+
<a href="#" className="flex items-center gap-2 self-center font-medium">
58+
<div className="flex size-6 items-center justify-center rounded-md bg-primary text-primary-foreground">
59+
<GalleryVerticalEnd className="size-4" />
60+
</div>
61+
ObjectStack
62+
</a>
63+
<div className="flex flex-col gap-6">
64+
<Card>
65+
<CardHeader className="text-center">
66+
<CardTitle className="text-xl">Create an account</CardTitle>
67+
<CardDescription>Join ObjectStack Studio.</CardDescription>
68+
</CardHeader>
69+
<CardContent>
70+
<form onSubmit={handleSubmit}>
71+
<div className="flex flex-col gap-4">
72+
<SocialSignInButtons mode="sign-up" />
73+
<div className="flex flex-col gap-2">
74+
<Label htmlFor="name">Name</Label>
75+
<Input
76+
id="name"
77+
autoComplete="name"
78+
required
79+
value={name}
80+
onChange={(e) => setName(e.target.value)}
81+
/>
82+
</div>
83+
<div className="flex flex-col gap-2">
84+
<Label htmlFor="email">Email</Label>
85+
<Input
86+
id="email"
87+
type="email"
88+
placeholder="m@example.com"
89+
autoComplete="email"
90+
required
91+
value={email}
92+
onChange={(e) => setEmail(e.target.value)}
93+
/>
94+
</div>
95+
<div className="flex flex-col gap-2">
96+
<Label htmlFor="password">Password</Label>
97+
<Input
98+
id="password"
99+
type="password"
100+
autoComplete="new-password"
101+
required
102+
minLength={8}
103+
value={password}
104+
onChange={(e) => setPassword(e.target.value)}
105+
/>
106+
</div>
107+
<Button type="submit" className="w-full" disabled={submitting}>
108+
{submitting ? "Creating…" : "Create account"}
109+
</Button>
110+
<p className="text-center text-sm text-muted-foreground">
111+
Already have an account?{" "}
112+
<Link to="/login" className="underline underline-offset-4 hover:text-primary">
113+
Sign in
114+
</Link>
115+
</p>
116+
</div>
117+
</form>
118+
</CardContent>
119+
</Card>
120+
<p className="px-6 text-center text-xs text-muted-foreground [&_a]:underline [&_a]:underline-offset-4 [&_a]:hover:text-primary">
121+
By clicking continue, you agree to our{" "}
122+
<a href="#">Terms of Service</a> and <a href="#">Privacy Policy</a>.
123+
</p>
124+
</div>
125+
</div>
110126
</div>
111127
);
112128
}

0 commit comments

Comments
 (0)