Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 131 additions & 0 deletions apps/roam/src/components/auth/Account.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import React, { useState, useEffect } from "react";
import { createClient } from "@/lib/supabase/client";
import { Button, Label } from "@blueprintjs/core";
import { SignUpForm } from "./SignUpForm";
import { LoginForm } from "./LoginForm";
import { ForgotPasswordForm } from "./ForgotPasswordForm";
import { UpdatePasswordForm } from "./UpdatePasswordForm";

// based on https://supabase.com/ui/docs/react/password-based-auth

enum AuthAction {
waiting,
loggedIn,
login,
signup,
forgotPassword,
updatePassword,
emailSent,
}

export const Account = () => {
const [action, setAction] = useState(AuthAction.waiting);

const supabase = createClient();
useEffect(() => {
supabase.auth.getUser().then(({ data: { user } }) => {
if (user) setAction(AuthAction.loggedIn);
else setAction(AuthAction.login);
});

const {
data: { subscription },
} = supabase.auth.onAuthStateChange((_event, session) => {
if (session) setAction(AuthAction.loggedIn);
else setAction(AuthAction.login);
});

return () => subscription.unsubscribe();
}, []);
Comment on lines +24 to +39
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Optimize Supabase client creation and fix useEffect dependencies.

The Supabase client is being recreated on every render. Consider using useMemo to memoize it. Also, supabase should be included in the useEffect dependency array.

+import React, { useState, useEffect, useMemo } from "react";
-import React, { useState, useEffect } from "react";
-  const supabase = createClient();
+  const supabase = useMemo(() => createClient(), []);
   useEffect(() => {
     // ... existing code ...
-  }, []);
+  }, [supabase]);
🤖 Prompt for AI Agents
In apps/roam/src/components/auth/Account.tsx around lines 25 to 40, the Supabase
client is created inside the component, causing it to be recreated on every
render, and the useEffect hook does not include supabase in its dependency
array. Fix this by memoizing the Supabase client using useMemo to create it only
once, and add supabase to the useEffect dependency array to ensure proper effect
execution.


switch (action) {
case AuthAction.waiting:
return (
<div>
<p>Checking...</p>
</div>
);
case AuthAction.emailSent:
return (
<div>
<p>An email was sent</p>
</div>
);
case AuthAction.loggedIn:
return (
<div>
<p>Logged in!</p>
<Button
type="button"
onClick={() => {
supabase.auth.signOut().then(() => {
setAction(AuthAction.login);
});
}}
>
Log out
</Button>
<br />
<Button
type="button"
onClick={() => {
setAction(AuthAction.updatePassword);
}}
>
Reset password
</Button>
</div>
);
case AuthAction.login:
return (
<div>
<LoginForm />
<Button
type="button"
onClick={() => {
setAction(AuthAction.signup);
}}
>
Sign up
</Button>
<br />
<Label htmlFor="login_to_forgot">Forgot your password?</Label>
<Button
id="login_to_forgot"
type="button"
onClick={() => {
setAction(AuthAction.forgotPassword);
}}
>
Reset password
</Button>
</div>
);
case AuthAction.signup:
return (
<div>
<SignUpForm />
<Button
type="button"
onClick={() => {
setAction(AuthAction.login);
}}
>
login
</Button>
</div>
);
case AuthAction.forgotPassword:
return (
<div>
<ForgotPasswordForm />
</div>
);
case AuthAction.updatePassword:
return (
<div>
<UpdatePasswordForm />
</div>
);
}
};
102 changes: 102 additions & 0 deletions apps/roam/src/components/auth/ForgotPasswordForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { cn } from "@/lib/utils";
import { createClient } from "@/lib/supabase/client";
import { Button, Card, InputGroup, Label } from "@blueprintjs/core";
import React, { useState } from "react";

// based on https://supabase.com/ui/docs/react/password-based-auth

export const ForgotPasswordForm = ({
className,
...props
}: React.ComponentPropsWithoutRef<"div">) => {
const [email, setEmail] = useState("");
const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState(false);
const [isLoading, setIsLoading] = useState(false);

const handleForgotPassword = async (e: React.FormEvent) => {
const supabase = createClient();
e.preventDefault();
setIsLoading(true);
setError(null);

try {
// The url which will be included in the email. This URL needs to be configured in your redirect URLs in the Supabase dashboard at https://supabase.com/dashboard/project/_/auth/url-configuration
const { error } = await supabase.auth.resetPasswordForEmail(email, {
redirectTo: "http://localhost:3000/update-password",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Replace hard-coded localhost URL with environment variable.

The hard-coded localhost URL will not work in production or other environments and should be configurable.

-        redirectTo: "http://localhost:3000/update-password",
+        redirectTo: `${process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'}/update-password`,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
redirectTo: "http://localhost:3000/update-password",
redirectTo: `${process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'}/update-password`,
🤖 Prompt for AI Agents
In apps/roam/src/components/auth/ForgotPasswordForm.tsx at line 24, replace the
hard-coded redirect URL "http://localhost:3000/update-password" with a value
read from an environment variable. Update the code to use process.env or the
appropriate environment config to dynamically set the redirect URL, ensuring it
works correctly across different environments like production and development.

});
if (error) throw error;
setSuccess(true);
} catch (error: unknown) {
setError(error instanceof Error ? error.message : "An error occurred");
} finally {
setIsLoading(false);
}
};

return (
<div className={cn("flex flex-col gap-6", className)} {...props}>
{success ? (
<Card>
<div className={cn("flex flex-col space-y-1.5 p-6", className)}>
<div
className={cn(
"text-2xl font-semibold leading-none tracking-tight",
className,
)}
>
Check Your Email
</div>
<div className={cn("text-muted-foreground text-sm", className)}>
Password reset instructions sent
</div>
</div>
<div className={cn("p-6 pt-0", className)}>
<p className="text-muted-foreground text-sm">
If you registered using your email and password, you will receive
a password reset email.
</p>
</div>
</Card>
) : (
<Card>
<div className={cn("flex flex-col space-y-1.5 p-6", className)}>
<div
className={cn(
"text-2xl font-semibold leading-none tracking-tight",
className,
)}
>
Reset Your Password
</div>
<div className={cn("text-muted-foreground text-sm", className)}>
Type in your email and we&apos;ll send you a link to reset your
password
</div>
</div>
<div className={cn("p-6 pt-0", className)}>
<form onSubmit={handleForgotPassword}>
<div className="flex flex-col gap-6">
<div className="grid gap-2">
<Label htmlFor="email">Email</Label>
<InputGroup
id="email"
type="email"
placeholder="m@example.com"
required
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</div>
{error && <p className="text-sm text-red-500">{error}</p>}
<Button type="submit" className="w-full" disabled={isLoading}>
{isLoading ? "Sending..." : "Send reset email"}
</Button>
</div>
</form>
</div>
</Card>
)}
</div>
);
};
97 changes: 97 additions & 0 deletions apps/roam/src/components/auth/LoginForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { cn } from "@/lib/utils";
import { createClient } from "@/lib/supabase/client";
import { Button, Card, InputGroup, Label } from "@blueprintjs/core";
import React, { useState } from "react";

// based on https://supabase.com/ui/docs/react/password-based-auth

export const LoginForm = ({
className,
...props
}: React.ComponentPropsWithoutRef<"div">) => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false);
const supabase = createClient();

const handleLogin = async (e: React.FormEvent) => {
e.preventDefault();
setIsLoading(true);
setError(null);

try {
const { error } = await supabase.auth.signInWithPassword({
email,
password,
});
if (error) throw error;
// Original: Update this route to redirect to an authenticated route. The user already has an active session.
// TODO: Replacement action
// location.href = '/protected'
Comment on lines +29 to +31
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Implement proper navigation after successful login.

The commented code suggests navigation should happen after login, but it's currently not implemented. This leaves users without clear feedback about successful authentication.

Consider implementing proper navigation. Since this is a roam app, you might want to:

      if (error) throw error
-     // Original: Update this route to redirect to an authenticated route. The user already has an active session.
-     // TODO: Replacement action
-     // location.href = '/protected'
+     // Navigate to authenticated state or refresh the component
+     window.location.reload(); // or implement proper state management
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Original: Update this route to redirect to an authenticated route. The user already has an active session.
// TODO: Replacement action
// location.href = '/protected'
if (error) throw error
// Navigate to authenticated state or refresh the component
window.location.reload(); // or implement proper state management
🤖 Prompt for AI Agents
In apps/roam/src/components/auth/LoginForm.tsx around lines 24 to 26, the code
currently lacks navigation after a successful login, leaving users without
feedback. Replace the commented-out location.href assignment with a proper
navigation method suitable for the app's routing system, such as using a
router's navigate function or a redirect to an authenticated route like
'/protected' to ensure users are directed appropriately after login.

} catch (error: unknown) {
setError(error instanceof Error ? error.message : "An error occurred");
} finally {
setIsLoading(false);
}
};

return (
<div className={cn("flex flex-col gap-6", className)} {...props}>
<Card>
<div className={cn("flex flex-col space-y-1.5 p-6", className)}>
<div
className={cn(
"text-2xl font-semibold leading-none tracking-tight",
className,
)}
>
Login
</div>
<div className={cn("text-muted-foreground text-sm", className)}>
Enter your email below to login to your account
</div>
</div>
<div className={cn("p-6 pt-0", className)}>
<form onSubmit={handleLogin}>
<div className="flex flex-col gap-6">
<div className="grid gap-2">
<Label htmlFor="email">Email</Label>
<InputGroup
id="email"
type="email"
placeholder="m@example.com"
required
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</div>
<div className="grid gap-2">
<div className="flex items-center">
<Label htmlFor="password">Password</Label>
</div>
<InputGroup
id="password"
type="password"
required
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</div>
{error && <p className="text-sm text-red-500">{error}</p>}
<Button type="submit" className="w-full" disabled={isLoading}>
{isLoading ? "Logging in..." : "Login"}
</Button>
</div>
<div className="mt-4 text-center text-sm">
Don&apos;t have an account?{" "}
<a href="/sign-up" className="underline underline-offset-4">
Sign up
</a>
</div>
</form>
</div>
</Card>
</div>
);
}
Loading