Skip to content
Open
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
188 changes: 177 additions & 11 deletions apps/web/app/(org)/login/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { AnimatePresence, motion } from "framer-motion";
import Cookies from "js-cookie";
import { LucideArrowUpRight } from "lucide-react";
import { KeyRound, LucideArrowUpRight } from "lucide-react";
import Image from "next/image";
import Link from "next/link";
import { useRouter, useSearchParams } from "next/navigation";
Expand Down Expand Up @@ -40,6 +40,8 @@ export function LoginForm() {
const [lastEmailSentTime, setLastEmailSentTime] = useState<number | null>(
null,
);
const [password, setPassword] = useState("");
const [showCredientialLogin,setShowCredientialLogin]=useState(false);
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 | 🟡 Minor

Fix typo and formatting.

The variable name has a typo ("Crediential" should be "Credential") and is missing spaces around operators.

Apply this diff:

-	const [showCredientialLogin,setShowCredientialLogin]=useState(false);
+	const [showCredentialLogin, setShowCredentialLogin] = useState(false);

Note: This change will require updating all references to these identifiers throughout the file (lines 253, 368, 376, 387, 467, 475, 522, 553, 562, 619).

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/web/app/(org)/login/form.tsx around line 44, the state variable
declaration contains a typo and formatting issues: rename showCredientialLogin
to showCredentialLogin, and rename setShowCredientialLogin to
setShowCredentialLogin, adding spaces around the = and after commas; then update
all usages of these identifiers throughout the file (lines 253, 368, 376, 387,
467, 475, 522, 553, 562, 619) to the corrected names so references remain
consistent.

const theme = Cookies.get("theme") || "light";

useEffect(() => {
Expand Down Expand Up @@ -248,6 +250,60 @@ export function LoginForm() {
e.preventDefault();
if (!email) return;

if (showCredientialLogin) {
if (!email || !password) {
toast.error("Please enter email and password");
return;
}

try {
setLoading(true);
trackEvent("auth_started", { method: "password", is_signup: false });

const res = await signIn("credentials", {
email,
password,
redirect: false,
...(next && next.length > 0 ? { callbackUrl: next } : {}),
});

setLoading(false);

if (res?.ok && !res?.error) {
trackEvent("auth_success", { method: "password", is_signup: false });
router.push(next || "/dashboard");
return;
}
// Handle specific known errors first
if (res?.error?.toLowerCase().includes("verify")) {
toast.error("Please verify your email before logging in.");
const resend = await fetch("/api/auth/resend", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email }),
});

const data = await resend.json();
if (!data?.status) throw new Error("Something went wrong, try other login methods.");

router.push(`/verify-otp?email=${encodeURIComponent(email)}&type=credentials`);
return;
Comment on lines +280 to +290
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 | 🔴 Critical

🧩 Analysis chain

Verify the /api/auth/resend endpoint exists.

The code calls /api/auth/resend to resend verification during login for unverified accounts. A previous review noted this endpoint was missing from the API contract. Ensure this endpoint is implemented and handles credential-based OTP resends correctly.

Run the following script to check if the endpoint exists:


🏁 Script executed:

#!/bin/bash
# Search for the resend endpoint in API routes and contract
rg -l 'auth.*resend|resend.*auth' --type ts --type tsx -g '!node_modules'

# Check if endpoint is defined in API contract
cat packages/web-domain/src/Http/Api.ts 2>/dev/null | grep -i resend || echo "Not found in API contract"

# Look for Next.js API route handlers
fd 'route\.(ts|js)' apps/web/app/api | xargs rg -l 'resend' 2>/dev/null || echo "No API route found"

Length of output: 72



---

🏁 Script executed:

```shell
#!/bin/bash
# Broader search without type restrictions
echo "=== Searching for 'resend' in all files ==="
rg 'resend' -g '!node_modules' -g '!.next' -g '!dist' | head -30

echo -e "\n=== Checking API routes structure ==="
fd -e ts -e js 'app/api' apps/web/ 2>/dev/null | head -20

echo -e "\n=== Looking for auth endpoints ==="
fd -e ts -e js . apps/web/app/api 2>/dev/null | xargs grep -l 'auth' 2>/dev/null | head -10

echo -e "\n=== Checking POST route pattern in api/auth ==="
find apps/web/app/api -name '*route*' -type f 2>/dev/null | head -15

Length of output: 2185



---

🏁 Script executed:

```shell
#!/bin/bash
# Check the catch-all route handler
echo "=== apps/web/app/api/[[...route]]/route.ts ==="
cat apps/web/app/api/[[...route]]/route.ts 2>/dev/null | head -100

echo -e "\n=== apps/web/app/api/auth/[...nextauth]/route.ts (first 150 lines) ==="
cat apps/web/app/api/auth/\[...\]nextauth\]/route.ts 2>/dev/null | head -150

echo -e "\n=== Searching for 'resend' handler implementations ==="
rg -A5 -B2 'resend' apps/web/app/api -g '!node_modules' 2>/dev/null

Length of output: 574



---

🏁 Script executed:

```shell
#!/bin/bash
# Find web-backend package
echo "=== Looking for web-backend package ==="
find . -name "web-backend" -type d -not -path "*/node_modules/*" 2>/dev/null

echo -e "\n=== Searching for HttpLive export ==="
rg 'export.*HttpLive' -g '!node_modules' 2>/dev/null | head -5

echo -e "\n=== Searching for auth/resend in web-backend or backend packages ==="
find . -path "*/packages/*backend*" -name "*.ts" -type f -not -path "*/node_modules/*" 2>/dev/null | xargs rg 'resend|auth.*resend' 2>/dev/null | head -20

echo -e "\n=== Looking for Api.ts or similar contracts ==="
find . -name "Api.ts" -o -name "api.ts" -not -path "*/node_modules/*" 2>/dev/null | head -10

Length of output: 528


}

if (res?.error) {
toast.error(res.error);
return;
}

} catch (error) {
console.error("Credential login error:", error);
toast.error("Unexpected error, please try again later.");
} finally {
setLoading(false);
}
return;
}

// Check if we're rate limited on the client side
if (lastEmailSentTime) {
const timeSinceLastRequest =
Expand Down Expand Up @@ -309,15 +365,28 @@ export function LoginForm() {
}}
className="flex flex-col space-y-3"
>
<NormalLogin
setShowOrgInput={setShowOrgInput}
email={email}
emailSent={emailSent}
setEmail={setEmail}
loading={loading}
oauthError={oauthError}
handleGoogleSignIn={handleGoogleSignIn}
/>
{showCredientialLogin ? (
<LoginWithEmailAndPassword
email={email}
emailSent={emailSent}
setEmail={setEmail}
password={password}
setPassword={setPassword}
loading={loading}
setShowCredientialLogin={setShowCredientialLogin}
/>
) : (
<NormalLogin
setShowOrgInput={setShowOrgInput}
email={email}
emailSent={emailSent}
setEmail={setEmail}
loading={loading}
oauthError={oauthError}
handleGoogleSignIn={handleGoogleSignIn}
setShowCredientialLogin={setShowCredientialLogin}
/>
)}
</motion.form>
)}
</motion.div>
Expand Down Expand Up @@ -388,6 +457,91 @@ const LoginWithSSO = ({
);
};

const LoginWithEmailAndPassword = ({
email,
emailSent,
setEmail,
loading,
password,
setPassword,
setShowCredientialLogin
}: {
email: string;
emailSent: boolean;
setEmail: (email: string) => void;
password: string,
setPassword: (password: string) => void;
loading: boolean;
setShowCredientialLogin : (show : boolean) => void

}) => {
return (
<motion.div>
<motion.div layout className="flex flex-col space-y-3">
<MotionInput
id="email"
name="email"
autoFocus
type="email"
placeholder={emailSent ? "" : "[email protected]"}
autoComplete="email"
required
value={email}
disabled={emailSent || loading}
onChange={(e) => {
setEmail(e.target.value);
}}
/>
<MotionInput
id="password"
name="password"
autoFocus
type="password"
placeholder={emailSent ? "" : "password"}
autoComplete="current-password"
required
value={password}
disabled={emailSent || loading}
onChange={(e) => {
setPassword(e.target.value);
}}
/>
<MotionButton
variant="dark"
type="submit"
disabled={loading || emailSent}
icon={<FontAwesomeIcon className="mr-1 size-4" icon={faEnvelope} />}
>
Login with email
</MotionButton>
<MotionButton
variant="gray"
type="button"
className="w-full"
layout
onClick={() => setShowCredientialLogin(false)}
disabled={loading || emailSent}
>
<LucideArrowUpRight size={20} />
Login with OTP
</MotionButton>
<motion.p
layout="position"
className="mt-3 mb-2 text-xs text-center text-gray-9"
>
Don't have an account?{" "}
<Link
href="/signup"
className="text-xs font-semibold text-blue-9 hover:text-blue-8"
>
Sign up here
</Link>
</motion.p>
</motion.div>
</motion.div>
);
};

const NormalLogin = ({
setShowOrgInput,
email,
Expand All @@ -396,6 +550,7 @@ const NormalLogin = ({
loading,
oauthError,
handleGoogleSignIn,
setShowCredientialLogin
}: {
setShowOrgInput: (show: boolean) => void;
email: string;
Expand All @@ -404,6 +559,7 @@ const NormalLogin = ({
loading: boolean;
oauthError: boolean;
handleGoogleSignIn: () => void;
setShowCredientialLogin : (show : boolean) => void
}) => {
const publicEnv = usePublicEnv();

Expand Down Expand Up @@ -455,7 +611,17 @@ const NormalLogin = ({
Sign up here
</Link>
</motion.p>

<MotionButton
variant="gray"
type="button"
className="w-full"
layout
onClick={() => setShowCredientialLogin(true)}
disabled={loading || emailSent}
>
<KeyRound size={18}/>
Login with Password
</MotionButton>
{(publicEnv.googleAuthAvailable || publicEnv.workosAuthAvailable) && (
<>
<div className="flex gap-4 items-center mt-4 mb-4">
Expand Down
Loading