feat: prettier format

This commit is contained in:
Iain Learmonth 2026-06-22 15:50:35 +01:00
parent 6eed4dc75d
commit 734dc440c6
15 changed files with 955 additions and 791 deletions

View file

@ -17,9 +17,9 @@ If you are developing a production application, we recommend updating the config
```js ```js
export default defineConfig([ export default defineConfig([
globalIgnores(['dist']), globalIgnores(["dist"]),
{ {
files: ['**/*.{ts,tsx}'], files: ["**/*.{ts,tsx}"],
extends: [ extends: [
// Other configs... // Other configs...
@ -34,40 +34,40 @@ export default defineConfig([
], ],
languageOptions: { languageOptions: {
parserOptions: { parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'], project: ["./tsconfig.node.json", "./tsconfig.app.json"],
tsconfigRootDir: import.meta.dirname, tsconfigRootDir: import.meta.dirname,
}, },
// other options... // other options...
}, },
}, },
]) ]);
``` ```
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
```js ```js
// eslint.config.js // eslint.config.js
import reactX from 'eslint-plugin-react-x' import reactX from "eslint-plugin-react-x";
import reactDom from 'eslint-plugin-react-dom' import reactDom from "eslint-plugin-react-dom";
export default defineConfig([ export default defineConfig([
globalIgnores(['dist']), globalIgnores(["dist"]),
{ {
files: ['**/*.{ts,tsx}'], files: ["**/*.{ts,tsx}"],
extends: [ extends: [
// Other configs... // Other configs...
// Enable lint rules for React // Enable lint rules for React
reactX.configs['recommended-typescript'], reactX.configs["recommended-typescript"],
// Enable lint rules for React DOM // Enable lint rules for React DOM
reactDom.configs.recommended, reactDom.configs.recommended,
], ],
languageOptions: { languageOptions: {
parserOptions: { parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'], project: ["./tsconfig.node.json", "./tsconfig.app.json"],
tsconfigRootDir: import.meta.dirname, tsconfigRootDir: import.meta.dirname,
}, },
// other options... // other options...
}, },
}, },
]) ]);
``` ```

View file

@ -1,15 +1,15 @@
import js from '@eslint/js' import js from "@eslint/js";
import globals from 'globals' import globals from "globals";
import reactHooks from 'eslint-plugin-react-hooks' import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from 'eslint-plugin-react-refresh' import reactRefresh from "eslint-plugin-react-refresh";
import tseslint from 'typescript-eslint' import tseslint from "typescript-eslint";
import { defineConfig, globalIgnores } from 'eslint/config' import { defineConfig, globalIgnores } from "eslint/config";
import eslintConfigPrettier from "eslint-config-prettier"; import eslintConfigPrettier from "eslint-config-prettier";
export default defineConfig([ export default defineConfig([
globalIgnores(['dist']), globalIgnores(["dist"]),
{ {
files: ['**/*.{ts,tsx}'], files: ["**/*.{ts,tsx}"],
extends: [ extends: [
js.configs.recommended, js.configs.recommended,
tseslint.configs.recommended, tseslint.configs.recommended,
@ -21,4 +21,4 @@ export default defineConfig([
globals: globals.browser, globals: globals.browser,
}, },
}, },
]) ]);

View file

@ -1,8 +1,6 @@
{ {
"$schema": "https://docs.renovatebot.com/renovate-schema.json", "$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [ "extends": ["config:recommended"],
"config:recommended"
],
"minimumReleaseAge": "14 days", "minimumReleaseAge": "14 days",
"gitAuthor": "Renovate<noreply@sr2.uk>" "gitAuthor": "Renovate<noreply@sr2.uk>"
} }

View file

@ -1,31 +1,33 @@
import "./App.css";
import './App.css'
import { useAuth, hasAuthParams } from "react-oidc-context"; import { useAuth, hasAuthParams } from "react-oidc-context";
import React, {useState} from 'react'; import React, { useState } from "react";
import { import {
SettingOutlined, SettingOutlined,
UserOutlined, UserOutlined,
DeploymentUnitOutlined, DeploymentUnitOutlined,
} from '@ant-design/icons'; } from "@ant-design/icons";
import {Breadcrumb, type MenuProps} from 'antd'; import { Breadcrumb, type MenuProps } from "antd";
import { Layout, Menu, theme } from 'antd'; import { Layout, Menu, theme } from "antd";
import Home from "./Home.tsx"; import Home from "./Home.tsx";
import {Route, Routes, useLocation, useNavigate} from "react-router"; import { Route, Routes, useLocation, useNavigate } from "react-router";
import Profile from "./Profile.tsx"; import Profile from "./Profile.tsx";
import {type UserContextType} from './hooks/UserContext.ts'; import { type UserContextType } from "./hooks/UserContext.ts";
import {UserContext} from './hooks/UserContext.ts'; import { UserContext } from "./hooks/UserContext.ts";
import type {userObject} from "./hooks/UserContext.ts"; import type { userObject } from "./hooks/UserContext.ts";
import Organisations from "./Organisations.tsx"; import Organisations from "./Organisations.tsx";
import {RefreshContext} from "./hooks/RefreshContext.ts"; import { RefreshContext } from "./hooks/RefreshContext.ts";
import Bridges from "./Bridges.tsx"; import Bridges from "./Bridges.tsx";
import {OrgContext, type OrgContextType, type OrgObject} from "./hooks/OrgContext.ts"; import {
OrgContext,
type OrgContextType,
type OrgObject,
} from "./hooks/OrgContext.ts";
import CreateOrgFlow from "./CreateOrgFlow.tsx"; import CreateOrgFlow from "./CreateOrgFlow.tsx";
const { Header, Content, Sider } = Layout; const { Header, Content, Sider } = Layout;
type MenuItem = Required<MenuProps>['items'][number]; type MenuItem = Required<MenuProps>["items"][number];
function getItem( function getItem(
label: React.ReactNode, label: React.ReactNode,
@ -41,12 +43,11 @@ function getItem(
} as MenuItem; } as MenuItem;
} }
const App: React.FC = () => { const App: React.FC = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation(); const location = useLocation();
const { const {
token: {colorBgContainer, borderRadiusLG}, token: { colorBgContainer, borderRadiusLG },
} = theme.useToken(); } = theme.useToken();
/******************* /*******************
SIGNING IN WITH OIDC SIGNING IN WITH OIDC
@ -55,8 +56,11 @@ const App: React.FC = () => {
const hasTriedSignin = React.useRef(false); const hasTriedSignin = React.useRef(false);
// automatically sign-in // automatically sign-in
React.useEffect(() => { React.useEffect(() => {
if (!hasAuthParams() && if (
!auth.isAuthenticated && !auth.activeNavigator && !auth.isLoading && !hasAuthParams() &&
!auth.isAuthenticated &&
!auth.activeNavigator &&
!auth.isLoading &&
!hasTriedSignin.current !hasTriedSignin.current
) { ) {
auth.signinRedirect(); auth.signinRedirect();
@ -70,19 +74,19 @@ const App: React.FC = () => {
last_name: "", last_name: "",
email: "", email: "",
organisations: [], organisations: [],
id: -1 id: -1,
}, },
setCurrentUser: () => {} setCurrentUser: () => {},
} };
const defaultOrg: OrgContextType = { const defaultOrg: OrgContextType = {
currentOrg: { currentOrg: {
name: "", name: "",
status: "partial", status: "partial",
root_user_email: "", root_user_email: "",
billing_contact: {email: "", id: -1}, billing_contact: { email: "", id: -1 },
owner_contact: {email: "", id: -1}, owner_contact: { email: "", id: -1 },
security_contact: {email: "", id: -1}, security_contact: { email: "", id: -1 },
organisation_id: -1, organisation_id: -1,
intake_questionnaire: { intake_questionnaire: {
question_one: "", question_one: "",
@ -90,13 +94,16 @@ const App: React.FC = () => {
question_three: "", question_three: "",
}, },
}, },
setCurrentOrg: () => {} setCurrentOrg: () => {},
} };
const [currentUser, setCurrentUser] = useState<userObject>(defaultUser.currentUser); const [currentUser, setCurrentUser] = useState<userObject>(
defaultUser.currentUser,
);
const [refreshKey, setRefreshKey] = useState<number>(1); const [refreshKey, setRefreshKey] = useState<number>(1);
const [currentOrg, setCurrentOrg] = useState<OrgObject>(defaultOrg.currentOrg); const [currentOrg, setCurrentOrg] = useState<OrgObject>(
defaultOrg.currentOrg,
);
/***************************** /*****************************
GETTING CURRENT USER FROM API GETTING CURRENT USER FROM API
@ -105,7 +112,9 @@ const App: React.FC = () => {
React.useEffect(() => { React.useEffect(() => {
(async () => { (async () => {
if (!auth.user) { return } if (!auth.user) {
return;
}
try { try {
const token = auth.user.access_token; const token = auth.user.access_token;
const response = await fetch("/api/v1/user/self/db", { const response = await fetch("/api/v1/user/self/db", {
@ -120,9 +129,8 @@ const App: React.FC = () => {
})(); })();
}, [auth, refreshKey]); }, [auth, refreshKey]);
if (!currentUser) { if (!currentUser) {
return( return (
<UserContext.Provider value={{ currentUser, setCurrentUser }}> <UserContext.Provider value={{ currentUser, setCurrentUser }}>
<div>Determining your existing user...</div>; <div>Determining your existing user...</div>;
</UserContext.Provider> </UserContext.Provider>
@ -130,7 +138,7 @@ const App: React.FC = () => {
} }
if (auth.isLoading) { if (auth.isLoading) {
return( return (
<UserContext.Provider value={{ currentUser, setCurrentUser }}> <UserContext.Provider value={{ currentUser, setCurrentUser }}>
<div>Signing you in/out...</div>; <div>Signing you in/out...</div>;
</UserContext.Provider> </UserContext.Provider>
@ -138,49 +146,53 @@ const App: React.FC = () => {
} }
if (!auth.isAuthenticated) { if (!auth.isAuthenticated) {
return( return (
<UserContext.Provider value={{ currentUser, setCurrentUser }}> <UserContext.Provider value={{ currentUser, setCurrentUser }}>
<div>Unable to log in</div>; <div>Unable to log in</div>;
</UserContext.Provider> </UserContext.Provider>
); );
} }
const onClick: MenuProps["onClick"] = (e) => {
const onClick: MenuProps['onClick'] = (e) => {
switch (e.key) { switch (e.key) {
case 'profile': case "profile":
navigate('/profile'); navigate("/profile");
break; break;
case 'orgs': case "orgs":
navigate('/create-org-flow'); navigate("/create-org-flow");
break; break;
case 'logout': case "logout":
auth.removeUser().then(() => navigate('/profile')); auth.removeUser().then(() => navigate("/profile"));
break; break;
case 'bridges': case "bridges":
navigate('/bridges'); navigate("/bridges");
break; break;
default: default:
console.log('Clicked item:', e.key); console.log("Clicked item:", e.key);
} }
}; };
let side_nav_items: MenuItem[] = [];
let side_nav_items: MenuItem[] = []
const top_nav_items = [ const top_nav_items = [
getItem(currentUser.first_name + " " +currentUser.last_name , 'account', <UserOutlined />, [getItem('Account details', 'profile'), getItem('Log out', 'logout')]), getItem(
currentUser.first_name + " " + currentUser.last_name,
"account",
<UserOutlined />,
[getItem("Account details", "profile"), getItem("Log out", "logout")],
),
]; ];
if (currentUser.organisations.length > 0) { if (currentUser.organisations.length > 0) {
side_nav_items = [
getItem("Onboarding", "orgs", <SettingOutlined />),
getItem("Bridges", "bridges", <DeploymentUnitOutlined />),
];
side_nav_items = [ getItem('Onboarding', 'orgs', <SettingOutlined />), const currentOrg = currentUser.organisations[0]["name"];
getItem('Bridges', 'bridges', <DeploymentUnitOutlined />), ]; top_nav_items.push(
getItem(currentOrg + " Settings", "orgsettings", <SettingOutlined />),
const currentOrg = currentUser.organisations[0]['name'] );
top_nav_items.push(getItem(currentOrg +" Settings", 'orgsettings', <SettingOutlined />));
} }
return ( return (
@ -188,34 +200,37 @@ const App: React.FC = () => {
<UserContext.Provider value={{ currentUser, setCurrentUser }}> <UserContext.Provider value={{ currentUser, setCurrentUser }}>
<RefreshContext.Provider value={{ refreshKey, setRefreshKey }}> <RefreshContext.Provider value={{ refreshKey, setRefreshKey }}>
<Layout> <Layout>
<Header style={{ display: 'flex', alignItems: 'right' }}> <Header style={{ display: "flex", alignItems: "right" }}>
<div className="demo-logo" > <div className="demo-logo">
<h1>SR22</h1> <h1>SR22</h1>
</div> </div>
<Menu <Menu
theme="dark" theme="dark"
mode="horizontal" mode="horizontal"
defaultSelectedKeys={['2']} defaultSelectedKeys={["2"]}
items={top_nav_items} items={top_nav_items}
onClick={onClick} onClick={onClick}
style={{ flex: 1, minWidth: 0, justifyContent:"flex-end" }} style={{ flex: 1, minWidth: 0, justifyContent: "flex-end" }}
/> />
</Header> </Header>
<Layout> <Layout>
<Sider width={200} style={{ background: colorBgContainer }}> <Sider width={200} style={{ background: colorBgContainer }}>
<Menu <Menu
mode="inline" mode="inline"
defaultSelectedKeys={['1']} defaultSelectedKeys={["1"]}
defaultOpenKeys={['sub1']} defaultOpenKeys={["sub1"]}
style={{ height: '100%', borderInlineEnd: 0 }} style={{ height: "100%", borderInlineEnd: 0 }}
items={side_nav_items} items={side_nav_items}
onClick={onClick} onClick={onClick}
/> />
</Sider> </Sider>
<Layout style={{ padding: '0 24px 24px' }}> <Layout style={{ padding: "0 24px 24px" }}>
<Breadcrumb <Breadcrumb
items={[{ title: 'Home' }, { title: location.pathname.substring(1) }]} items={[
style={{ margin: '16px 0' }} { title: "Home" },
{ title: location.pathname.substring(1) },
]}
style={{ margin: "16px 0" }}
/> />
<Content <Content
style={{ style={{
@ -226,15 +241,24 @@ const App: React.FC = () => {
borderRadius: borderRadiusLG, borderRadius: borderRadiusLG,
}} }}
> >
<Routes> <Routes>
<Route path="/" element={<Home/>}/> <Route path="/" element={<Home />} />
{currentUser && <Route path="/profile" element={<Profile/>}/> } {currentUser && (
{currentUser && <Route path="/organisations" element={<Organisations/>}/> } <Route path="/profile" element={<Profile />} />
{currentUser && <Route path="/create" element={<CreateOrgFlow/>}/> } )}
{currentUser && (
{currentUser && <Route path="/bridges" element={<Bridges/>}/> } <Route
path="/organisations"
element={<Organisations />}
/>
)}
{currentUser && (
<Route path="/create" element={<CreateOrgFlow />} />
)}
{currentUser && (
<Route path="/bridges" element={<Bridges />} />
)}
</Routes> </Routes>
</Content> </Content>
</Layout> </Layout>
@ -243,10 +267,7 @@ const App: React.FC = () => {
</RefreshContext.Provider> </RefreshContext.Provider>
</UserContext.Provider> </UserContext.Provider>
</OrgContext.Provider> </OrgContext.Provider>
); );
};
} export default App;
export default App

View file

@ -1,10 +1,10 @@
import { useRef, useState } from 'react'; import { useRef, useState } from "react";
import { SearchOutlined } from '@ant-design/icons'; import { SearchOutlined } from "@ant-design/icons";
import type { InputRef, TableColumnsType, TableColumnType } from 'antd'; import type { InputRef, TableColumnsType, TableColumnType } from "antd";
import { Button, Input, Space, Table } from 'antd'; import { Button, Input, Space, Table } from "antd";
import type { FilterDropdownProps } from 'antd/es/table/interface'; import type { FilterDropdownProps } from "antd/es/table/interface";
import Highlighter from 'react-highlight-words'; import Highlighter from "react-highlight-words";
import { QRCode } from 'antd'; import { QRCode } from "antd";
interface BridgeDataType { interface BridgeDataType {
fingerprint: string; fingerprint: string;
bridgeline: string; bridgeline: string;
@ -12,29 +12,49 @@ interface BridgeDataType {
} }
const Bridges = () => { const Bridges = () => {
const bridgeLines: BridgeDataType[] = [ const bridgeLines: BridgeDataType[] = [
{fingerprint: '0C6DDE3F9FC377A5E69DC044E81C857277148D71', bridgeline: "obfs4 62.224.107.157:443 0C6DDE3F9FC377A5E69DC044E81C857277148D71 cert=6vp+cyZmJOp+QFtytv9Ca+Z+ASF4l7r12MeEevvufl4OcDimBhjQlxctjfgdCmnT7iu7Lg iat-mode=0", qrCodeText: "" }, {
{fingerprint:'8DFC222D2295A909B34B3AAAA584648EE6FAF14D', bridgeline: "obfs4 144.31.125.189:2063 8DFC222D2295A909B34B3AAAA584648EE6FAF14D cert=3BPE8q1dHF1AWoqsQEDGZHrRXPw/AaTwyM7YLGMOfFlxY6qRAlW1Jq0LlzKbe4Gh9+SacA iat-mode=0", qrCodeText: "" }, fingerprint: "0C6DDE3F9FC377A5E69DC044E81C857277148D71",
{fingerprint: '0C6DDE3F9FC377A5E69DC044E81C857277148D71', bridgeline: "obfs4 62.224.107.157:443 0C6DDE3F9FC377A5E69DC044E81C857277148D71 cert=6vp+cyZmJOp+QFtytv9Ca+Z+ASF4l7r12MeEevvufl4OcDimBhjQlxctjfgdCmnT7iu7Lg iat-mode=0", qrCodeText: ""}, bridgeline:
{fingerprint:'8DFC222D2295A909B34B3AAAA584648EE6FAF14D', bridgeline: "obfs4 144.31.125.189:2063 8DFC222D2295A909B34B3AAAA584648EE6FAF14D cert=3BPE8q1dHF1AWoqsQEDGZHrRXPw/AaTwyM7YLGMOfFlxY6qRAlW1Jq0LlzKbe4Gh9+SacA iat-mode=0", qrCodeText: ""}, "obfs4 62.224.107.157:443 0C6DDE3F9FC377A5E69DC044E81C857277148D71 cert=6vp+cyZmJOp+QFtytv9Ca+Z+ASF4l7r12MeEevvufl4OcDimBhjQlxctjfgdCmnT7iu7Lg iat-mode=0",
qrCodeText: "",
},
{
fingerprint: "8DFC222D2295A909B34B3AAAA584648EE6FAF14D",
bridgeline:
"obfs4 144.31.125.189:2063 8DFC222D2295A909B34B3AAAA584648EE6FAF14D cert=3BPE8q1dHF1AWoqsQEDGZHrRXPw/AaTwyM7YLGMOfFlxY6qRAlW1Jq0LlzKbe4Gh9+SacA iat-mode=0",
qrCodeText: "",
},
{
fingerprint: "0C6DDE3F9FC377A5E69DC044E81C857277148D71",
bridgeline:
"obfs4 62.224.107.157:443 0C6DDE3F9FC377A5E69DC044E81C857277148D71 cert=6vp+cyZmJOp+QFtytv9Ca+Z+ASF4l7r12MeEevvufl4OcDimBhjQlxctjfgdCmnT7iu7Lg iat-mode=0",
qrCodeText: "",
},
{
fingerprint: "8DFC222D2295A909B34B3AAAA584648EE6FAF14D",
bridgeline:
"obfs4 144.31.125.189:2063 8DFC222D2295A909B34B3AAAA584648EE6FAF14D cert=3BPE8q1dHF1AWoqsQEDGZHrRXPw/AaTwyM7YLGMOfFlxY6qRAlW1Jq0LlzKbe4Gh9+SacA iat-mode=0",
qrCodeText: "",
},
]; ];
function makeQRCodeText(bridgeline: string) { function makeQRCodeText(bridgeline: string) {
return '["' + bridgeline + '"]'; return '["' + bridgeline + '"]';
} }
bridgeLines.forEach((line) => {line.qrCodeText = makeQRCodeText(line.bridgeline)}); bridgeLines.forEach((line) => {
line.qrCodeText = makeQRCodeText(line.bridgeline);
});
console.log(bridgeLines); console.log(bridgeLines);
const [searchText, setSearchText] = useState(''); const [searchText, setSearchText] = useState("");
const [searchedColumn, setSearchedColumn] = useState(''); const [searchedColumn, setSearchedColumn] = useState("");
const searchInput = useRef<InputRef>(null); const searchInput = useRef<InputRef>(null);
const handleSearch = ( const handleSearch = (
selectedKeys: string[], selectedKeys: string[],
confirm: FilterDropdownProps['confirm'], confirm: FilterDropdownProps["confirm"],
dataIndex: DataIndex, dataIndex: DataIndex,
) => { ) => {
confirm(); confirm();
@ -44,25 +64,39 @@ const Bridges = () => {
const handleReset = (clearFilters: () => void) => { const handleReset = (clearFilters: () => void) => {
clearFilters(); clearFilters();
setSearchText(''); setSearchText("");
}; };
type DataIndex = keyof BridgeDataType; type DataIndex = keyof BridgeDataType;
const getColumnSearchProps = (dataIndex: DataIndex): TableColumnType<BridgeDataType> => ({ const getColumnSearchProps = (
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters, close }) => ( dataIndex: DataIndex,
): TableColumnType<BridgeDataType> => ({
filterDropdown: ({
setSelectedKeys,
selectedKeys,
confirm,
clearFilters,
close,
}) => (
<div style={{ padding: 8 }} onKeyDown={(e) => e.stopPropagation()}> <div style={{ padding: 8 }} onKeyDown={(e) => e.stopPropagation()}>
<Input <Input
//ref={searchInput} //ref={searchInput}
placeholder={`Search ${dataIndex}`} placeholder={`Search ${dataIndex}`}
value={selectedKeys[0]} value={selectedKeys[0]}
onChange={(e) => setSelectedKeys(e.target.value ? [e.target.value] : [])} onChange={(e) =>
onPressEnter={() => handleSearch(selectedKeys as string[], confirm, dataIndex)} setSelectedKeys(e.target.value ? [e.target.value] : [])
style={{ marginBottom: 8, display: 'block' }} }
onPressEnter={() =>
handleSearch(selectedKeys as string[], confirm, dataIndex)
}
style={{ marginBottom: 8, display: "block" }}
/> />
<Space> <Space>
<Button <Button
type="primary" type="primary"
onClick={() => handleSearch(selectedKeys as string[], confirm, dataIndex)} onClick={() =>
handleSearch(selectedKeys as string[], confirm, dataIndex)
}
icon={<SearchOutlined />} icon={<SearchOutlined />}
size="small" size="small"
style={{ width: 90 }} style={{ width: 90 }}
@ -100,7 +134,7 @@ const Bridges = () => {
</div> </div>
), ),
filterIcon: (filtered: boolean) => ( filterIcon: (filtered: boolean) => (
<SearchOutlined style={{ color: filtered ? '#1677ff' : undefined }} /> <SearchOutlined style={{ color: filtered ? "#1677ff" : undefined }} />
), ),
onFilter: (value, record) => onFilter: (value, record) =>
record[dataIndex] record[dataIndex]
@ -117,10 +151,10 @@ const Bridges = () => {
render: (text) => render: (text) =>
searchedColumn === dataIndex ? ( searchedColumn === dataIndex ? (
<Highlighter <Highlighter
highlightStyle={{ backgroundColor: '#ffc069', padding: 0 }} highlightStyle={{ backgroundColor: "#ffc069", padding: 0 }}
searchWords={[searchText]} searchWords={[searchText]}
autoEscape autoEscape
textToHighlight={text ? text.toString() : ''} textToHighlight={text ? text.toString() : ""}
/> />
) : ( ) : (
text text
@ -128,34 +162,42 @@ const Bridges = () => {
}); });
const bridgesColumns: TableColumnsType<BridgeDataType> = [ const bridgesColumns: TableColumnsType<BridgeDataType> = [
{title: 'Fingerprint', {
dataIndex: 'fingerprint', title: "Fingerprint",
key: 'fingerprint', dataIndex: "fingerprint",
width: '30%', key: "fingerprint",
...getColumnSearchProps('fingerprint')}, width: "30%",
...getColumnSearchProps("fingerprint"),
{title: 'Bridgeline',
dataIndex: 'bridgeline',
key: 'bridgeline',
width: '40%',
...getColumnSearchProps('bridgeline')},
{title: 'QR Code',
width: '40%',
key: 'qrCodeText',
dataIndex: 'qrCodeText',
render: (_text, record) => {return (<QRCode value={record.qrCodeText} />)}
}, },
] {
title: "Bridgeline",
dataIndex: "bridgeline",
key: "bridgeline",
width: "40%",
...getColumnSearchProps("bridgeline"),
},
{
title: "QR Code",
width: "40%",
key: "qrCodeText",
dataIndex: "qrCodeText",
render: (_text, record) => {
return <QRCode value={record.qrCodeText} />;
},
},
];
return( return (
<> <>
<Table<BridgeDataType>
<Table<BridgeDataType> columns={bridgesColumns} dataSource={bridgeLines} />; columns={bridgesColumns}
</>); dataSource={bridgeLines}
/>
} ;
</>
);
};
export default Bridges; export default Bridges;

View file

@ -1,59 +1,61 @@
// src/Posts.jsx // src/Posts.jsx
import {useUserContext} from './hooks/UserContext.ts'; import { useUserContext } from "./hooks/UserContext.ts";
import { Button, Form, Input } from 'antd'; import { Button, Form, Input } from "antd";
//import React, {useEffect, useState} from "react"; //import React, {useEffect, useState} from "react";
import {useAuth} from "react-oidc-context"; import { useAuth } from "react-oidc-context";
import {RefreshContext} from "./hooks/RefreshContext.ts"; import { RefreshContext } from "./hooks/RefreshContext.ts";
import {useContext, useState} from "react"; import { useContext, useState } from "react";
import {useOrgContext} from "./hooks/OrgContext.ts"; import { useOrgContext } from "./hooks/OrgContext.ts";
const CreateOrgStep1 = () => { const CreateOrgStep1 = () => {
const {currentUser} = useUserContext(); const { currentUser } = useUserContext();
const [form] = Form.useForm(); const [form] = Form.useForm();
const auth = useAuth(); const auth = useAuth();
const {refreshKey, setRefreshKey} = useContext(RefreshContext); const { refreshKey, setRefreshKey } = useContext(RefreshContext);
const {currentOrg, setCurrentOrg} = useOrgContext(); const { currentOrg, setCurrentOrg } = useOrgContext();
console.log('context value:', {currentOrg, setCurrentOrg}); console.log("context value:", { currentOrg, setCurrentOrg });
const {TextArea} = Input; const { TextArea } = Input;
const [currentStep, setCurrentStep] = useState(1); const [currentStep, setCurrentStep] = useState(1);
async function submitOrgBasicData(values: FormData) { async function submitOrgBasicData(values: FormData) {
if (!auth.user) { if (!auth.user) {
return return;
} }
try { try {
const [name, q1, q2] = Object.values(values); const [name, q1, q2] = Object.values(values);
const data = { const data = {
"name": name, name: name,
"intake_questionnaire": { intake_questionnaire: {
"question_one": q1, question_one: q1,
"question_two": q2, question_two: q2,
} },
} };
const token = auth.user.access_token; const token = auth.user.access_token;
const requestOptions = { const requestOptions = {
method: 'POST', method: "POST",
body: JSON.stringify(data), body: JSON.stringify(data),
headers: { headers: {
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
'Content-Type': 'application/json' "Content-Type": "application/json",
} },
} };
const response_org = await fetch("/api/v1/org", requestOptions); const response_org = await fetch("/api/v1/org", requestOptions);
const orgID = await response_org.json(); const orgID = await response_org.json();
const response_currentOrg = await fetch("/api/v1/org?org_id=" + orgID['id'], { const response_currentOrg = await fetch(
"/api/v1/org?org_id=" + orgID["id"],
{
headers: { headers: {
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
'Content-Type': 'application/json' "Content-Type": "application/json",
} },
}); },
const currentOrgData = await response_currentOrg.json() );
setCurrentOrg(currentOrgData['organisations'][0]); const currentOrgData = await response_currentOrg.json();
setCurrentOrg(currentOrgData["organisations"][0]);
setCurrentStep(currentStep + 1); setCurrentStep(currentStep + 1);
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
@ -61,36 +63,44 @@ const CreateOrgStep1 = () => {
async function submitOrgBillingData(values: FormData) { async function submitOrgBillingData(values: FormData) {
if (!auth.user) { if (!auth.user) {
return return;
} }
try { try {
const [first_name, last_name, email, phone_number, vat_number, street_address, street_address_line_2, locality] = Object.values(values); const [
first_name,
last_name,
email,
phone_number,
vat_number,
street_address,
street_address_line_2,
locality,
] = Object.values(values);
const data = { const data = {
"organisation_id": currentOrg.organisation_id, organisation_id: currentOrg.organisation_id,
"contact_type": "billing", contact_type: "billing",
"first_name": first_name, first_name: first_name,
"last_name": last_name, last_name: last_name,
"email": email, email: email,
"phone_number": phone_number, phone_number: phone_number,
"vat_number": vat_number, vat_number: vat_number,
"street_address": street_address, street_address: street_address,
"street_address_line_2": street_address_line_2, street_address_line_2: street_address_line_2,
"locality": locality, locality: locality,
} };
const token = auth.user.access_token; const token = auth.user.access_token;
const requestOptions = { const requestOptions = {
method: 'PATCH', method: "PATCH",
body: JSON.stringify(data), body: JSON.stringify(data),
headers: { headers: {
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
'Content-Type': 'application/json' "Content-Type": "application/json",
} },
} };
await fetch("/api/v1/org/contact", requestOptions); await fetch("/api/v1/org/contact", requestOptions);
setCurrentStep(currentStep + 1); setCurrentStep(currentStep + 1);
setRefreshKey(refreshKey +1); setRefreshKey(refreshKey + 1);
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
@ -104,28 +114,49 @@ const CreateOrgStep1 = () => {
return ( return (
<> <>
<h2>Step 1 - Basic info</h2> <h2>Step 1 - Basic info</h2>
<Form <Form layout="vertical" form={form} onFinish={submitOrgBasicData}>
layout="vertical" <Form.Item
form={form} name="Organisation name"
onFinish={submitOrgBasicData} label="Organisation name"
rules={[{ required: true }]}
> >
<Form.Item name="Organisation name" label="Organisation name" rules={[{required: true}]}> <Input />
<Input/>
</Form.Item> </Form.Item>
<h4>Intake Questionnaire</h4> <h4>Intake Questionnaire</h4>
<Form.Item name="Question 1" label="What region(s) does your organisation operate in?"> <Form.Item
<TextArea showCount maxLength={100} placeholder="e.g., Southern Africa"/> name="Question 1"
label="What region(s) does your organisation operate in?"
>
<TextArea
showCount
maxLength={100}
placeholder="e.g., Southern Africa"
/>
</Form.Item> </Form.Item>
<Form.Item name="Question 2" label="Briefly describe how your organisation supports civil society."> <Form.Item
<TextArea showCount maxLength={300} name="Question 2"
placeholder="e.g., Our organisation supports various groups such as ..."/> label="Briefly describe how your organisation supports civil society."
>
<TextArea
showCount
maxLength={300}
placeholder="e.g., Our organisation supports various groups such as ..."
/>
</Form.Item> </Form.Item>
<Form.Item name="Question 3" label="Name two references/contacts within the community, and briefly describe the nature of your relationsip and projects you've worked on together."> <Form.Item
<TextArea showCount maxLength={500} name="Question 3"
placeholder="e.g., I worked with SR2 Communications on providing digital security training from 2023-2025."/> label="Name two references/contacts within the community, and briefly describe the nature of your relationsip and projects you've worked on together."
>
<TextArea
showCount
maxLength={500}
placeholder="e.g., I worked with SR2 Communications on providing digital security training from 2023-2025."
/>
</Form.Item> </Form.Item>
<Form.Item> <Form.Item>
<Button type="primary" htmlType="submit">Submit</Button> <Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item> </Form.Item>
</Form> </Form>
</> </>
@ -133,55 +164,91 @@ const CreateOrgStep1 = () => {
} }
if (currentStep === 2) { if (currentStep === 2) {
return <> return (
<>
<h2>Step 2 - Contact and billing details</h2> <h2>Step 2 - Contact and billing details</h2>
<Form <Form layout="vertical" form={form} onFinish={submitOrgBillingData}>
layout="vertical" <Form.Item
form={form} name="Billing contact first name"
onFinish={submitOrgBillingData} label="Billing contact first name"
rules={[{ required: true }]}
> >
<Form.Item name="Billing contact first name" label="Billing contact first name" <Input />
rules={[{required: true}]}>
<Input/>
</Form.Item> </Form.Item>
<Form.Item name="Billing contact last name" label="Billing contact last name" <Form.Item
rules={[{required: true}]}> name="Billing contact last name"
<Input/> label="Billing contact last name"
rules={[{ required: true }]}
>
<Input />
</Form.Item> </Form.Item>
<Form.Item name="Billing contact email" label="Billing contact email address" <Form.Item
rules={[{required: true, type: "email", message: "Not a valid e-mail address."}]}> name="Billing contact email"
<Input/> label="Billing contact email address"
rules={[
{
required: true,
type: "email",
message: "Not a valid e-mail address.",
},
]}
>
<Input />
</Form.Item> </Form.Item>
<Form.Item name="Billing contact phone" label="Billing contact phone number"> <Form.Item
<Input/> name="Billing contact phone"
label="Billing contact phone number"
>
<Input />
</Form.Item> </Form.Item>
<Form.Item name="Billing contact VAT number" label="Billing contact VAT number"> <Form.Item
<Input/> name="Billing contact VAT number"
label="Billing contact VAT number"
>
<Input />
</Form.Item> </Form.Item>
<Form.Item name="Billing address line 1" label="Billing address line 1" rules={[{required: true}]}> <Form.Item
<Input/> name="Billing address line 1"
label="Billing address line 1"
rules={[{ required: true }]}
>
<Input />
</Form.Item> </Form.Item>
<Form.Item name="Billing address line 2" label="Billing address line 2"> <Form.Item
<Input/> name="Billing address line 2"
label="Billing address line 2"
>
<Input />
</Form.Item> </Form.Item>
<Form.Item name="locality" label="City" rules={[{required: true}]}> <Form.Item name="locality" label="City" rules={[{ required: true }]}>
<Input/> <Input />
</Form.Item> </Form.Item>
<Form.Item> <Form.Item>
<Button type="primary" htmlType="submit">Submit</Button> <Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item> </Form.Item>
</Form> </Form>
</> </>
);
} }
if (currentStep === 3) { if (currentStep === 3) {
return <> <h2>Thank you for submitting your organisation info. </h2> return (
<p> This will be manually reviewed by a member of SR2 Cloud and you will be contacted shortly. <>
We aim to complete reviews within 24 hours. You can change your answers at any point from the {" "}
'My organisations' section.</p> <h2>Thank you for submitting your organisation info. </h2>
<p>
{" "}
This will be manually reviewed by a member of SR2 Cloud and you will
be contacted shortly. We aim to complete reviews within 24 hours. You
can change your answers at any point from the 'My organisations'
section.
</p>
</> </>
);
} }
} };
export default CreateOrgStep1; export default CreateOrgStep1;

View file

@ -1,7 +1,7 @@
import {Breadcrumb, Button, theme} from "antd"; import { Breadcrumb, Button, theme } from "antd";
import React from "react"; import React from "react";
import {useUserContext} from "./hooks/UserContext.ts"; import { useUserContext } from "./hooks/UserContext.ts";
import {useNavigate} from "react-router"; import { useNavigate } from "react-router";
import Organisations from "./Organisations.tsx"; import Organisations from "./Organisations.tsx";
const Home: React.FC = () => { const Home: React.FC = () => {
@ -12,18 +12,34 @@ const Home: React.FC = () => {
const { currentUser } = useUserContext(); const { currentUser } = useUserContext();
if (currentUser.organisations.length === 0) { if (currentUser.organisations.length === 0) {
return <> return (
<>
<h2>Welcome to SR2 Cloud! </h2> <h2>Welcome to SR2 Cloud! </h2>
<h3>If your organisation already uses SR2 Cloud, please ask your administrator to add you to the organisation.</h3> <h3>
<p> If your organanisation is new to SR2 Cloud, start the onboarding process </p> If your organisation already uses SR2 Cloud, please ask your
administrator to add you to the organisation.
</h3>
<p>
{" "}
If your organanisation is new to SR2 Cloud, start the onboarding
process{" "}
</p>
<Button type="primary" onClick={() => navigate('/create')}> Start onboarding </Button> <Button type="primary" onClick={() => navigate("/create")}>
{" "}
Start onboarding{" "}
</Button>
</> </>
);
} }
//TODO: fix this breadcrumb //TODO: fix this breadcrumb
return (<> return (
<Breadcrumb style={{ margin: '16px 0' }} items={[{ title: 'User' }, { title: 'Dashboard' }]} /> <>
<Breadcrumb
style={{ margin: "16px 0" }}
items={[{ title: "User" }, { title: "Dashboard" }]}
/>
<div <div
style={{ style={{
padding: 24, padding: 24,
@ -34,10 +50,11 @@ const Home: React.FC = () => {
> >
<h1>Welcome to your dashboard, {currentUser.first_name}!</h1> <h1>Welcome to your dashboard, {currentUser.first_name}!</h1>
<div> <div>
<Organisations/> <Organisations />
</div> </div>
</div> </div>
</>) </>
} );
};
export default Home; export default Home;

View file

@ -1,52 +1,55 @@
import {Button, Card, Space} from 'antd'; import { Button, Card, Space } from "antd";
import type {OrgObject} from "./hooks/OrgContext.ts"; import type { OrgObject } from "./hooks/OrgContext.ts";
import {useUserContext} from "./hooks/UserContext.ts"; import { useUserContext } from "./hooks/UserContext.ts";
import {useAuth} from "react-oidc-context"; import { useAuth } from "react-oidc-context";
import {useContext} from "react"; import { useContext } from "react";
import {RefreshContext} from "./hooks/RefreshContext.ts"; import { RefreshContext } from "./hooks/RefreshContext.ts";
const OrgPanel = (props: OrgObject) => { const OrgPanel = (props: OrgObject) => {
const auth = useAuth(); const auth = useAuth();
const { refreshKey, setRefreshKey } = useContext(RefreshContext); const { refreshKey, setRefreshKey } = useContext(RefreshContext);
const {currentUser} = useUserContext(); const { currentUser } = useUserContext();
async function removeUserFromOrg(org_id: number) { async function removeUserFromOrg(org_id: number) {
if (!auth.user) { if (!auth.user) {
return return;
} }
try { try {
const token = auth.user.access_token; const token = auth.user.access_token;
const requestOptions = { const requestOptions = {
method: 'DELETE', method: "DELETE",
headers: {Authorization: `Bearer ${token}`, headers: {
'Content-Type': 'application/json'} Authorization: `Bearer ${token}`,
} "Content-Type": "application/json",
const response = await fetch("/api/v1/org/user?org_id=" + org_id + "&user_id=" + currentUser.id, requestOptions); },
};
const response = await fetch(
"/api/v1/org/user?org_id=" + org_id + "&user_id=" + currentUser.id,
requestOptions,
);
await response.json(); await response.json();
setRefreshKey(prev => prev + 1); setRefreshKey((prev) => prev + 1);
console.log(refreshKey) console.log(refreshKey);
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
} }
async function removeOrg(org_id: number) { async function removeOrg(org_id: number) {
if (!auth.user) { if (!auth.user) {
return return;
} }
try { try {
const token = auth.user.access_token; const token = auth.user.access_token;
const requestOptions = { const requestOptions = {
method: 'DELETE', method: "DELETE",
headers: {Authorization: `Bearer ${token}`, headers: {
'Content-Type': 'application/json'} Authorization: `Bearer ${token}`,
} "Content-Type": "application/json",
},
};
await fetch("/api/v1/org/self?org_id=" + org_id, requestOptions); await fetch("/api/v1/org/self?org_id=" + org_id, requestOptions);
setRefreshKey(prev => prev + 1); setRefreshKey((prev) => prev + 1);
console.log(refreshKey) console.log(refreshKey);
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
@ -54,23 +57,34 @@ const OrgPanel = (props: OrgObject) => {
return ( return (
<Space vertical size={16}> <Space vertical size={16}>
<Card title={props.name} extra={<a href="#">More</a>} style={{width: 300}}> <Card
title={props.name}
extra={<a href="#">More</a>}
style={{ width: 300 }}
>
<p>Validated? {props.status}</p> <p>Validated? {props.status}</p>
<p>Root User: {props.root_user_email}</p> <p>Root User: {props.root_user_email}</p>
<p>Billing Contact: {props.billing_contact?.email || 'Not set'}</p> <p>Billing Contact: {props.billing_contact?.email || "Not set"}</p>
<h3>Questionnaire</h3> <h3>Questionnaire</h3>
<p>Q1: {props.intake_questionnaire?.question_one || 'Not set'}</p> <p>Q1: {props.intake_questionnaire?.question_one || "Not set"}</p>
<p>Q2: {props.intake_questionnaire?.question_two || 'Not set'}</p> <p>Q2: {props.intake_questionnaire?.question_two || "Not set"}</p>
<p>Q3: {props.intake_questionnaire?.question_three || 'Not set'}</p> <p>Q3: {props.intake_questionnaire?.question_three || "Not set"}</p>
<Button type="primary" onClick={() => removeUserFromOrg(props.organisation_id)}> <Button
type="primary"
onClick={() => removeUserFromOrg(props.organisation_id)}
>
Remove yourself from organisation Remove yourself from organisation
</Button> </Button>
<Button type="primary" danger onClick={() => removeOrg(props.organisation_id)}> <Button
type="primary"
danger
onClick={() => removeOrg(props.organisation_id)}
>
Remove organisation Remove organisation
</Button> </Button>
</Card> </Card>
</Space> </Space>
); );
} };
export default OrgPanel; export default OrgPanel;

View file

@ -1,9 +1,9 @@
import React, {useState} from "react"; import React, { useState } from "react";
import OrgPanel from "./OrgPanel"; import OrgPanel from "./OrgPanel";
import {useAuth} from "react-oidc-context"; import { useAuth } from "react-oidc-context";
import type {OrgObject} from "./hooks/OrgContext.ts"; import type { OrgObject } from "./hooks/OrgContext.ts";
import type { JSX } from "react/jsx-runtime"; import type { JSX } from "react/jsx-runtime";
import {useRefreshContext} from "./hooks/RefreshContext.ts"; import { useRefreshContext } from "./hooks/RefreshContext.ts";
const Organisations: React.FC = () => { const Organisations: React.FC = () => {
const auth = useAuth(); const auth = useAuth();
@ -12,37 +12,39 @@ const Organisations: React.FC = () => {
React.useEffect(() => { React.useEffect(() => {
const fetchOrgData = async () => { const fetchOrgData = async () => {
console.log("Fetching org data"); console.log("Fetching org data");
if (!auth.user) return if (!auth.user) return;
try { try {
const requestOptions = { const requestOptions = {
method: 'GET', method: "GET",
headers: { headers: {
Authorization: `Bearer ${auth.user.access_token}`, Authorization: `Bearer ${auth.user.access_token}`,
'Content-Type': 'application/json' "Content-Type": "application/json",
} },
} };
const response = await fetch("/api/v1/user/self/orgs", requestOptions); const response = await fetch("/api/v1/user/self/orgs", requestOptions);
const orgsObject = await response.json(); const orgsObject = await response.json();
setCurrentOrgData(orgsObject['organisations']) setCurrentOrgData(orgsObject["organisations"]);
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
} };
fetchOrgData() fetchOrgData().catch(console.error);
.catch(console.error);
}, [auth.user, refreshKey]); }, [auth.user, refreshKey]);
console.log(orgData); console.log(orgData);
let panels: JSX.Element[] = []; let panels: JSX.Element[] = [];
if (orgData) { if (orgData) {
panels = orgData.map(org => <OrgPanel {...org} key={org.organisation_id} />); panels = orgData.map((org) => (
<OrgPanel {...org} key={org.organisation_id} />
));
} }
return(<> return (
<>
<h1>Your organisations</h1> <h1>Your organisations</h1>
{panels} {panels}
</>); </>
} );
};
export default Organisations; export default Organisations;

View file

@ -1,6 +1,6 @@
// src/Posts.jsx // src/Posts.jsx
import { useUserContext} from './hooks/UserContext.ts'; import { useUserContext } from "./hooks/UserContext.ts";
const Profile = () => { const Profile = () => {
const { currentUser } = useUserContext(); const { currentUser } = useUserContext();
@ -10,14 +10,16 @@ const Profile = () => {
} }
console.log(currentUser); console.log(currentUser);
console.log(currentUser.organisations); console.log(currentUser.organisations);
const userOrgs: { name: string, id: number }[] = currentUser.organisations; const userOrgs: { name: string; id: number }[] = currentUser.organisations;
return ( return (
<> <>
<p>User: {currentUser.first_name} {currentUser.last_name}</p> <p>
User: {currentUser.first_name} {currentUser.last_name}
</p>
<p>Email: {currentUser.email}</p> <p>Email: {currentUser.email}</p>
{userOrgs[0] && <p>Current Orgs: {userOrgs[0].name}</p>} {userOrgs[0] && <p>Current Orgs: {userOrgs[0].name}</p>}
</> </>
); );
}; };
export default Profile; export default Profile;

View file

@ -1,4 +1,4 @@
import React, {createContext, useContext} from 'react'; import React, { createContext, useContext } from "react";
export interface Contact { export interface Contact {
email: string; email: string;
@ -6,18 +6,18 @@ export interface Contact {
} }
export interface OrgObject { export interface OrgObject {
name: string, name: string;
status: "partial", status: "partial";
root_user_email: string, root_user_email: string;
billing_contact: Contact, billing_contact: Contact;
owner_contact: Contact, owner_contact: Contact;
security_contact:Contact, security_contact: Contact;
organisation_id: number, organisation_id: number;
intake_questionnaire: { intake_questionnaire: {
question_one: string, question_one: string;
question_two: string, question_two: string;
question_three: string question_three: string;
}, };
} }
export interface OrgContextType { export interface OrgContextType {
@ -30,7 +30,7 @@ export const OrgContext = createContext<OrgContextType | undefined>(undefined);
export const useOrgContext = () => { export const useOrgContext = () => {
const context: OrgContextType | undefined = useContext(OrgContext); const context: OrgContextType | undefined = useContext(OrgContext);
if (context === undefined) { if (context === undefined) {
throw new Error('Organisation context not found'); throw new Error("Organisation context not found");
} }
return context; return context;
}; };

View file

@ -1,4 +1,4 @@
import React, {createContext, useContext} from 'react'; import React, { createContext, useContext } from "react";
export interface RefreshType { export interface RefreshType {
refreshKey: number; refreshKey: number;
@ -13,7 +13,7 @@ export const RefreshContext = createContext<RefreshType>({
export const useRefreshContext = () => { export const useRefreshContext = () => {
const context: RefreshType | undefined = useContext(RefreshContext); const context: RefreshType | undefined = useContext(RefreshContext);
if (context === undefined) { if (context === undefined) {
throw new Error('Organisation refresh context not found'); throw new Error("Organisation refresh context not found");
} }
return context; return context;
}; };

View file

@ -1,11 +1,10 @@
import React, { createContext, useContext } from 'react'; import React, { createContext, useContext } from "react";
export interface userObject { export interface userObject {
first_name: string; first_name: string;
last_name: string; last_name: string;
email: string; email: string;
organisations: { name: string, id: number }[]; organisations: { name: string; id: number }[];
id: number; id: number;
} }
@ -14,14 +13,14 @@ export interface UserContextType {
setCurrentUser: React.Dispatch<React.SetStateAction<userObject>>; setCurrentUser: React.Dispatch<React.SetStateAction<userObject>>;
} }
export const UserContext = createContext<UserContextType | undefined>(undefined); export const UserContext = createContext<UserContextType | undefined>(
undefined,
);
export const useUserContext = () => { export const useUserContext = () => {
const context: UserContextType | undefined = useContext(UserContext); const context: UserContextType | undefined = useContext(UserContext);
if (context === undefined) { if (context === undefined) {
throw new Error('User context not found!'); throw new Error("User context not found!");
} }
return context; return context;
}; };

View file

@ -1,27 +1,29 @@
import {StrictMode} from 'react' import { StrictMode } from "react";
import {createRoot} from 'react-dom/client' import { createRoot } from "react-dom/client";
import './index.css' import "./index.css";
import App from './App.tsx' import App from "./App.tsx";
import {AuthProvider, type AuthProviderProps} from "react-oidc-context"; import { AuthProvider, type AuthProviderProps } from "react-oidc-context";
import {type User, WebStorageStateStore} from "oidc-client-ts"; import { type User, WebStorageStateStore } from "oidc-client-ts";
import {BrowserRouter} from "react-router"; import { BrowserRouter } from "react-router";
const onSigninCallback = (_user: User | void): void => { const onSigninCallback = (_user: User | void): void => {
window.history.replaceState({}, document.title, window.location.pathname) window.history.replaceState({}, document.title, window.location.pathname);
} };
const oidcConfig: AuthProviderProps = { const oidcConfig: AuthProviderProps = {
authority: "https://sso.sr2.uk/realms/sr2", authority: "https://sso.sr2.uk/realms/sr2",
client_id: "chris-dev", client_id: "chris-dev",
redirect_uri: window.location.href, redirect_uri: window.location.href,
onSigninCallback: onSigninCallback, onSigninCallback: onSigninCallback,
userStore: new WebStorageStateStore({store: window.localStorage}), userStore: new WebStorageStateStore({ store: window.localStorage }),
}; };
createRoot(document.getElementById('root')!).render(<StrictMode> createRoot(document.getElementById("root")!).render(
<StrictMode>
<AuthProvider {...oidcConfig}> <AuthProvider {...oidcConfig}>
<BrowserRouter basename="/ui"> <BrowserRouter basename="/ui">
<App/> <App />
</BrowserRouter> </BrowserRouter>
</AuthProvider> </AuthProvider>
</StrictMode>,) </StrictMode>,
);

View file

@ -1,23 +1,23 @@
import { defineConfig } from 'vite' import { defineConfig } from "vite";
import react from '@vitejs/plugin-react' import react from "@vitejs/plugin-react";
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig({ export default defineConfig({
base: '/ui/', base: "/ui/",
plugins: [react()], plugins: [react()],
server: { server: {
proxy: { proxy: {
'/api': { "/api": {
target: 'http://localhost:8000', target: "http://localhost:8000",
changeOrigin: true, changeOrigin: true,
secure: false, secure: false,
ws: true, ws: true,
configure: (proxy, _options) => { configure: (proxy, _options) => {
proxy.on('error', err => { proxy.on("error", (err) => {
console.error(err.message) console.error(err.message);
}) });
} },
} },
} },
} },
}) });