Logto Integration Attempt 1.

This commit is contained in:
2025-05-07 22:53:15 +08:00
parent 8252a4bb14
commit 4682fe3eb4
18 changed files with 487 additions and 37 deletions
@@ -0,0 +1,98 @@
"use client";
import { useAuth } from "../contexts/AuthContext";
export default function AuthStatus() {
const { isAuthenticated, user, login, logout, loading } = useAuth();
if (loading) {
return (
<div className="auth-status loading">
<p>Loading authentication status...</p>
</div>
);
}
return (
<div className="auth-status">
<div className="auth-info">
{isAuthenticated ? (
<>
<h3>Authenticated ✅</h3>
{user && (
<div className="user-info">
<p>Name: {user.name || "N/A"}</p>
<p>Email: {user.email || "N/A"}</p>
</div>
)}
</>
) : (
<h3>Not authenticated ❌</h3>
)}
</div>
<div className="auth-actions">
{isAuthenticated ? (
<button onClick={logout} className="logout-button">
Sign Out
</button>
) : (
<button onClick={login} className="login-button">
Sign In
</button>
)}
</div>
<style jsx>{`
.auth-status {
margin: 2rem 0;
padding: 1.5rem;
border-radius: 8px;
background-color: #f8f9fa;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.auth-info {
margin-bottom: 1rem;
}
.user-info {
margin-top: 0.5rem;
padding: 0.75rem;
background-color: #e9ecef;
border-radius: 4px;
}
.auth-actions {
margin-top: 1rem;
}
button {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: 500;
transition: background-color 0.2s;
}
.login-button {
background-color: #4285f4;
color: white;
}
.logout-button {
background-color: #ea4335;
color: white;
}
button:hover {
opacity: 0.9;
}
.loading {
opacity: 0.7;
}
`}</style>
</div>
);
}
+3 -1
View File
@@ -10,7 +10,9 @@ export default function HealthStatus() {
const checkHealth = async () => {
try {
setLoading(true);
const response = await fetch("/service/health");
const response = await fetch(
`${process.env.NEXT_PUBLIC_BACKEND_URL}/health`
);
if (!response.ok) {
throw new Error(`Server responded with status: ${response.status}`);
+85
View File
@@ -0,0 +1,85 @@
import {
createContext,
useContext,
useState,
useEffect,
ReactNode,
} from "react";
interface UserData {
id?: string;
email?: string;
name?: string;
[key: string]: unknown;
}
interface AuthContextType {
isAuthenticated: boolean;
user: UserData | null;
login: () => void;
logout: () => void;
loading: boolean;
}
const AuthContext = createContext<AuthContextType | undefined>(undefined);
export function AuthProvider({ children }: { children: ReactNode }) {
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [user, setUser] = useState<UserData | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Check authentication status on mount
checkAuthStatus();
}, []);
const checkAuthStatus = async () => {
try {
const response = await fetch(
`${process.env.NEXT_PUBLIC_BACKEND_URL}/auth/user-id-token-claims`,
{
credentials: "include",
}
);
if (response.ok) {
const userData = await response.json();
setUser(userData);
setIsAuthenticated(true);
} else {
setUser(null);
setIsAuthenticated(false);
}
} catch (error) {
console.error("Error checking auth status:", error);
setIsAuthenticated(false);
setUser(null);
} finally {
setLoading(false);
}
};
const login = () => {
window.location.href = `${process.env.NEXT_PUBLIC_BACKEND_URL}/auth/sign-in`;
};
const logout = () => {
window.location.href = `${process.env.NEXT_PUBLIC_BACKEND_URL}/auth/sign-out`;
};
return (
<AuthContext.Provider
value={{ isAuthenticated, user, login, logout, loading }}
>
{children}
</AuthContext.Provider>
);
}
export function useAuth() {
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error("useAuth must be used within an AuthProvider");
}
return context;
}
+4 -1
View File
@@ -1,4 +1,5 @@
import type { Metadata } from "next";
import { Providers } from "./providers";
import "./globals.css";
export const metadata: Metadata = {
@@ -13,7 +14,9 @@ export default function RootLayout({
}) {
return (
<html lang="en">
<body>{children}</body>
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}
+28 -1
View File
@@ -1,11 +1,22 @@
"use client";
import HealthStatus from "./components/HealthStatus";
import AuthStatus from "./components/AuthStatus";
export default function Home() {
return (
<main className="container">
<h1>Welcome to Starter App</h1>
<HealthStatus />
<section className="auth-section">
<h2>Authentication</h2>
<AuthStatus />
</section>
<section className="health-section">
<h2>Backend Health</h2>
<HealthStatus />
</section>
<style jsx>{`
.container {
max-width: 800px;
@@ -13,7 +24,23 @@ export default function Home() {
padding: 2rem;
}
h1 {
margin-bottom: 2rem;
color: #333;
font-size: 2.5rem;
text-align: center;
}
h2 {
color: #555;
margin-bottom: 1rem;
border-bottom: 1px solid #eee;
padding-bottom: 0.5rem;
}
section {
margin-bottom: 2rem;
background-color: white;
padding: 1rem;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}
`}</style>
</main>
+8
View File
@@ -0,0 +1,8 @@
"use client";
import { ReactNode } from "react";
import { AuthProvider } from "./contexts/AuthContext";
export function Providers({ children }: { children: ReactNode }) {
return <AuthProvider>{children}</AuthProvider>;
}