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
27 changes: 18 additions & 9 deletions src/app/context/AuthContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ interface AuthState {
refreshToken: string | null;
isLoading: boolean;
login: (email: string, password: string) => Promise<{ success: boolean; error?: string }>;
signup: (email: string, password: string, fullName: string, role: string) => Promise<{ success: boolean; error?: string }>;
signup: (email: string, password: string, fullName: string, role: string) => Promise<{ success: boolean; error?: string; confirmationRequired?: boolean }>;
loginWithGoogle: () => Promise<{ success: boolean; error?: string }>;
logout: () => void;
fetchWithAuth: (url: string, options?: RequestInit) => Promise<Response>;
Expand Down Expand Up @@ -308,14 +308,21 @@ export function AuthProvider({ children }: { children: ReactNode }) {
}

const { tokens, user: userProfile } = json.data;
setToken(tokens.access_token);
setRefreshToken(tokens.refresh_token);
setUser(userProfile);
if (typeof window !== "undefined") {
localStorage.setItem("ozymorlab_token", tokens.access_token);
localStorage.setItem("ozymorlab_refresh_token", tokens.refresh_token);
if (tokens && userProfile) {
setToken(tokens.access_token);
setRefreshToken(tokens.refresh_token);
setUser(userProfile);
if (typeof window !== "undefined") {
localStorage.setItem("ozymorlab_token", tokens.access_token);
localStorage.setItem("ozymorlab_refresh_token", tokens.refresh_token);
}
return { success: true };
}
return { success: true };

if (json.data?.user) {
setUser(json.data.user);
}
return { success: true, confirmationRequired: true };
} catch (err: any) {
return { success: false, error: err.message || "Failed to register local account" };
}
Expand Down Expand Up @@ -367,8 +374,10 @@ export function AuthProvider({ children }: { children: ReactNode }) {
is_active: true,
});
}
return { success: true };
}
return { success: true };

return { success: true, confirmationRequired: true };
} catch {
return { success: false, error: "Network error. Is Supabase configured correctly?" };
}
Expand Down
61 changes: 45 additions & 16 deletions src/app/login/page.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
"use client";

import { useState, useEffect, Suspense } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { useState, useEffect } from "react";
import { useRouter } from "next/navigation";
import Link from "next/link";
import { Eye, EyeOff, ArrowRight, AlertCircle, CheckCircle2, Sparkles } from "lucide-react";
import { AuthProvider, useAuth } from "../context/AuthContext";

function LoginPageContent() {
const router = useRouter();
const searchParams = useSearchParams();
const { login, signup, loginWithGoogle, user, isLoading } = useAuth();
const { login, signup, loginWithGoogle, logout, user, isLoading } = useAuth();

const [activeTab, setActiveTab] = useState<"login" | "signup">(
searchParams.get("tab") === "signup" ? "signup" : "login"
);
const [activeTab, setActiveTab] = useState<"login" | "signup">("login");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [fullName, setFullName] = useState("");
Expand All @@ -23,12 +20,42 @@ function LoginPageContent() {
const [loading, setLoading] = useState(false);
const [success, setSuccess] = useState("");

// Redirect if already logged in
useEffect(() => {
if (!isLoading && user) {
router.push("/dashboard");
if (typeof window !== "undefined") {
const params = new URLSearchParams(window.location.search);
if (params.get("tab") === "signup") {
setActiveTab("signup");
}
}
}, [user, isLoading, router]);
}, []);

// If already logged in, show a small informative UI instead of auto-redirecting.
// This prevents the back button from immediately bouncing the user back to dashboard.
if (!isLoading && user) {
return (
<div className="auth-page">
<div className="auth-brand">
<div className="auth-brand-content">
<div className="landing-logo" style={{ marginBottom: 32 }}>
<div className="logo-mark" style={{ width: 36, height: 36, fontSize: 13 }}>Oz</div>
<span className="logo-name" style={{ fontSize: 18 }}>OzymorLab AIOS</span>
</div>
<h2 className="auth-brand-title">Welcome back</h2>
<p className="auth-brand-desc">You are already signed in as <strong>{user.email}</strong>. Use the buttons to continue.</p>
<div style={{ display: 'flex', gap: 12, marginTop: 12 }}>
<button className="btn btn-brand" onClick={() => router.push('/dashboard')}>Go to Dashboard</button>
<button className="btn btn-secondary" onClick={async () => { await logout(); router.push('/login'); }}>Sign Out</button>
</div>
</div>
</div>
<div className="auth-form-panel">
<div className="auth-form-container">
<p style={{ color: 'var(--muted)', marginTop: 20 }}>If you want to use a different account, sign out first.</p>
</div>
</div>
</div>
);
}

const handleLogin = async (e: React.FormEvent) => {
e.preventDefault();
Expand Down Expand Up @@ -56,8 +83,12 @@ function LoginPageContent() {

const result = await signup(email, password, fullName, role);
if (result.success) {
setSuccess("Account created! Redirecting...");
setTimeout(() => router.push("/dashboard"), 800);
if (result.confirmationRequired) {
setSuccess("Registration successful. Check your email and confirm your address before signing in.");
} else {
setSuccess("Account created! Redirecting...");
setTimeout(() => router.push("/dashboard"), 800);
}
} else {
setError(result.error || "Signup failed");
}
Expand Down Expand Up @@ -363,9 +394,7 @@ function LoginPageContent() {
export default function LoginPage() {
return (
<AuthProvider>
<Suspense fallback={<div className="auth-page"><div className="auth-loading"><div className="auth-spinner" /></div></div>}>
<LoginPageContent />
</Suspense>
<LoginPageContent />
</AuthProvider>
);
}
4 changes: 1 addition & 3 deletions test-results/.last-run.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
{
"status": "failed",
"failedTests": [
"70b872a5e72a7b2c7282-d4de8269ab84cb7dd8a5"
]
"failedTests": []
}
Loading