diff --git a/.gitignore b/.gitignore
index ca9e1674..47ac76d7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,4 +3,11 @@
.mypy_cache/
__pycache__/
.env
-.runs/
\ No newline at end of file
+.runs/
+
+# Web/Node
+web/.env
+web/.env.local
+web/node_modules/
+web/build/
+web/dist/
\ No newline at end of file
diff --git a/web/.env.example b/web/.env.example
new file mode 100644
index 00000000..157b329b
--- /dev/null
+++ b/web/.env.example
@@ -0,0 +1,14 @@
+# Azure AD Configuration for MSAL Authentication
+# To use Azure AD authentication, copy this file to .env and fill in your values
+
+# Your Azure AD Application (client) ID
+VITE_AZURE_CLIENT_ID=your-client-id-here
+
+# Your Azure AD Authority URL
+# For single tenant: https://login.microsoftonline.com/{tenant-id}
+# For multi tenant: https://login.microsoftonline.com/common
+VITE_AZURE_AUTHORITY=https://login.microsoftonline.com/common
+
+# Redirect URI after authentication
+# Should match the redirect URI configured in Azure AD app registration
+VITE_AZURE_REDIRECT_URI=http://localhost:5173
diff --git a/web/README.md b/web/README.md
index 5a756172..70e01d13 100644
--- a/web/README.md
+++ b/web/README.md
@@ -1,2 +1,24 @@
# sustineo
Your personal AI assistant.
+
+## Application Type
+
+This is a **Single-Page Application (SPA)** built with React and React Router. The application runs entirely in the browser and uses MSAL (Microsoft Authentication Library) for Azure AD authentication.
+
+For detailed information about authentication setup, see [Authentication Documentation](./docs/AUTHENTICATION.md).
+
+## Key Features
+
+- 🔒 Azure AD authentication with MSAL React
+- 🎨 Modern React with TypeScript
+- 📱 Single-page application architecture
+- 🔐 Public client authentication (no server-side secrets)
+
+## Quick Start
+
+1. Install dependencies: `npm install`
+2. Set up environment variables (copy `.env.example` to `.env`)
+3. Run development server: `npm run dev`
+4. Build for production: `npm run build`
+
+See the main project [README](../README.md) for complete setup instructions.
diff --git a/web/app/entry.client.tsx b/web/app/entry.client.tsx
new file mode 100644
index 00000000..9ab50dd1
--- /dev/null
+++ b/web/app/entry.client.tsx
@@ -0,0 +1,20 @@
+import { startTransition, StrictMode } from "react";
+import { hydrateRoot } from "react-dom/client";
+import { HydratedRouter } from "react-router/dom";
+import { MsalProvider } from "@azure/msal-react";
+import { PublicClientApplication } from "@azure/msal-browser";
+import { msalConfig } from "store/authConfig";
+
+// Initialize MSAL instance
+const msalInstance = new PublicClientApplication(msalConfig);
+
+startTransition(() => {
+ hydrateRoot(
+ document,
+
+
+
+
+
+ );
+});
diff --git a/web/app/favicon.tsx b/web/app/favicon.tsx
index 766e4db5..fdbae8af 100644
--- a/web/app/favicon.tsx
+++ b/web/app/favicon.tsx
@@ -1,12 +1,27 @@
-import type { Route } from "./+types/favicon";
import { TbSocial } from "react-icons/tb";
import { renderToString } from "react-dom/server";
-export async function loader({ params }: Route.LoaderArgs) {
- return new Response(renderToString(), {
- status: 200,
- headers: {
- "Content-Type": "image/svg+xml",
- },
- });
+// In SPA mode, serve favicon as a static component
+export default function Favicon() {
+ const svg = renderToString();
+
+ return (
+
+ );
}
diff --git a/web/app/routes/app.tsx b/web/app/routes/app.tsx
index 1238c71d..0f1b26ab 100644
--- a/web/app/routes/app.tsx
+++ b/web/app/routes/app.tsx
@@ -65,45 +65,41 @@ interface ImageFunctionCall {
image?: string;
}
-export async function loader({ params }: Route.LoaderArgs) {
- const designConfig = new DesignConfiguration();
- try {
- const defaultDesign = await designConfig.fetchDefaultDesign();
- return defaultDesign;
- } catch (error) {
- console.error("Error fetching default design:", error);
- }
- // You can perform any data fetching or initialization here
- // For example, you might want to fetch user data or initial settings
- const design: Design = {
- id: "default",
- background: "/images/background.jpg",
- default: true,
- logo: "",
- title: "BuildEvents",
- sub_title: "by Contoso",
- description: "Making Things Happen since 1935",
- };
-
- return design;
+export function meta() {
+ return [
+ { title: "BuildEvents by Contoso" },
+ { name: "description", content: "Making Things Happen since 1935" },
+ ];
}
-export function meta({ data }: Route.MetaArgs) {
- if (!data) {
- return [
- { title: "BuildEvents by Contoso" },
- { name: "description", content: "Making Things Happen since 1935" },
- ];
- }
- const title = `${data["title"] || "BuildEvents"} ${
- data["sub_title"] || ""
- }`;
- const description = data["description"] || "Making Things Happen since 1935";
- return [{ title: title }, { name: "description", content: description }];
-}
+const defaultDesign: Design = {
+ id: "default",
+ background: "/images/background.jpg",
+ default: true,
+ logo: "",
+ title: "BuildEvents",
+ sub_title: "by Contoso",
+ description: "Making Things Happen since 1935",
+};
+
+export default function App() {
+ const [design, setDesign] = useState(defaultDesign);
+
+ useEffect(() => {
+ const fetchDesign = async () => {
+ const designConfig = new DesignConfiguration();
+ try {
+ const defaultDesign = await designConfig.fetchDefaultDesign();
+ setDesign(defaultDesign);
+ } catch (error) {
+ console.error("Error fetching default design:", error);
+ }
+ };
+
+ fetchDesign();
+ }, []);
-export default function App({ loaderData }: Route.ComponentProps) {
- const { background, logo, title, sub_title, description } = loaderData as unknown as Design;
+ const { background, logo, title, sub_title, description } = design;
const location = useLocation();
diff --git a/web/app/routes/home.tsx b/web/app/routes/home.tsx
index b682e26b..d4475b3e 100644
--- a/web/app/routes/home.tsx
+++ b/web/app/routes/home.tsx
@@ -1,48 +1,43 @@
import { DesignConfiguration, type Design } from "store/design";
import MicIcon from "../../components/micicon";
import styles from "./home.module.scss";
-import type { Route } from "../+types/root";
+import { useState, useEffect } from "react";
-export async function loader({ params }: Route.LoaderArgs) {
- const designConfig = new DesignConfiguration();
- try {
- const defaultDesign = await designConfig.fetchDefaultDesign();
- return defaultDesign;
- } catch (error) {
- console.error("Error fetching default design:", error);
- }
- // You can perform any data fetching or initialization here
- // For example, you might want to fetch user data or initial settings
- const design: Design = {
- id: "default",
- background: "/images/background.jpg",
- default: true,
- logo: "",
- title: "BuildEvents",
- sub_title: "by Contoso",
- description: "Making Things Happen since 1935",
- };
-
- return design;
+export function meta() {
+ return [
+ { title: "BuildEvents by Contoso" },
+ { name: "description", content: "Making Things Happen since 1935" },
+ ];
}
-export function meta({ data }: Route.MetaArgs) {
- if (!data) {
- return [
- { title: "BuildEvents by Contoso" },
- { name: "description", content: "Making Things Happen since 1935" },
- ];
- }
- const title = `${data["title"] || "BuildEvents"} ${
- data["sub_title"] || "by Contoso"
- }`;
- const description = data["description"] || "Making Things Happen since 1935";
- return [{ title: title }, { name: "description", content: description }];
-}
+const defaultDesign: Design = {
+ id: "default",
+ background: "/images/background.jpg",
+ default: true,
+ logo: "",
+ title: "BuildEvents",
+ sub_title: "by Contoso",
+ description: "Making Things Happen since 1935",
+};
+
+export default function Home() {
+ const [design, setDesign] = useState(defaultDesign);
+
+ useEffect(() => {
+ const fetchDesign = async () => {
+ const designConfig = new DesignConfiguration();
+ try {
+ const defaultDesign = await designConfig.fetchDefaultDesign();
+ setDesign(defaultDesign);
+ } catch (error) {
+ console.error("Error fetching default design:", error);
+ }
+ };
+
+ fetchDesign();
+ }, []);
-export default function Home({ loaderData }: Route.ComponentProps) {
- const { background, logo, title, sub_title, description } =
- loaderData as unknown as Design;
+ const { background, logo, title, sub_title, description } = design;
return (
diff --git a/web/docs/AUTHENTICATION.md b/web/docs/AUTHENTICATION.md
new file mode 100644
index 00000000..3bed7af7
--- /dev/null
+++ b/web/docs/AUTHENTICATION.md
@@ -0,0 +1,135 @@
+# Authentication Setup
+
+This application is configured as a **Single-Page Application (SPA)** using MSAL (Microsoft Authentication Library) for Azure AD authentication.
+
+## Application Type
+
+### Single-Page Application (SPA)
+A single-page application runs entirely in the browser and fetches data (HTML, CSS, and JavaScript) dynamically or at application load time. It can call web APIs to interact with back-end data sources.
+
+Because a SPA's code runs entirely in the browser, it's considered a **public client** that's unable to store secrets securely.
+
+## Authentication Architecture
+
+This application uses:
+- **Framework**: React with React Router (SPA mode)
+- **Authentication Library**: MSAL React (`@azure/msal-react`)
+- **Browser Library**: MSAL Browser (`@azure/msal-browser`)
+
+### Features
+- ✅ Request ID tokens for user sign-in
+- ✅ Request access tokens for protected web APIs
+- ✅ Client-side authentication flow
+- ✅ No server-side session management
+
+## Configuration
+
+### 1. Azure AD App Registration
+
+Before using authentication, you need to register your application in Azure AD:
+
+1. Go to [Azure Portal](https://portal.azure.com)
+2. Navigate to **Azure Active Directory** > **App registrations**
+3. Click **New registration**
+4. Configure:
+ - **Name**: Your application name (e.g., "BuildEvents by Contoso")
+ - **Supported account types**: Choose appropriate option
+ - **Redirect URI**:
+ - Platform: **Single-page application (SPA)**
+ - URI: `http://localhost:5173` (for development)
+5. After registration, note the **Application (client) ID**
+6. Note your **Directory (tenant) ID** (from the Overview page)
+
+### 2. Environment Variables
+
+Create a `.env` file in the `web` directory (copy from `.env.example`):
+
+```env
+VITE_AZURE_CLIENT_ID=your-client-id-here
+VITE_AZURE_AUTHORITY=https://login.microsoftonline.com/your-tenant-id
+VITE_AZURE_REDIRECT_URI=http://localhost:5173
+```
+
+For multi-tenant support, use:
+```env
+VITE_AZURE_AUTHORITY=https://login.microsoftonline.com/common
+```
+
+### 3. SPA Mode Configuration
+
+The application is configured in `react-router.config.ts`:
+
+```typescript
+export default {
+ ssr: false, // SPA mode enabled
+} satisfies Config;
+```
+
+## Local Development
+
+For local development, the application falls back to a default user when running on `localhost`:
+
+```typescript
+// Default user for localhost development
+const defaultUser = {
+ key: "seth-juarez",
+ name: "Seth Juarez",
+ email: "seth.juarez@microsoft.com",
+ avatar: "/images/people/seth-juarez.jpg",
+};
+```
+
+This allows development without requiring Azure AD configuration.
+
+## Authentication Flow
+
+### Sign In
+When deployed (not localhost), users are authenticated using MSAL:
+
+1. MSAL checks for existing authentication
+2. If not authenticated, user can be prompted to sign in
+3. User is redirected to Azure AD login page
+4. After successful authentication, user is redirected back to the app
+5. MSAL manages tokens and session
+
+### User Information
+The `useUser()` hook provides:
+- `user`: User object with name, email, and avatar
+- `loading`: Loading state during authentication
+- `error`: Any authentication errors
+
+## Code Structure
+
+### Files
+- `store/authConfig.ts`: MSAL configuration
+- `store/useuser.tsx`: User authentication hook
+- `app/root.tsx`: MSAL provider setup
+
+### Usage Example
+
+```tsx
+import { useUser } from "store/useuser";
+
+function MyComponent() {
+ const { user, loading, error } = useUser();
+
+ if (loading) return Loading...
;
+ if (error) return Error: {error.message}
;
+
+ return Welcome, {user.name}!
;
+}
+```
+
+## Deployment
+
+When deploying to production:
+
+1. Update redirect URIs in Azure AD app registration to include production URL
+2. Set environment variables in your hosting platform
+3. Ensure the application is served over HTTPS (required by Azure AD)
+
+## References
+
+- [MSAL React Documentation](https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/lib/msal-react)
+- [Azure AD SPA Quickstart](https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-v2-javascript-auth-code)
+- [React Router SPA Mode](https://reactrouter.com/how-to/spa)
diff --git a/web/docs/SPA_CONVERSION_SUMMARY.md b/web/docs/SPA_CONVERSION_SUMMARY.md
new file mode 100644
index 00000000..26c50c59
--- /dev/null
+++ b/web/docs/SPA_CONVERSION_SUMMARY.md
@@ -0,0 +1,183 @@
+# SPA Conversion Summary
+
+## Overview
+This document summarizes the conversion of the BuildEvents application from a server-side rendered (SSR) web application to a Single-Page Application (SPA) with MSAL authentication.
+
+## What Changed
+
+### Before (Web Application)
+- **Architecture**: Server-Side Rendering (SSR) enabled
+- **Authentication**: Azure Easy Auth (`/.auth/me` endpoint)
+- **Client Type**: Confidential client (can store secrets)
+- **Data Loading**: Server-side loaders
+- **Deployment**: Requires Node.js server
+
+### After (Single-Page Application)
+- **Architecture**: Client-side only (SPA mode)
+- **Authentication**: MSAL React (Azure AD)
+- **Client Type**: Public client (cannot store secrets)
+- **Data Loading**: Client-side with `useEffect`
+- **Deployment**: Static files only (CDN-ready)
+
+## Technical Changes
+
+### 1. Configuration Changes
+**File**: `web/react-router.config.ts`
+```typescript
+// Before
+ssr: true
+
+// After
+ssr: false // Enables SPA mode
+```
+
+### 2. Authentication Implementation
+**New Files**:
+- `web/store/authConfig.ts` - MSAL configuration
+- `web/app/entry.client.tsx` - Custom entry point with MsalProvider
+
+**Modified Files**:
+- `web/store/useuser.tsx` - Now uses `useMsal()` hook instead of `fetch('/.auth/me')`
+
+**Key Changes**:
+```typescript
+// Before: Server-side auth
+const response = await fetch(`${WEB_ENDPOINT}/.auth/me`);
+const userData = await response.json();
+
+// After: MSAL client-side auth
+const { instance, accounts } = useMsal();
+const account = accounts[0];
+```
+
+### 3. Data Loading Pattern
+**Modified Files**:
+- `web/app/routes/home.tsx`
+- `web/app/routes/app.tsx`
+- `web/app/favicon.tsx`
+
+**Before**: Server-side loaders
+```typescript
+export async function loader({ params }: Route.LoaderArgs) {
+ const data = await fetchData();
+ return data;
+}
+
+export default function Component({ loaderData }: Route.ComponentProps) {
+ const data = loaderData;
+ // ...
+}
+```
+
+**After**: Client-side data fetching
+```typescript
+export default function Component() {
+ const [data, setData] = useState(defaultData);
+
+ useEffect(() => {
+ const fetchData = async () => {
+ const result = await fetchData();
+ setData(result);
+ };
+ fetchData();
+ }, []);
+ // ...
+}
+```
+
+### 4. Package Dependencies
+**Added**:
+- `@azure/msal-react` - MSAL React components and hooks
+- `@azure/msal-browser` - MSAL browser authentication library
+
+## Benefits of SPA Mode
+
+### Advantages
+1. ✅ **Simpler Deployment**: Static files only (no server required)
+2. ✅ **CDN-Friendly**: Can be deployed to CDN for global distribution
+3. ✅ **Lower Cost**: No server costs, just static hosting
+4. ✅ **Better Caching**: Static assets can be aggressively cached
+5. ✅ **Client-Side Routing**: Instant page transitions
+6. ✅ **Industry Standard**: Matches Microsoft's recommended pattern for SPAs
+
+### Trade-offs
+1. ⚠️ **No SSR**: Slightly slower initial page load (mitigated by code splitting)
+2. ⚠️ **Client-Side Auth**: Cannot store secrets (appropriate for public clients)
+3. ⚠️ **API Required**: Must have separate API for data operations
+
+## Authentication Flow
+
+### Development (localhost)
+- Uses default user (Seth Juarez)
+- No Azure AD configuration required
+- Allows local development without cloud dependencies
+
+### Production
+1. User visits application
+2. MSAL checks for existing session
+3. If not authenticated, redirects to Azure AD login
+4. User authenticates with Microsoft credentials
+5. Redirected back to app with tokens
+6. MSAL manages token refresh automatically
+
+## Environment Configuration
+
+### Required Variables (Production)
+```env
+VITE_AZURE_CLIENT_ID=
+VITE_AZURE_AUTHORITY=https://login.microsoftonline.com/
+VITE_AZURE_REDIRECT_URI=
+```
+
+### Azure AD App Registration
+- **Platform**: Single-page application (SPA)
+- **Redirect URIs**: Must match production URLs
+- **Supported account types**: Configure as needed
+- **API permissions**: User.Read (for profile access)
+
+## Build Output
+
+### SPA Mode Output
+```
+build/client/
+├── assets/ # JS and CSS bundles
+├── images/ # Static images
+├── videos/ # Static videos
+└── index.html # Entry point (isSpaMode: true)
+```
+
+### Deployment
+The `build/client` directory contains all static files needed for deployment:
+- Can be deployed to Azure Static Web Apps
+- Can be deployed to Azure Blob Storage with CDN
+- Can be deployed to any static hosting service (Netlify, Vercel, etc.)
+
+## Testing
+
+### Build Verification
+```bash
+cd web
+npm run build
+# Verify: "SPA Mode: Generated build/client/index.html"
+```
+
+### Local Testing
+```bash
+cd web
+npm run dev
+# Visit: http://localhost:5173
+```
+
+### Type Checking
+```bash
+cd web
+npm run typecheck
+```
+
+## Documentation
+- **Authentication Setup**: [AUTHENTICATION.md](./AUTHENTICATION.md)
+- **React Router SPA Mode**: https://reactrouter.com/how-to/spa
+- **MSAL React Docs**: https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/lib/msal-react
+
+## Conclusion
+The application is now a true Single-Page Application that follows Microsoft's recommended pattern for browser-based applications. It uses MSAL React for secure authentication and can be deployed as static files to any hosting platform.
diff --git a/web/package-lock.json b/web/package-lock.json
index c6cf1b52..fe83844a 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -6,6 +6,8 @@
"": {
"name": "web",
"dependencies": {
+ "@azure/msal-browser": "^4.24.1",
+ "@azure/msal-react": "^3.0.20",
"@juggle/resize-observer": "^3.4.0",
"@react-router/node": "^7.6.1",
"@react-router/serve": "^7.6.1",
@@ -50,6 +52,40 @@
"node": ">=6.0.0"
}
},
+ "node_modules/@azure/msal-browser": {
+ "version": "4.24.1",
+ "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-4.24.1.tgz",
+ "integrity": "sha512-e4sp8ihJIyZQvN0ZM1MMuKlEiiLWUS9V9+kxsVAc6K8MtpXHui8VINmKUxXH0OOksLhFDpdq4sGW1w6uYp431A==",
+ "license": "MIT",
+ "dependencies": {
+ "@azure/msal-common": "15.13.0"
+ },
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/@azure/msal-common": {
+ "version": "15.13.0",
+ "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-15.13.0.tgz",
+ "integrity": "sha512-8oF6nj02qX7eE/6+wFT5NluXRHc05AgdCC3fJnkjiJooq8u7BcLmxaYYSwc2AfEkWRMRi6Eyvvbeqk4U4412Ag==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/@azure/msal-react": {
+ "version": "3.0.20",
+ "resolved": "https://registry.npmjs.org/@azure/msal-react/-/msal-react-3.0.20.tgz",
+ "integrity": "sha512-+mlGe5rzJDe1Feb0BcPwCkcRTIXAUX0mxBnP8hDuzIXrwBAT/iHHl6wcsZ5iKBnMuqOicJhGX5l2/Iwqguom0Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@azure/msal-browser": "^4.24.0",
+ "react": "^16.8.0 || ^17 || ^18 || ^19"
+ }
+ },
"node_modules/@babel/code-frame": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
diff --git a/web/package.json b/web/package.json
index 5c4f8f33..a754c360 100644
--- a/web/package.json
+++ b/web/package.json
@@ -9,6 +9,8 @@
"typecheck": "react-router typegen && tsc"
},
"dependencies": {
+ "@azure/msal-browser": "^4.24.1",
+ "@azure/msal-react": "^3.0.20",
"@juggle/resize-observer": "^3.4.0",
"@react-router/node": "^7.6.1",
"@react-router/serve": "^7.6.1",
diff --git a/web/react-router.config.ts b/web/react-router.config.ts
index 6ff16f91..b8b143a3 100644
--- a/web/react-router.config.ts
+++ b/web/react-router.config.ts
@@ -3,5 +3,5 @@ import type { Config } from "@react-router/dev/config";
export default {
// Config options...
// Server-side render by default, to enable SPA mode set this to `false`
- ssr: true,
+ ssr: false,
} satisfies Config;
diff --git a/web/store/authConfig.ts b/web/store/authConfig.ts
new file mode 100644
index 00000000..43435e0a
--- /dev/null
+++ b/web/store/authConfig.ts
@@ -0,0 +1,28 @@
+import type { Configuration, PopupRequest } from '@azure/msal-browser';
+
+/**
+ * Configuration object to be passed to MSAL instance on creation.
+ * For a full list of MSAL.js configuration parameters, visit:
+ * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/configuration.md
+ */
+export const msalConfig: Configuration = {
+ auth: {
+ clientId: import.meta.env.VITE_AZURE_CLIENT_ID || 'YOUR_CLIENT_ID',
+ authority: import.meta.env.VITE_AZURE_AUTHORITY || 'https://login.microsoftonline.com/common',
+ redirectUri: import.meta.env.VITE_AZURE_REDIRECT_URI || '/',
+ },
+ cache: {
+ cacheLocation: 'sessionStorage', // This configures where your cache will be stored
+ storeAuthStateInCookie: false, // Set this to "true" if you are having issues on IE11 or Edge
+ },
+};
+
+/**
+ * Scopes you add here will be prompted for user consent during sign-in.
+ * By default, MSAL.js will add OIDC scopes (openid, profile, email) to any login request.
+ * For more information about OIDC scopes, visit:
+ * https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent#openid-connect-scopes
+ */
+export const loginRequest: PopupRequest = {
+ scopes: ['User.Read'],
+};
diff --git a/web/store/useuser.tsx b/web/store/useuser.tsx
index 6a6e78ba..f895b1cb 100644
--- a/web/store/useuser.tsx
+++ b/web/store/useuser.tsx
@@ -1,4 +1,5 @@
import { useState, useEffect } from "react";
+import { useMsal } from "@azure/msal-react";
import { WEB_ENDPOINT } from "store/endpoint";
export interface User {
@@ -25,42 +26,28 @@ const defaultUser: User = {
avatar: "/images/people/seth-juarez.jpg",
};
-const getUser = async (): Promise => {
- if (WEB_ENDPOINT.startsWith("http://localhost")) {
- //TODO: check for local json
- return defaultUser;
- }
- const response = await fetch(`${WEB_ENDPOINT}/.auth/me`);
- if (!response.ok) {
- return defaultUser;
- }
- const userData = await response.json();
- const email: string = userData[0].user_id;
- const name: string = userData[0].user_claims
- .find((claim: any) => claim.typ === "name")
- ?.val.toString();
- const nameKey: string = name.toLowerCase().replace(/\s/g, "-");
+/**
+ * Extract user information from MSAL account
+ */
+const getUserFromAccount = (account: any): User => {
+ const name = account.name || account.username || "Unknown User";
+ const email = account.username || account.email || "";
+ const nameKey = name.toLowerCase().replace(/\s/g, "-");
const userAvatar = availableUsers[nameKey];
- if (userAvatar) {
- return {
- key: nameKey,
- name: name,
- email: email,
- avatar: userAvatar,
- };
- } else {
- return {
- key: nameKey,
- name: userData.name,
- email: userData.email,
- };
- }
+
+ return {
+ key: nameKey,
+ name: name,
+ email: email,
+ avatar: userAvatar,
+ };
};
/**
- * React hook for fetching and managing user data
+ * React hook for fetching and managing user data with MSAL authentication
*/
export const useUser = () => {
+ const { instance, accounts } = useMsal();
const [user, setUser] = useState({
key: "",
name: "",
@@ -73,8 +60,22 @@ export const useUser = () => {
const fetchUser = async () => {
setLoading(true);
try {
- const userData = await getUser();
- setUser(userData);
+ // For localhost development, use default user
+ if (WEB_ENDPOINT.startsWith("http://localhost")) {
+ setUser(defaultUser);
+ setLoading(false);
+ return;
+ }
+
+ // Check if user is authenticated with MSAL
+ if (accounts.length > 0) {
+ const account = accounts[0];
+ const userData = getUserFromAccount(account);
+ setUser(userData);
+ } else {
+ // Not authenticated, use default user or trigger login
+ setUser(defaultUser);
+ }
} catch (err) {
setError(err as Error);
// on error, set user to default user
@@ -85,7 +86,7 @@ export const useUser = () => {
};
fetchUser();
- }, []);
+ }, [accounts, instance]);
return { user, loading, error } as const;
};