Skip to content

Commit c4af5bc

Browse files
committed
Integrate Auth0 for authentication and update UI accordingly
Integrates Auth0 for user authentication across the app, including login, logout, and protected route handling. Updates the sidebar, chat container, and APIs to conditionally handle authenticated users, display user info, and manage chat histories securely with access tokens. Adds an `AuthStatus` component to handle authentication states and improves API request handling.
1 parent c782d52 commit c4af5bc

File tree

8 files changed

+203
-58
lines changed

8 files changed

+203
-58
lines changed

src/App.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { WelcomeScreen } from './components/WelcomeScreen';
1010
import { Cog6ToothIcon } from "@heroicons/react/24/outline";
1111
import { NotificationProvider } from './context/NotificationContext';
1212
import {NotificationContainer} from "./components/NotificationContainer.tsx";
13+
import { Auth0Provider } from '@auth0/auth0-react';
1314

1415
interface ModelSettings {
1516
temperature: number;
@@ -110,6 +111,15 @@ function App() {
110111
};
111112

112113
return (
114+
<Auth0Provider
115+
domain="shaharia-lab-beta.eu.auth0.com"
116+
clientId="TMpTLSNJEcOSISWvHmEW7GJS6mawcXQR"
117+
authorizationParams={{
118+
redirect_uri: window.location.origin,
119+
audience: 'mcp-kit-backend',
120+
}}
121+
>
122+
113123
<NotificationProvider>
114124
<div className="bg-gray-50 min-h-screen">
115125
<div
@@ -181,6 +191,7 @@ function App() {
181191
<NotificationContainer />
182192
</div>
183193
</NotificationProvider>
194+
</Auth0Provider>
184195
);
185196
}
186197

src/api/index.ts

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
1-
// src/api/index.ts
2-
import { ChatHistory } from '../types';
3-
4-
export const fetchChatHistories = async (): Promise<ChatHistory[]> => {
5-
const response = await fetch('http://localhost:8081/chats');
6-
if (!response.ok) {
7-
throw new Error('Failed to fetch chat histories');
1+
export const fetchChatHistories = async (token: string) => {
2+
try {
3+
const response = await fetch('http://localhost:8081/chats', {
4+
headers: {
5+
'Authorization': `Bearer ${token}`,
6+
'Content-Type': 'application/json'
7+
}
8+
});
9+
if (!response.ok) {
10+
throw new Error('Failed to fetch chat histories');
11+
}
12+
return await response.json();
13+
} catch (error) {
14+
throw error;
815
}
9-
const data = await response.json();
10-
return data.chats;
1116
};

src/components/AuthStatus.tsx

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { useAuth0 } from '@auth0/auth0-react';
2+
3+
export function AuthStatus() {
4+
const {
5+
isAuthenticated,
6+
loginWithRedirect,
7+
logout,
8+
user,
9+
isLoading
10+
} = useAuth0();
11+
12+
if (isLoading) {
13+
return <div className="flex items-center justify-center">
14+
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-gray-900"></div>
15+
</div>;
16+
}
17+
18+
if (isAuthenticated) {
19+
return (
20+
<div className="flex items-center gap-2 p-2">
21+
<img
22+
src={user?.picture}
23+
alt={user?.name}
24+
className="w-8 h-8 rounded-full"
25+
/>
26+
<span className="text-sm">{user?.name}</span>
27+
<button
28+
onClick={() => logout({
29+
logoutParams: { returnTo: window.location.origin }
30+
})}
31+
className="px-3 py-1 text-sm text-red-600 hover:text-red-800"
32+
>
33+
Logout
34+
</button>
35+
</div>
36+
);
37+
}
38+
39+
return (
40+
<button
41+
onClick={() => loginWithRedirect()}
42+
className="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-md hover:bg-blue-700"
43+
>
44+
Log In
45+
</button>
46+
);
47+
}

src/components/ChatContainer.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {Message} from "./Message/Message.tsx";
44
import {ChatInput} from "./ChatInput.tsx";
55
import { ChatPayload } from '../types/chat';
66
import {useNotification} from "../context/NotificationContext.tsx";
7+
import { useAuth0 } from '@auth0/auth0-react';
78

89
interface ModelSettings {
910
temperature: number;
@@ -36,6 +37,21 @@ export const ChatContainer: React.FC<ChatContainerProps> = ({
3637
const [selectedTools, setSelectedTools] = useState<string[]>([]);
3738
const [selectedProvider, setSelectedProvider] = useState<string | null>(null);
3839
const [selectedModelId, setSelectedModelId] = useState<string | null>(null);
40+
const { isAuthenticated, loginWithRedirect } = useAuth0();
41+
if (!isAuthenticated) {
42+
return (
43+
<div className="flex flex-col items-center justify-center h-full">
44+
<h2 className="text-xl mb-4">Please log in to use the chat</h2>
45+
<button
46+
onClick={() => loginWithRedirect()}
47+
className="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-md hover:bg-blue-700"
48+
>
49+
Log In
50+
</button>
51+
</div>
52+
);
53+
}
54+
3955

4056
const handleProviderChange = (provider: string, modelId: string) => {
4157
setSelectedProvider(provider);

src/components/Sidebar.tsx

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { SidebarHeader } from "./sidebar/SidebarHeader";
66
import { NewChatSection } from "./sidebar/NewChatSection";
77
import { ChatHistoryList } from "./sidebar/ChatHistoryList";
88
import { SidebarFooter } from "./sidebar/SidebarFooter";
9+
import { useAuth0 } from '@auth0/auth0-react';
10+
911

1012
interface SidebarProps {
1113
isOpen: boolean;
@@ -23,24 +25,33 @@ export const Sidebar: React.FC<SidebarProps> = ({
2325
const [chatHistories, setChatHistories] = useState<ChatHistory[]>([]);
2426
const [isLoading, setIsLoading] = useState(false);
2527
const [error, setError] = useState<string | null>(null);
28+
const { getAccessTokenSilently, isAuthenticated } = useAuth0();
2629

2730
const loadChatHistories = async () => {
31+
if (!isAuthenticated) return;
32+
2833
setIsLoading(true);
2934
try {
30-
const histories = await fetchChatHistories();
31-
setChatHistories(histories);
35+
const token = await getAccessTokenSilently();
36+
const response = await fetchChatHistories(token);
37+
// Access the chats array from the response
38+
setChatHistories(response.chats || []);
3239
setError(null);
3340
} catch (err) {
3441
setError('Failed to load chat histories');
3542
console.error(err);
43+
setChatHistories([]);
3644
} finally {
3745
setIsLoading(false);
3846
}
3947
};
4048

49+
4150
useEffect(() => {
42-
loadChatHistories();
43-
}, []);
51+
if (isAuthenticated) {
52+
loadChatHistories();
53+
}
54+
}, [isAuthenticated]);
4455

4556
const getFirstMessage = (chat: ChatHistory): string => {
4657
if (!chat.messages || chat.messages.length === 0) {
@@ -76,29 +87,33 @@ export const Sidebar: React.FC<SidebarProps> = ({
7687
};
7788

7889
return (
79-
<div
80-
className={`fixed top-0 left-0 h-full bg-white shadow-lg z-40 transition-all duration-300 transform ${
81-
isOpen ? 'translate-x-0' : '-translate-x-full'
82-
} sidebar-width flex flex-col`}
83-
>
90+
<div className={`fixed top-0 left-0 h-full bg-white shadow-lg z-40 transition-all duration-300 transform ${
91+
isOpen ? 'translate-x-0' : '-translate-x-full'
92+
} sidebar-width flex flex-col`}>
8493
<SidebarHeader onClose={onClose} />
85-
<NewChatSection onChatSelect={onChatSelect ?? (() => {})} />
86-
87-
<ChatHistoryList
88-
isLoading={isLoading}
89-
error={error}
90-
chatHistories={chatHistories}
91-
selectedChatId={selectedChatId}
92-
onChatSelect={onChatSelect}
93-
getFirstMessage={getFirstMessage}
94-
formatDate={formatDate}
95-
/>
96-
94+
{isAuthenticated ? (
95+
<>
96+
<NewChatSection onChatSelect={onChatSelect ?? (() => {})} />
97+
<ChatHistoryList
98+
isLoading={isLoading}
99+
error={error}
100+
chatHistories={chatHistories}
101+
selectedChatId={selectedChatId}
102+
onChatSelect={onChatSelect}
103+
getFirstMessage={getFirstMessage}
104+
formatDate={formatDate}
105+
/>
106+
</>
107+
) : (
108+
<div className="flex-1 flex items-center justify-center p-4 text-center text-gray-500">
109+
Please log in to view your chat history
110+
</div>
111+
)}
97112
<SidebarFooter
98113
onRefresh={handleRefresh}
99114
onHelp={handleHelp}
100115
onSettings={handleSettings}
101116
/>
102117
</div>
103118
);
104-
};
119+
};

src/components/sidebar/ChatHistoryList.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ interface ChatHistoryListProps {
1515
export const ChatHistoryList: React.FC<ChatHistoryListProps> = ({
1616
isLoading,
1717
error,
18-
chatHistories,
18+
chatHistories = [],
1919
selectedChatId,
2020
onChatSelect,
2121
getFirstMessage,
Lines changed: 72 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import React from 'react';
2-
import { ArrowPathIcon, QuestionMarkCircleIcon, Cog6ToothIcon } from '@heroicons/react/24/outline';
2+
import {
3+
ArrowPathIcon,
4+
QuestionMarkCircleIcon,
5+
Cog6ToothIcon,
6+
ArrowLeftOnRectangleIcon,
7+
} from "@heroicons/react/24/outline";
8+
import { useAuth0 } from '@auth0/auth0-react';
39

410
interface SidebarFooterProps {
511
onRefresh?: () => void;
@@ -12,31 +18,72 @@ export const SidebarFooter: React.FC<SidebarFooterProps> = ({
1218
onHelp,
1319
onSettings
1420
}) => {
21+
const { user, logout, isAuthenticated } = useAuth0();
1522
return (
16-
<div className="p-4 border-t flex-shrink-0">
17-
<div className="flex space-x-4 justify-center">
18-
<button
19-
className="p-2 hover:bg-gray-100 rounded"
20-
aria-label="Refresh"
21-
onClick={onRefresh}
22-
>
23-
<ArrowPathIcon className="h-5 w-5 text-gray-500" />
24-
</button>
25-
<button
26-
className="p-2 hover:bg-gray-100 rounded"
27-
aria-label="Help"
28-
onClick={onHelp}
29-
>
30-
<QuestionMarkCircleIcon className="h-5 w-5 text-gray-500" />
31-
</button>
32-
<button
33-
className="p-2 hover:bg-gray-100 rounded"
34-
aria-label="Settings"
35-
onClick={onSettings}
36-
>
37-
<Cog6ToothIcon className="h-5 w-5 text-gray-500" />
38-
</button>
23+
<div className="mt-auto border-t border-gray-200">
24+
{isAuthenticated && user && (
25+
<div className="p-4 border-b border-gray-200">
26+
<div className="flex items-center gap-3">
27+
{user.picture ? (
28+
<img
29+
src={user.picture}
30+
alt={user.name || 'User'}
31+
className="w-8 h-8 rounded-full"
32+
/>
33+
) : (
34+
<div className="w-8 h-8 rounded-full bg-gray-200 flex items-center justify-center">
35+
<span className="text-gray-600 text-sm">
36+
{(user.name || 'U')[0].toUpperCase()}
37+
</span>
38+
</div>
39+
)}
40+
<div className="flex-1 min-w-0">
41+
<p className="text-sm font-medium text-gray-900 truncate">
42+
{user.name}
43+
</p>
44+
<p className="text-xs text-gray-500 truncate">
45+
{user.email}
46+
</p>
47+
</div>
48+
</div>
49+
</div>
50+
)}
51+
<div className="p-4 flex items-center justify-between">
52+
<div className="flex gap-4">
53+
<button
54+
onClick={onRefresh}
55+
className="text-gray-600 hover:text-gray-900"
56+
title="Refresh"
57+
>
58+
<ArrowPathIcon className="w-5 h-5" />
59+
</button>
60+
<button
61+
onClick={onHelp}
62+
className="text-gray-600 hover:text-gray-900"
63+
title="Help"
64+
>
65+
<QuestionMarkCircleIcon className="w-5 h-5" />
66+
</button>
67+
<button
68+
onClick={onSettings}
69+
className="text-gray-600 hover:text-gray-900"
70+
title="Settings"
71+
>
72+
<Cog6ToothIcon className="w-5 h-5" />
73+
</button>
74+
</div>
75+
{isAuthenticated && (
76+
<button
77+
onClick={() => logout({
78+
logoutParams: { returnTo: window.location.origin }
79+
})}
80+
className="text-gray-600 hover:text-red-600"
81+
title="Logout"
82+
>
83+
<ArrowLeftOnRectangleIcon className="w-5 h-5" />
84+
</button>
85+
)}
3986
</div>
4087
</div>
4188
);
42-
};
89+
};

src/types/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ export interface Message {
66

77
export interface ChatHistory {
88
uuid: string;
9-
messages: Message[];
9+
messages: Array<{
10+
Role: string;
11+
Text: string;
12+
generated_at: string;
13+
}>;
1014
created_at: string;
1115
}

0 commit comments

Comments
 (0)