Skip to content

Commit

Permalink
Merge pull request #271 from COS301-SE-2024/feat/web/Dashboard-Creation
Browse files Browse the repository at this point in the history
Feat/web/dashboard creation
  • Loading branch information
Tinashe-Austin authored Aug 6, 2024
2 parents 98f87ac + fcda023 commit ccd3524
Show file tree
Hide file tree
Showing 39 changed files with 1,807 additions and 767 deletions.
Binary file modified frontend/occupi-web/bun.lockb
Binary file not shown.
11 changes: 10 additions & 1 deletion frontend/occupi-web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@
"@remixicon/react": "^4.2.0",
"@testing-library/jest-dom": "^6.4.6",
"@testing-library/react": "^16.0.0",
"@testing-library/user-event": "^14.5.2",
"@tremor/react": "^3.17.4",
"@types/babel__core": "^7.20.5",
"@types/jest": "^29.5.12",
"@types/prop-types": "^15.7.12",
"@types/react-beautiful-dnd": "^13.1.8",
"@types/react-grid-layout": "^1.3.5",
"axios": "^1.7.2",
"body-parser": "^1.20.2",
"bun-types": "^1.1.17",
Expand All @@ -51,9 +53,15 @@
"npm": "^10.8.1",
"react": "^18.3.1",
"react-beautiful-dnd": "^13.1.1",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dnd-test-backend": "^16.0.1",
"react-dnd-test-utils": "^16.0.1",
"react-dom": "^18.3.1",
"react-grid-layout": "^1.4.4",
"react-icons": "^5.2.1",
"react-pdf-charts": "^0.2.5",
"react-rnd": "^10.4.11",
"react-router-dom": "^6.24.0",
"react-test-renderer": "^18.3.1",
"react-to-print": "^2.15.1",
Expand All @@ -63,7 +71,8 @@
"ts-jest": "^29.1.5",
"ts-node": "^10.9.2",
"vite-tsconfig-paths": "^4.3.2",
"vitest": ""
"vitest": "",
"zustand": "^4.5.4"
},
"devDependencies": {
"@happy-dom/global-registrator": "^14.12.3",
Expand Down
11 changes: 9 additions & 2 deletions frontend/occupi-web/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { LoginForm, OtpPage, Settings, Dashboard,Analysis,Visitation,Faq} from "@pages/index";
import { LoginForm, OtpPage, Settings, Dashboard,Analysis,Visitation,Faq,AiDashboard,Rooms} from "@pages/index";
import {Appearance, OverviewComponent,BookingComponent,PDFReport} from "@components/index";
import { Layout } from "@layouts/index";
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
Expand Down Expand Up @@ -35,12 +35,19 @@ function App() {
<Routes>
<Route path="dashboard/*" element={<Dashboard />} >
<Route path="overview" element={<OverviewComponent />} />
<Route path="bookings" element={<BookingComponent />} />{/**attach appropriate component */}
<Route path="bookings" element={<BookingComponent />} />*attach appropriate component
<Route path="visitations" element={<Visitation />} />{/**attach appropriate component */}
<Route path="analysis" element={<Analysis/>} />{}
</Route>
{/* <Route path="dashboard/bookings" element={<BookingComponent />} />*attach appropriate component */}

<Route path="reports" element={<PDFReport />} />{/**attach appropriate component */}
<Route path="faq" element={ <Faq/> } />{/**attach appropriate component */}
<Route path="ai-dashboard" element={<AiDashboard />} />{/**consider making ths its own page */}
<Route path="rooms" element={<Rooms />} />{/**attach appropriate component */}




<Route path="settings/*" element={<Settings />}>
<Route path="profile" element={<Appearance />} />{/**attach appropriate component */}
Expand Down
84 changes: 59 additions & 25 deletions frontend/occupi-web/src/AuthService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,38 @@ import axios from 'axios';
const API_URL = '/auth'; // This will be proxied to https://dev.occupi.tech
const API_USER_URL = '/api'; // Adjust this if needed

interface PublicKeyCredential {
id: string;
rawId: ArrayBuffer;
type: string;
response: {
attestationObject: ArrayBuffer;
clientDataJSON: ArrayBuffer;
};
}

interface PublicKeyAssertion {
id: string;
rawId: ArrayBuffer;
type: string;
response: {
authenticatorData: ArrayBuffer;
clientDataJSON: ArrayBuffer;
signature: ArrayBuffer;
userHandle: ArrayBuffer;
};
}

// interface PublicKeyCredentialRequestOptions {
// challenge: ArrayBuffer;
// allowCredentials: Array<{
// id: ArrayBuffer;
// type: string;
// transports?: string[];
// }>;
// // Add other properties as needed
// }

const AuthService = {
login: async (email: string, password: string) => {
try {
Expand Down Expand Up @@ -32,7 +64,9 @@ const AuthService = {
response.data.data.options.publicKey.challenge = bufferDecode(response.data.data.options.publicKey.challenge);
response.data.data.options.publicKey.user.id = bufferDecode(response.data.data.options.publicKey.user.id);

const credential: any = await navigator.credentials.create({ publicKey: response.data.data.options.publicKey });
const credential = await navigator.credentials.create({
publicKey: response.data.data.options.publicKey
}) as PublicKeyCredential;

if (!credential) {
throw new Error('Failed to create credential');
Expand Down Expand Up @@ -65,29 +99,40 @@ const AuthService = {
const response = await axios.post(`${API_URL}/login-admin-begin`, {
email,
});

if (response.data.message === "Please check your email for an otp."){
return response.data;
}

// if backend returns this message: "Error getting user credentials, please register for WebAuthn",
// then do an automatic call to register function
if (response.data.message === "Error getting user credentials, please register for WebAuthn") {
const response3 = await AuthService.webauthnRegister(email);
return response3;
}

response.data.data.options.publicKey.challenge = bufferDecode(response.data.data.options.publicKey.challenge);
response.data.data.options.publicKey.allowCredentials.forEach(function (listItem: any) {
listItem.id = bufferDecode(listItem.id)
});

const assertion: any = await navigator.credentials.get({ publicKey: response.data.data.options.publicKey });

response.data.data.options.publicKey.allowCredentials = response.data.data.options.publicKey.allowCredentials.map(
(listItem: { id: string; type: string; transports?: string[] }) => ({
...listItem,
id: bufferDecode(listItem.id),
})
);

const assertion = await navigator.credentials.get({
publicKey: {
...response.data.data.options.publicKey,
allowCredentials: response.data.data.options.publicKey.allowCredentials.map(
(listItem: { id: ArrayBuffer; type: string; transports?: string[] }) => ({
...listItem,
transports: listItem.transports as AuthenticatorTransport[],
})
),
},
}) as PublicKeyAssertion;

if (assertion === null) {
throw new Error('No assertion returned');
}

const assertionJSON = JSON.stringify({
id: assertion.id,
rawId: bufferEncode(assertion.rawId),
Expand All @@ -102,7 +147,7 @@ const AuthService = {

// Send the assertion to the server
const response2 = await axios.post(`${API_URL}/login-admin-finish${response.data.data.uuid}`, assertionJSON);

return response2.data;
} catch (error) {
if (axios.isAxiosError(error) && error.response?.data) {
Expand All @@ -112,13 +157,9 @@ const AuthService = {
}
},



logout: async () => {
try {
const response = await axios.post(`${API_URL}/logout`, {

});
const response = await axios.post(`${API_URL}/logout`, {});
return response.data;
} catch (error) {
if (axios.isAxiosError(error) && error.response?.data) {
Expand Down Expand Up @@ -150,9 +191,6 @@ const AuthService = {
throw new Error('An unexpected error occurred while fetching user details');
}
},




verifyOtpLogin: async (email: string, otp: string) => {
try {
Expand All @@ -171,9 +209,6 @@ const AuthService = {
throw new Error('An unexpected error occurred during OTP verification');
}
}



};

function bufferEncode(value: ArrayBuffer): string {
Expand All @@ -200,5 +235,4 @@ function bufferDecode(value: string | null): Uint8Array | null {
return Uint8Array.from(atob(value), c => c.charCodeAt(0));
}


export default AuthService;
51 changes: 0 additions & 51 deletions frontend/occupi-web/src/UserContext.tsx

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,26 +1,20 @@
import { describe, expect, test } from "bun:test";
import { render, screen } from "@testing-library/react";
import OverviewComponent from "./OverviewComponent";
import { UserProvider } from "UserContext"; // Import the UserProvider

// Create a wrapper component that provides the UserContext
import { ReactNode } from "react";

const Wrapper = ({ children }: { children: ReactNode }) => (
<UserProvider>
{children}
</UserProvider>
);


describe("OverviewComponent Tests", () => {
// test("renders greeting and welcome messages", () => {
// render(<OverviewComponent />, { wrapper: Wrapper });
// // expect(screen.getByText("Hi Tina 👋")).toBeTruthy();
// expect(screen.getByText("Welcome to Occupi")).toBeTruthy();
// });
test("renders greeting and welcome messages", () => {
render(<OverviewComponent />);
// expect(screen.getByText("Hi Tina 👋")).toBeTruthy();
expect(screen.getByText("Welcome to Occupi")).toBeTruthy();
});

test("renders images and checks their presence", () => {
render(<OverviewComponent />, { wrapper: Wrapper });
render(<OverviewComponent />);
const images = screen.getAllByRole("img");
expect(images.length).toBeGreaterThan(0);
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { expect, test, mock } from "bun:test";
import { render,cleanup } from "@testing-library/react";
import {AiDashCard} from "@components/index";
import { afterEach } from "bun:test";


afterEach(() => {
cleanup();
});

test("AiDashCard renders correctly", () => {
const mockProps = {
title: "Test Card",
icon: <div>Icon</div>,
stat: "100",
trend: 5,
onRemove: mock(() => {}),
};

const { getByText } = render(<AiDashCard {...mockProps} />);

expect(getByText("Test Card")).toBeDefined();
expect(getByText("100")).toBeDefined();
expect(getByText("5% Since last month")).toBeDefined();
});



test("AiDashCard calls onRemove when close button is clicked", () => {
const mockOnRemove = mock(() => {});
const mockProps = {
title: "Test Card",
icon: <div>Icon</div>,
stat: "100",
trend: 5,
onRemove: mockOnRemove,
};

const { getByText } = render(<AiDashCard {...mockProps} />);
const closeButton = getByText("×");
closeButton.click();

expect(mockOnRemove).toHaveBeenCalled();
});

test("AiDashCard displays negative trend correctly", () => {
const mockProps = {
title: "Test Card",
icon: <div>Icon</div>,
stat: "100",
trend: -5,
onRemove: mock(() => {}),
};

const { getByText } = render(<AiDashCard {...mockProps} />);

expect(getByText("5% Since last month")).toBeDefined();
});

Loading

0 comments on commit ccd3524

Please sign in to comment.