This commit is contained in:
parent
5d3751602e
commit
bcfa6dab06
|
|
@ -0,0 +1,11 @@
|
||||||
|
/**
|
||||||
|
* API route to expose public configuration
|
||||||
|
* This allows environment variables to be loaded at runtime instead of build time
|
||||||
|
*/
|
||||||
|
import { NextResponse } from "next/server";
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
return NextResponse.json({
|
||||||
|
googleClientId: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID || "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -53,7 +53,11 @@ export function LoginButton() {
|
||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button variant="outline" size="sm" className="gap-2">
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="gap-2 bg-white/20 hover:bg-white/30 text-white border-white/30 border"
|
||||||
|
>
|
||||||
{user.avatar_url ? (
|
{user.avatar_url ? (
|
||||||
<img
|
<img
|
||||||
src={user.avatar_url}
|
src={user.avatar_url}
|
||||||
|
|
@ -66,7 +70,7 @@ export function LoginButton() {
|
||||||
<span className="hidden sm:inline max-w-[100px] truncate">
|
<span className="hidden sm:inline max-w-[100px] truncate">
|
||||||
{user.name || user.email}
|
{user.name || user.email}
|
||||||
</span>
|
</span>
|
||||||
<Cloud className="w-3 h-3 text-green-500" />
|
<Cloud className="w-3 h-3 text-green-300" />
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end" className="w-56">
|
<DropdownMenuContent align="end" className="w-56">
|
||||||
|
|
@ -90,10 +94,15 @@ export function LoginButton() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button variant="outline" size="sm" onClick={login} className="gap-2">
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={login}
|
||||||
|
className="gap-2 bg-white/20 hover:bg-white/30 text-white border-white/30 border"
|
||||||
|
>
|
||||||
<GoogleIcon />
|
<GoogleIcon />
|
||||||
<span className="hidden sm:inline">登入</span>
|
<span className="hidden sm:inline">登入</span>
|
||||||
<CloudOff className="w-3 h-3 text-gray-400 sm:hidden" />
|
<CloudOff className="w-3 h-3 text-white/60 sm:hidden" />
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,12 +31,6 @@ const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
||||||
// Token storage key
|
// Token storage key
|
||||||
const TOKEN_KEY = "tradingagents_auth_token";
|
const TOKEN_KEY = "tradingagents_auth_token";
|
||||||
|
|
||||||
// Google OAuth client ID from environment
|
|
||||||
const GOOGLE_CLIENT_ID = process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID || "";
|
|
||||||
|
|
||||||
// Backend URL
|
|
||||||
const BACKEND_URL = process.env.NEXT_PUBLIC_API_URL || "";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Auth Provider Component
|
* Auth Provider Component
|
||||||
*/
|
*/
|
||||||
|
|
@ -44,6 +38,19 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||||
const [user, setUser] = useState<User | null>(null);
|
const [user, setUser] = useState<User | null>(null);
|
||||||
const [token, setToken] = useState<string | null>(null);
|
const [token, setToken] = useState<string | null>(null);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
const [googleClientId, setGoogleClientId] = useState<string>("");
|
||||||
|
|
||||||
|
// Fetch Google Client ID from API (runtime)
|
||||||
|
useEffect(() => {
|
||||||
|
fetch("/api/config")
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
setGoogleClientId(data.googleClientId || "");
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error("Failed to fetch config:", err);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Parse JWT token to get user info
|
// Parse JWT token to get user info
|
||||||
const parseToken = useCallback((token: string): User | null => {
|
const parseToken = useCallback((token: string): User | null => {
|
||||||
|
|
@ -102,7 +109,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||||
|
|
||||||
// Login - redirect to Google OAuth
|
// Login - redirect to Google OAuth
|
||||||
const login = useCallback(() => {
|
const login = useCallback(() => {
|
||||||
if (!GOOGLE_CLIENT_ID) {
|
if (!googleClientId) {
|
||||||
console.error("Google Client ID not configured");
|
console.error("Google Client ID not configured");
|
||||||
alert("Google 登入尚未設定。請聯繫管理員。");
|
alert("Google 登入尚未設定。請聯繫管理員。");
|
||||||
return;
|
return;
|
||||||
|
|
@ -113,7 +120,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||||
const scope = "openid email profile";
|
const scope = "openid email profile";
|
||||||
|
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
client_id: GOOGLE_CLIENT_ID,
|
client_id: googleClientId,
|
||||||
redirect_uri: redirectUri,
|
redirect_uri: redirectUri,
|
||||||
response_type: "code",
|
response_type: "code",
|
||||||
scope: scope,
|
scope: scope,
|
||||||
|
|
@ -122,7 +129,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||||
});
|
});
|
||||||
|
|
||||||
window.location.href = `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`;
|
window.location.href = `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`;
|
||||||
}, []);
|
}, [googleClientId]);
|
||||||
|
|
||||||
// Logout
|
// Logout
|
||||||
const logout = useCallback(() => {
|
const logout = useCallback(() => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue