diff --git a/scripts/create_table.py b/scripts/create_table.py index 50ac301..34df871 100644 --- a/scripts/create_table.py +++ b/scripts/create_table.py @@ -4,7 +4,7 @@ import requests url = get_server_url() print('Creating tables on ' + url +'...') - url += '/dev/createTables' + url += '/dev/createTables/public' response = requests.post(url) print(response.text) except: diff --git a/src/App.tsx b/src/App.tsx index 8d46b2a..7040b7d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -9,6 +9,7 @@ import { UserProvider } from './providers/UserProvider'; import Settings from './components/Settings/Settings'; import { NotificationProvider } from './providers/NotificationProvider'; import CreateOrg from './components/CreateOrg/CreateOrg'; +import { OrganizationProvider } from './providers/OrganizationProvider'; // The main component of the application function App() { @@ -17,31 +18,36 @@ function App() { - - - - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/src/components/FileUpload/FileUpload.tsx b/src/components/FileUpload/FileUpload.tsx index 54ce7b6..7d4b376 100644 --- a/src/components/FileUpload/FileUpload.tsx +++ b/src/components/FileUpload/FileUpload.tsx @@ -65,10 +65,8 @@ function FileUpload() { const handleUpload = () => { if (files.length === 0) return; - console.log(files); APIService.upload(files).then((res) => { showNotification('File upload', res); - setFiles([]); }); }; diff --git a/src/components/Profile/Profile.scss b/src/components/Profile/Profile.scss index b961dfc..bcb8cb1 100644 --- a/src/components/Profile/Profile.scss +++ b/src/components/Profile/Profile.scss @@ -9,10 +9,25 @@ font-weight: bold; margin-bottom: 20px; } - .orgBtns { + .orgs { display: flex; + flex-direction: column; + .create { + max-width: 150px; + } + } + .orgItem { + display: flex; + flex-direction: row; + align-items: center; + p { + font-size: 20px; + line-height: 20px; + font-weight: bold; + min-width: 200px; + } * { - margin-right: 10px; + margin-right: 15px; } } } diff --git a/src/components/Profile/Profile.tsx b/src/components/Profile/Profile.tsx index 9527abb..4b54397 100644 --- a/src/components/Profile/Profile.tsx +++ b/src/components/Profile/Profile.tsx @@ -1,5 +1,4 @@ import { Link, useNavigate } from 'react-router-dom'; -import APIService from '../../utils/ApiService'; import Navbar from '../Navbar/Navbar'; import './Profile.scss'; import { useEffect, useState } from 'react'; @@ -10,6 +9,11 @@ import { } from '../../providers/UserProvider'; import MyFiles from '../MyFiles/MyFiles'; import { useShowChoiceNotification } from '../../providers/NotificationProvider'; +import Organization from '../../utils/Organization'; +import { + useGetOrganizations, + useUpdateOrganizations, +} from '../../providers/OrganizationProvider'; /** * The profile page component @@ -23,27 +27,17 @@ function Profile() { const logOut = useLogOut(); const [username] = useState(getUserName()); + // eslint-disable-next-line @typescript-eslint/no-unused-vars const [fileNames, setFileNames] = useState([]); - const [organizationName, setOrganizationName] = useState(''); - - const getFiles = () => { - APIService.getFileNames(username).then((res) => { - setFileNames(res); - }); - }; - const getOrganizationName = () => { - APIService.getOrganization(username).then((res) => { - setOrganizationName(res); - }); - }; + const getOrganizations = useGetOrganizations(); + const updateOrganizations = useUpdateOrganizations(); useEffect(() => { if (!isLoggedIn) { navigate('/'); } - getFiles(); - getOrganizationName(); + updateOrganizations(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [isLoggedIn, username]); @@ -56,52 +50,50 @@ function Profile() { {username.slice(-2)}

- MyOrganization + MyOrganizations

- {organizationName !== '' ? ( -

- {organizationName} -

- ) : ( + {getOrganizations().length === 0 && (

You are not a member of any organization.

)} -
- {organizationName !== '' ? ( - <> - {/* //TODO */} - - - - - ) : ( - <> - - Create New - - - )} +
+ {getOrganizations().length > 0 && + getOrganizations().map((org: Organization) => ( +
  • +

    {org.name}

    + + + +
  • + ))} + + Create New +

    MyFiles diff --git a/src/providers/OrganizationProvider.tsx b/src/providers/OrganizationProvider.tsx new file mode 100644 index 0000000..1f363c0 --- /dev/null +++ b/src/providers/OrganizationProvider.tsx @@ -0,0 +1,82 @@ +/* eslint-disable react-refresh/only-export-components */ +import React, { ReactNode, useEffect } from 'react'; +import Organization from '../utils/Organization'; +import APIService from '../utils/ApiService'; + +const OrganizationContext = React.createContext({ + getOrganizations: (): Organization[] => { + return []; + }, + updateOrganizations: () => {}, +}); + +/** + * A hook to get the organizations from the OrganizationProvider + * @returns A function that returns an array of organizations + */ +export function useGetOrganizations() { + const context = React.useContext(OrganizationContext); + if (!context) { + throw new Error( + 'useGetOrganizations must be used within a OrganizationProvider' + ); + } + return context.getOrganizations; +} + +/** + * A hook to update the organizations in the OrganizationProvider + * @returns A function that updates the organizations in the OrganizationProvider + */ +export function useUpdateOrganizations() { + const context = React.useContext(OrganizationContext); + if (!context) { + throw new Error( + 'useUpdateOrganizations must be used within a OrganizationProvider' + ); + } + return context.updateOrganizations; +} + +interface Props { + children: ReactNode; +} + +/** + * A provider that provides the organizations to the application + */ +export function OrganizationProvider({ children }: Props) { + const [organizations, setOrganizations] = React.useState( + [] + ); + + useEffect(() => { + updateOrganizations(); + }, []); + + const getOrganizations = () => { + return organizations; + }; + + const updateOrganizations = () => { + APIService.getOrganizationNames().then((response) => { + if (!response) { + setOrganizations([]); + return; + } + response = response.filter((org) => org.id > 0); + setOrganizations(response); + }); + }; + + return ( + + {children} + + ); +} diff --git a/src/utils/ApiService.test.ts b/src/utils/ApiService.test.ts index bcd300f..ea05c8d 100644 --- a/src/utils/ApiService.test.ts +++ b/src/utils/ApiService.test.ts @@ -6,6 +6,7 @@ import { beforeAll, afterAll, expectTypeOf, + assert, } from 'vitest'; import 'src/utils/sessionStoragePolifill.js'; @@ -30,10 +31,11 @@ function generateRandomString(length: number): string { describe('APIService', () => { const salt = generateRandomString(10); const alternative = generateRandomString(10); + let saltOrganisationID: number | undefined = undefined; beforeAll(async () => { await APIService.register(salt, salt); - await APIService.createOrganization(salt); + saltOrganisationID = await APIService.createOrganization(salt); }); afterAll(async () => { @@ -72,17 +74,39 @@ describe('APIService', () => { expect(token).toBe(null); }); - test('should get organization successfully', async () => { + test('should get organizations successfully', async () => { await APIService.login(salt, salt); const organizationId = await APIService.getOrganizations(); expectTypeOf(organizationId).not.toBeBoolean(); expect(organizationId).toBeDefined(); + if (!organizationId || organizationId.length === 0) { + assert(false); + } + + expect(typeof organizationId[0]).toBe('number'); + expect(organizationId[0]).toBeGreaterThan(0); + }); + test('should get organizations successfully', async () => { + await APIService.login(salt, salt); + const organizations = await APIService.getOrganizationNames(); + assert(organizations); + assert(typeof organizations != 'boolean'); + expect(organizations[0].id > 0).true; + expect(typeof organizations[0].name).toBe('string'); + }); + test('should get organization successfully', async () => { + await APIService.login(salt, salt); + assert(saltOrganisationID); + const organizationName = + await APIService.getNameForOrganization(saltOrganisationID); + expectTypeOf(organizationName).not.toBeBoolean(); + expect(organizationName).toBeDefined(); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - expect(typeof organizationId[0]).toBe('number'); + expect(typeof organizationName[0]).toBe('string'); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - expect(organizationId[0]).toBeGreaterThan(10); + expect(organizationName[0].length).toBeGreaterThan(0); }); test('should create organization successfully', async () => { await APIService.login(salt, salt); diff --git a/src/utils/ApiService.ts b/src/utils/ApiService.ts index d91736d..d638b01 100644 --- a/src/utils/ApiService.ts +++ b/src/utils/ApiService.ts @@ -1,4 +1,5 @@ import axios from 'axios'; +import Organization from './Organization'; /** * This class is used to make requests to the backend API. @@ -11,26 +12,6 @@ class APIService { * @param schema The username to login * @returns A promise that resolves to true if the login was successful, false otherwise. */ - static async createTables(schema: string): Promise { - try { - const url = `${this.host}/dev/createTables/${schema}`; - const resp = await axios.post(url); - return resp.status === 200; - } catch (err) { - return false; - } - } - - static async dropTables(schema: string): Promise { - try { - const url = `${this.host}/dev/dropTables/${schema}`; - const resp = await axios.post(url); - return resp.status === 200; - } catch (err) { - return false; - } - } - static async login(username: string, password: string): Promise { const url = `${this.host}/login`; const resp = await axios.post(url, { @@ -106,7 +87,7 @@ class APIService { } } - static async getOrganizations(): Promise { + static async getOrganizations(): Promise { try { const url = `${this.host}/getOrganisations`; const resp = await axios.get(url, { @@ -117,7 +98,45 @@ class APIService { if (resp.status == 200) { return resp.data.organisation_ids as number[]; } - if (resp.status == 204) return false; + if (resp.status == 204) return undefined; + } catch (err) { + return undefined; + } + } + + static async getOrganizationNames(): Promise { + try { + const url = `${this.host}/getOrganisationNames`; + const resp = await axios.get<{ + organisations: Organization[]; + }>(url, { + headers: { + Authorization: this.getUserToken(), + }, + }); + if (resp.status == 200) { + return resp.data.organisations; + } + if (resp.status == 404) return undefined; + } catch (err) { + return undefined; + } + } + + static async getNameForOrganization( + id: number + ): Promise { + try { + const url = `${this.host}/getOrganisationName/${id}`; + const resp = await axios.get(url, { + headers: { + Authorization: this.getUserToken(), + }, + }); + if (resp.status == 200) { + return resp.data.organisation_name as string; + } + if (resp.status == 404) return undefined; } catch (err) { return undefined; } @@ -130,7 +149,7 @@ class APIService { */ static async createOrganization( orgName: string - ): Promise { + ): Promise { try { const url = `${this.host}/createOrganisation`; const resp = await axios.post( @@ -149,10 +168,10 @@ class APIService { 'organisation', JSON.stringify({ name: orgName, - id: resp.data.organisation_id, + id: resp.data.organisation_id as number, }) ); - return resp.data.organisation_id; + return resp.data.organisation_id as number; } } catch (err) { return undefined; @@ -180,7 +199,6 @@ class APIService { Authorization: this.getUserToken(), }, }); - console.log(resp); if (resp.status === 201) { return 'File uploaded successfully'; } diff --git a/src/utils/Organization.ts b/src/utils/Organization.ts new file mode 100644 index 0000000..9fe1400 --- /dev/null +++ b/src/utils/Organization.ts @@ -0,0 +1,14 @@ +/** + * Organization class to store organization data + */ +class Organization { + readonly name: string; + readonly id: number; + + constructor(name: string, id: number) { + this.name = name; + this.id = id; + } +} + +export default Organization; diff --git a/templates/Provider/$$name$$Provider.tsx b/templates/Provider/$$name$$Provider.tsx index e69de29..9871a16 100644 --- a/templates/Provider/$$name$$Provider.tsx +++ b/templates/Provider/$$name$$Provider.tsx @@ -0,0 +1,33 @@ +import React, { ReactNode } from 'react'; + +const $$name$$Context = React.createContext({ + test: () => {}, +}); + +export function useTest() { + const context = React.useContext($$name$$Context); + if (!context) { + throw new Error('useTest must be used within a $$name$$Provider'); + } + return context.test; +} + +interface Props { + children: ReactNode; +} + +export function $$name$$Provider({ children }: Props) { + const test = () => { + console.log('test'); + }; + + return ( + <$$name$$Context.Provider + value={{ + test: test, + }} + > + {children} + + ); +} diff --git a/tsconfig.json b/tsconfig.json index ca6bde6..cbe4e08 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "es2020", + "target": "ES2020", "useDefineForClassFields": true, "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "esnext", @@ -22,5 +22,5 @@ "noFallthroughCasesInSwitch": true }, "include": ["src"], - "references": [{ "path": "./tsconfig.node.json" }], + "references": [{ "path": "./tsconfig.node.json" }] }