Skip to content
Draft
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
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,11 @@
.mypy_cache/
__pycache__/
.env
.runs/
.runs/

# Web/Node
web/.env
web/.env.local
web/node_modules/
web/build/
web/dist/
14 changes: 14 additions & 0 deletions web/.env.example
Original file line number Diff line number Diff line change
@@ -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
22 changes: 22 additions & 0 deletions web/README.md
Original file line number Diff line number Diff line change
@@ -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.
20 changes: 20 additions & 0 deletions web/app/entry.client.tsx
Original file line number Diff line number Diff line change
@@ -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,
<StrictMode>
<MsalProvider instance={msalInstance}>
<HydratedRouter />
</MsalProvider>
</StrictMode>
);
});
31 changes: 23 additions & 8 deletions web/app/favicon.tsx
Original file line number Diff line number Diff line change
@@ -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(<TbSocial size={32} />), {
status: 200,
headers: {
"Content-Type": "image/svg+xml",
},
});
// In SPA mode, serve favicon as a static component
export default function Favicon() {
const svg = renderToString(<TbSocial size={32} />);

return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M18 8h1a4 4 0 0 1 0 8h-1" />
<path d="M2 8h16v9a4 4 0 0 1-4 4H6a4 4 0 0 1-4-4V8z" />
<line x1="6" y1="1" x2="6" y2="4" />
<line x1="10" y1="1" x2="10" y2="4" />
<line x1="14" y1="1" x2="14" y2="4" />
</svg>
);
}
68 changes: 32 additions & 36 deletions web/app/routes/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<Design>(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();
Expand Down
71 changes: 33 additions & 38 deletions web/app/routes/home.tsx
Original file line number Diff line number Diff line change
@@ -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<Design>(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 (
Expand Down
Loading